[Scummvm-git-logs] scummvm master -> 608e9af69d203d4dd4fc65d16ef808d945341ed3

dreammaster paulfgilbert at gmail.com
Sat May 25 03:29:41 CEST 2019


This automated email contains information about 20 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
b1f8e2ce83 GLK: HUGO: Move Hugo engine out of RELEASE_BUILD blocks
54d240d81f GLK: TADS: Improved detection to detect TADS version
0279143a62 GLK: TADS2: Adding headers
e83972f502 GLK: TADS2: Added code for tokenizer & vocabulary
105a1b94bd GLK: TADS2: Added code for output, run, various miscellaneous
f607792fa4 GLK: TADS2: More code files implemented
fcb2592ec2 GLK: TADS2: Added built in functions
3d9e03af55 GLK: TADS2: Soooo much more implementation
bc87d740f5 GLK: TADS2: Yet more volumous code additions
5aacb45645 GLK: TADS2: Added plygo
e3454b5d96 GLK: TADS: Don't support RTL
0d172c8ca6 GLK: Fix freeing closed windows
0c02346b48 GLK: TADS2: Fix os initialization
aa5fd603b6 GLK: TADS2: Compilation fixes
1a974b9749 GLK: TADS2: Further compilation fixes
5381930e8b GLK: TADS2: Fix uninitialized variable warnings
8f9fcec16c GLK: TADS2: Further compilation fixes
0d0dbf601e GLK: TADS2: Fix uninitialized variable warnings
139673984e GLK: TADS2: Revert char * statics back to string literals
608e9af69d GLK: TADS2: Fix more const loss warnings


Commit: b1f8e2ce837608ee18ebabe1097d64faae9c2243
    https://github.com/scummvm/scummvm/commit/b1f8e2ce837608ee18ebabe1097d64faae9c2243
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: HUGO: Move Hugo engine out of RELEASE_BUILD blocks

Changed paths:
    engines/glk/detection.cpp


diff --git a/engines/glk/detection.cpp b/engines/glk/detection.cpp
index fe2ea0b..a13bed3 100644
--- a/engines/glk/detection.cpp
+++ b/engines/glk/detection.cpp
@@ -113,10 +113,10 @@ Common::Error GlkMetaEngine::createInstance(OSystem *syst, Engine **engine) cons
 	*engine = nullptr;
 	if ((*engine = create<Glk::Frotz::FrotzMetaEngine, Glk::Frotz::Frotz>(syst, gameDesc)) != nullptr) {}
 	else if ((*engine = create<Glk::Glulxe::GlulxeMetaEngine, Glk::Glulxe::Glulxe>(syst, gameDesc)) != nullptr) {}
+	else if ((*engine = create<Glk::Hugo::HugoMetaEngine, Glk::Hugo::Hugo>(syst, gameDesc)) != nullptr) {}
 	else if ((*engine = create<Glk::Scott::ScottMetaEngine, Glk::Scott::Scott>(syst, gameDesc)) != nullptr) {}
 #ifndef RELEASE_BUILD
 	else if ((*engine = create<Glk::Alan2::Alan2MetaEngine, Glk::Alan2::Alan2>(syst, gameDesc)) != nullptr) {}
-	else if ((*engine = create<Glk::Hugo::HugoMetaEngine, Glk::Hugo::Hugo>(syst, gameDesc)) != nullptr) {}
 	else if ((*engine = create<Glk::Magnetic::MagneticMetaEngine, Glk::Magnetic::Magnetic>(syst, gameDesc)) != nullptr) {}
 	else if ((td = Glk::TADS::TADSMetaEngine::findGame(gameDesc._gameId.c_str()))._description) {
 		if (td._options & Glk::TADS::OPTION_TADS3)
@@ -158,10 +158,10 @@ PlainGameList GlkMetaEngine::getSupportedGames() const {
 	PlainGameList list;
 	Glk::Frotz::FrotzMetaEngine::getSupportedGames(list);
 	Glk::Glulxe::GlulxeMetaEngine::getSupportedGames(list);
+	Glk::Hugo::HugoMetaEngine::getSupportedGames(list);
 	Glk::Scott::ScottMetaEngine::getSupportedGames(list);
 #ifndef RELEASE_BUILD
 	Glk::Alan2::Alan2MetaEngine::getSupportedGames(list);
-	Glk::Hugo::HugoMetaEngine::getSupportedGames(list);
 	Glk::Magnetic::MagneticMetaEngine::getSupportedGames(list);
 	Glk::TADS::TADSMetaEngine::getSupportedGames(list);
 #endif
@@ -176,6 +176,9 @@ PlainGameDescriptor GlkMetaEngine::findGame(const char *gameId) const {
 	gd = Glk::Glulxe::GlulxeMetaEngine::findGame(gameId);
 	if (gd._description) return gd;
 
+	gd = Glk::Hugo::HugoMetaEngine::findGame(gameId);
+	if (gd._description) return gd;
+
 	gd = Glk::Scott::ScottMetaEngine::findGame(gameId);
 	if (gd._description) return gd;
 
@@ -183,9 +186,6 @@ PlainGameDescriptor GlkMetaEngine::findGame(const char *gameId) const {
 	gd = Glk::Alan2::Alan2MetaEngine::findGame(gameId);
 	if (gd._description) return gd;
 
-	gd = Glk::Hugo::HugoMetaEngine::findGame(gameId);
-	if (gd._description) return gd;
-
 	gd = Glk::Magnetic::MagneticMetaEngine::findGame(gameId);
 	if (gd._description) return gd;
 
@@ -202,11 +202,11 @@ DetectedGames GlkMetaEngine::detectGames(const Common::FSList &fslist) const {
 	DetectedGames detectedGames;
 	Glk::Frotz::FrotzMetaEngine::detectGames(fslist, detectedGames);
 	Glk::Glulxe::GlulxeMetaEngine::detectGames(fslist, detectedGames);
+	Glk::Hugo::HugoMetaEngine::detectGames(fslist, detectedGames);
 	Glk::Scott::ScottMetaEngine::detectGames(fslist, detectedGames);
 
 #ifndef RELEASE_BUILD
 	Glk::Alan2::Alan2MetaEngine::detectGames(fslist, detectedGames);
-	Glk::Hugo::HugoMetaEngine::detectGames(fslist, detectedGames);
 	Glk::Magnetic::MagneticMetaEngine::detectGames(fslist, detectedGames);
 	Glk::TADS::TADSMetaEngine::detectGames(fslist, detectedGames);
 #endif
@@ -218,11 +218,11 @@ void GlkMetaEngine::detectClashes() const {
 	Common::StringMap map;
 	Glk::Frotz::FrotzMetaEngine::detectClashes(map);
 	Glk::Glulxe::GlulxeMetaEngine::detectClashes(map);
+	Glk::Hugo::HugoMetaEngine::detectClashes(map);
 	Glk::Scott::ScottMetaEngine::detectClashes(map);
 
 #ifndef RELEASE_BUILD
 	Glk::Alan2::Alan2MetaEngine::detectClashes(map);
-	Glk::Hugo::HugoMetaEngine::detectClashes(map);
 	Glk::Magnetic::MagneticMetaEngine::detectClashes(map);
 	Glk::TADS::TADSMetaEngine::detectClashes(map);
 #endif


Commit: 54d240d81f8858f7ad694c690fcf738b3ec8b89d
    https://github.com/scummvm/scummvm/commit/54d240d81f8858f7ad694c690fcf738b3ec8b89d
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS: Improved detection to detect TADS version

Changed paths:
    engines/glk/tads/detection.cpp
    engines/glk/tads/detection.h


diff --git a/engines/glk/tads/detection.cpp b/engines/glk/tads/detection.cpp
index 4df6d25..51a29d1 100644
--- a/engines/glk/tads/detection.cpp
+++ b/engines/glk/tads/detection.cpp
@@ -59,7 +59,7 @@ GameDescriptor TADSMetaEngine::findGame(const char *gameId) {
 }
 
 bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
-	const char *const EXTENSIONS[] = { ".gam", nullptr };
+	const char *const EXTENSIONS[] = { ".gam", ".t3", nullptr };
 
 	// Loop through the files of the folder
 	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
@@ -68,7 +68,8 @@ bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &ga
 			continue;
 
 		Common::String filename = file->getName();
-		bool hasExt = Blorb::hasBlorbExt(filename), isBlorb = false;
+		bool hasExt = Blorb::hasBlorbExt(filename), isBlorb = true;
+		int tadsVersion = -1;
 		for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext)
 			hasExt = filename.hasSuffixIgnoreCase(*ext);
 		if (!hasExt)
@@ -81,10 +82,21 @@ bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &ga
 		Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
 		size_t filesize = gameFile.size();
 		gameFile.seek(0);
-		isBlorb = Blorb::isBlorb(gameFile, ID_TAD2) || Blorb::isBlorb(gameFile, ID_TAD3);
+		if (Blorb::isBlorb(gameFile, ID_TAD2))
+			tadsVersion = 2;
+		else if (Blorb::isBlorb(gameFile, ID_TAD3))
+			tadsVersion = 3;
+		else
+			isBlorb = false;
+
+		if (!isBlorb)
+			// Figure out the TADS version
+			tadsVersion = getTADSVersion(gameFile);
+
 		gameFile.close();
 
-		if (!isBlorb && Blorb::hasBlorbExt(filename))
+		if (tadsVersion == -1)
+			// Not a TADS game, or Blorb containing TADS game, so can be ignored
 			continue;
 
 		// Check for known games
@@ -95,15 +107,15 @@ bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &ga
 		DetectedGame gd;
 		if (!p->_gameId) {
 			if (gDebugLevel > 0) {
-				// Print an entry suitable for putting into the detection_tables.h, using the
+				// Print an entry suitable for putting into the detection_tables.h
 				Common::String fname = filename;
 				const char *dot = strchr(fname.c_str(), '.');
 				if (dot)
 					fname = Common::String(fname.c_str(), dot);
 
-				debug("ENTRY0(\"%s\", \"%s\", %u),", fname.c_str(), md5.c_str(), (uint)filesize);
+				debug("TADS%d ENTRY0(\"%s\", \"%s\", %u),", tadsVersion, fname.c_str(), md5.c_str(), (uint)filesize);
 			}
-			const GameDescriptor &desc = TADS2_GAME_LIST[0];
+			const GameDescriptor &desc = tadsVersion == 2 ? TADS2_GAME_LIST[0] : TADS3_GAME_LIST[0];
 			gd = DetectedGame(desc._gameId, desc._description, Common::UNK_LANG, Common::kPlatformUnknown);
 		} else {
 			PlainGameDescriptor gameDesc = findGame(p->_gameId);
@@ -130,5 +142,20 @@ void TADSMetaEngine::detectClashes(Common::StringMap &map) {
 	}
 }
 
+int TADSMetaEngine::getTADSVersion(Common::SeekableReadStream &game) {
+	// Read in the start of the file
+	char buffer[16];
+	game.seek(0);
+	game.read(buffer, 16);
+
+	// Check for valid game headers
+	if (memcmp(buffer, "TADS2 bin\n\r\032", 12) == 0)
+		return 2;
+	else if (memcmp(buffer, "T3-image\r\n\032", 11) == 0)
+		return 3;
+	else
+		return -1;
+}
+
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/detection.h b/engines/glk/tads/detection.h
index 0ed1e94..5fadd57 100644
--- a/engines/glk/tads/detection.h
+++ b/engines/glk/tads/detection.h
@@ -56,6 +56,13 @@ public:
 	 * Check for game Id clashes with other sub-engines
 	 */
 	static void detectClashes(Common::StringMap &map);
+
+	/**
+	 * Determines whether the given game is TADS 2 or 3
+	 * @param game		Open stream pointing to game file
+	 * @returns			2 for TADS 2, 3 for TADS 3, or -1 for error
+	 */
+	static int getTADSVersion(Common::SeekableReadStream &game);
 };
 
 } // End of namespace TADS


Commit: 0279143a62c2cdab635894103da03b43eba7cf9c
    https://github.com/scummvm/scummvm/commit/0279143a62c2cdab635894103da03b43eba7cf9c
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS2: Adding headers

Changed paths:
  A engines/glk/tads/os_filetype.h
  A engines/glk/tads/osfrobtads.cpp
  A engines/glk/tads/osfrobtads.h
  A engines/glk/tads/tads2/appctx.h
  A engines/glk/tads/tads2/built_in.h
  A engines/glk/tads/tads2/character_map.cpp
  A engines/glk/tads/tads2/character_map.h
  A engines/glk/tads/tads2/debug.h
  A engines/glk/tads/tads2/error.cpp
  A engines/glk/tads/tads2/error.h
  A engines/glk/tads/tads2/error_handling.cpp
  A engines/glk/tads/tads2/error_handling.h
  A engines/glk/tads/tads2/file_io.cpp
  A engines/glk/tads/tads2/file_io.h
  A engines/glk/tads/tads2/lib.h
  A engines/glk/tads/tads2/line_source.h
  A engines/glk/tads/tads2/memory_cache.cpp
  A engines/glk/tads/tads2/memory_cache.h
  A engines/glk/tads/tads2/memory_cache_heap.cpp
  A engines/glk/tads/tads2/memory_cache_heap.h
  A engines/glk/tads/tads2/memory_cache_loader.cpp
  A engines/glk/tads/tads2/memory_cache_loader.h
  A engines/glk/tads/tads2/memory_cache_swap.cpp
  A engines/glk/tads/tads2/memory_cache_swap.h
  A engines/glk/tads/tads2/object.cpp
  A engines/glk/tads/tads2/object.h
  A engines/glk/tads/tads2/opcode_defs.h
  A engines/glk/tads/tads2/post_compilation.h
  A engines/glk/tads/tads2/property.h
  A engines/glk/tads/tads2/run.cpp
  A engines/glk/tads/tads2/run.h
  A engines/glk/tads/tads2/text_io.h
  A engines/glk/tads/tads2/tokenizer.cpp
  A engines/glk/tads/tads2/tokenizer.h
  R engines/glk/tads/tads2/ler.cpp
  R engines/glk/tads/tads2/ler.h
  R engines/glk/tads/tads2/tads2_cmap.cpp
  R engines/glk/tads/tads2/types.h
    engines/glk/module.mk
    engines/glk/tads/tads2/data.cpp
    engines/glk/tads/tads2/data.h
    engines/glk/tads/tads2/os.cpp
    engines/glk/tads/tads2/os.h
    engines/glk/tads/tads2/regex.cpp
    engines/glk/tads/tads2/regex.h
    engines/glk/tads/tads2/tads2.cpp
    engines/glk/tads/tads2/tads2.h
    engines/glk/tads/tads2/vocabulary.cpp
    engines/glk/tads/tads2/vocabulary.h


diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index d84d542..43c2523 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -92,13 +92,22 @@ MODULE_OBJS := \
 	scott/detection.o \
 	scott/scott.o \
 	tads/detection.o \
+	tads/osfrobtads.o \
 	tads/tads.o \
+	tads/tads2/character_map.o \
 	tads/tads2/data.o \
-	tads/tads2/ler.o \
+	tads/tads2/error.o \
+	tads/tads2/error_handling.o \
+	tads/tads2/file_io.o \
+	tads/tads2/memory_cache.o \
+	tads/tads2/memory_cache_heap.o \
+	tads/tads2/memory_cache_loader.o \
+	tads/tads2/memory_cache_swap.o \
 	tads/tads2/os.o \
 	tads/tads2/regex.o \
+	tads/tads2/run.o \
 	tads/tads2/tads2.o \
-	tads/tads2/tads2_cmap.o \
+	tads/tads2/tokenizer.o \
 	tads/tads2/vocabulary.o \
 	tads/tads3/tads3.o
 
diff --git a/engines/glk/tads/os_filetype.h b/engines/glk/tads/os_filetype.h
new file mode 100644
index 0000000..93b1016
--- /dev/null
+++ b/engines/glk/tads/os_filetype.h
@@ -0,0 +1,64 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* TADS OS interface file type definitions
+ *
+ * Defines certain datatypes used in the TADS operating system interface
+ */
+
+#ifndef GLK_TADS_OS_DATATYPE
+#define GLK_TADS_OS_DATATYPE
+
+namespace Glk {
+namespace TADS {
+
+/**
+ * File types.  These type codes are used when opening or creating a
+ *   file, so that the OS routine can set appropriate file system metadata
+ *   to describe or find the file type.
+ *   
+ *   The type os_filetype_t is defined for documentary purposes; it's
+ *   always just an int.  
+ */
+enum os_filetype_t {
+	OSFTGAME  = 0,                               /* a game data file (.gam) */
+	OSFTSAVE  = 1,                                   /* a saved game (.sav) */
+	OSFTLOG   = 2,                               /* a transcript (log) file */
+	OSFTSWAP  = 3,                                             /* swap file */
+	OSFTDATA  = 4,      /* user data file (used with the TADS fopen() call) */
+	OSFTCMD   = 5,                                   /* QA command/log file */
+	OSFTERRS  = 6,                                    /* error message file */
+	OSFTTEXT  = 7,                     /* text file - used for source files */
+	OSFTBIN   = 8,          /* binary file of unknown type - resources, etc */
+	OSFTCMAP  = 9,                                /* character mapping file */
+	OSFTPREF  = 10,                                     /* preferences file */
+	OSFTUNK   = 11,         /* unknown - as a filter, matches any file type */
+	OSFTT3IMG = 12,                  /* T3 image file (.t3 - formerly .t3x) */
+	OSFTT3OBJ = 13,                                /* T3 object file (.t3o) */
+	OSFTT3SYM = 14,                         /* T3 symbol export file (.t3s) */
+	OSFTT3SAV = 15,                           /* T3 saved state file (.t3v) */
+};
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
new file mode 100644
index 0000000..fbcf1c1
--- /dev/null
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/osfrobtads.h"
+#include "common/file.h"
+
+namespace Glk {
+namespace TADS {
+
+osfildef *osfoprb(const char *fname, os_filetype_t typ) {
+	Common::File f;
+	if (f.open(fname))
+		return f.readStream(f.size());
+	else
+		return nullptr;
+}
+
+int osfrb(osfildef *fp, void *buf, size_t count) {
+	return fp->read(buf, count);
+}
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
new file mode 100644
index 0000000..2dce84c
--- /dev/null
+++ b/engines/glk/tads/osfrobtads.h
@@ -0,0 +1,267 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* OS-layer functions and macros.
+ *
+ * This file does not introduce any curses (or other screen-API)
+ * dependencies; it can be used for both the interpreter as well as the
+ * compiler.
+ */
+
+#ifndef GLK_TADS_OSFROBTADS
+#define GLK_TADS_OSFROBTADS
+
+#include "common/fs.h"
+#include "common/stream.h"
+#include "glk/glk_api.h"
+#include "glk/tads/os_filetype.h"
+
+namespace Glk {
+namespace TADS {
+
+
+/* Defined for Gargoyle. */
+#define HAVE_STDINT_
+
+#if 0
+#include "common.h"
+#endif
+
+/* Used by the base code to inhibit "unused parameter" compiler warnings. */
+#ifndef VARUSED
+#define VARUSED(var) (void)var
+#endif
+
+/* We assume that the C-compiler is mostly ANSI compatible. */
+#define OSANSI
+
+/* Special function qualifier needed for certain types of callback
+ * functions.  This is for old 16-bit systems; we don't need it and
+ * define it to nothing. */
+#define OS_LOADDS
+
+/* Unices don't suffer the near/far pointers brain damage (thank God) so
+ * we make this a do-nothing macro. */
+#define osfar_t
+
+/* This is used to explicitly discard computed values (some compilers
+ * would otherwise give a warning like "computed value not used" in some
+ * cases).  Casting to void should work on every ANSI-Compiler. */
+#define DISCARD (void)
+
+/* Copies a struct into another.  ANSI C allows the assignment operator
+ * to be used with structs. */
+#define OSCPYSTRUCT(x,y) ((x)=(y))
+
+/* Link error messages into the application. */
+#define ERR_LINK_MESSAGES
+
+/* Program Exit Codes. */
+#define OSEXSUCC 0 /* Successful completion. */
+#define OSEXFAIL 1 /* Failure. */
+
+/* Here we configure the osgen layer; refer to tads2/osgen3.c for more
+ * information about the meaning of these macros. */
+#define USE_DOSEXT
+#define USE_NULLSTYPE
+
+/* Theoretical maximum osmalloc() size.
+ * Unix systems have at least a 32-bit memory space.  Even on 64-bit
+ * systems, 2^32 is a good value, so we don't bother trying to find out
+ * an exact value. */
+#define OSMALMAX 0xffffffffL
+
+#define OSFNMAX 255
+
+/* File handle structure for osfxxx functions. */
+typedef Common::SeekableReadStream osfildef;
+
+/* Directory handle for searches via os_open_dir() et al. */
+typedef Common::FSNode *osdirhdl_t;
+
+/* file type/mode bits */
+#define OSFMODE_FILE    S_IFREG
+#define OSFMODE_DIR     S_IFDIR
+#define OSFMODE_CHAR    S_IFCHR
+#define OSFMODE_BLK     S_IFBLK
+#define OSFMODE_PIPE    S_IFIFO
+#ifdef S_IFLNK
+#define OSFMODE_LINK    S_IFLNK
+#else
+#define OSFMODE_LINK    0
+#endif
+#ifdef S_IFSOCK
+#define OSFMODE_SOCKET  S_IFSOCK
+#else
+#define OSFMODE_SOCKET  0
+#endif
+
+/* File attribute bits. */
+#define OSFATTR_HIDDEN  0x0001
+#define OSFATTR_SYSTEM  0x0002
+#define OSFATTR_READ    0x0004
+#define OSFATTR_WRITE   0x0008
+
+/* Get a file's stat() type. */
+int osfmode( const char* fname, int follow_links, unsigned long* mode,
+             unsigned long* attr );
+
+#if 0
+/* The maximum width of a line of text.
+ *
+ * We ignore this, but the base code needs it defined.  If the
+ * interpreter is run inside a console or terminal with more columns
+ * than the value defined here, weird things will happen, so we go safe
+ * and use a large value. */
+#define OS_MAXWIDTH 255
+#endif
+
+/* Disable the Tads swap file; computers have plenty of RAM these days.
+ */
+#define OS_DEFAULT_SWAP_ENABLED 0
+
+/* TADS 2 macro/function configuration.  Modern configurations always
+ * use the no-macro versions, so these definitions should always be set
+ * as shown below. */
+#define OS_MCM_NO_MACRO
+#define ERR_NO_MACRO
+
+/* These values are used for the "mode" parameter of osfseek() to
+ * indicate where to seek in the file. */
+#define OSFSK_SET SEEK_SET /* Set position relative to the start of the file. */
+#define OSFSK_CUR SEEK_CUR /* Set position relative to the current file position. */
+#define OSFSK_END SEEK_END /* Set position relative to the end of the file. */
+
+
+/* ============= Functions follow ================ */
+
+/* Allocate a block of memory of the given size in bytes. */
+#define osmalloc malloc
+
+/* Free memory previously allocated with osmalloc(). */
+#define osfree free
+
+/* Reallocate memory previously allocated with osmalloc() or osrealloc(),
+ * changing the block's size to the given number of bytes. */
+#define osrealloc realloc
+
+/* Open text file for reading. */
+#define osfoprt(fname,typ) (fopen((fname),"r"))
+
+/* Open text file for writing. */
+#define osfopwt(fname,typ) (fopen((fname),"w"))
+
+/* Open text file for reading and writing, keeping the file's existing
+ * contents if the file already exists or creating a new file if no
+ * such file exists. */
+osfildef *osfoprwt(const char *fname, os_filetype_t typ);
+
+/* Open text file for reading/writing.  If the file already exists,
+ * truncate the existing contents.  Create a new file if it doesn't
+ * already exist. */
+#define osfoprwtt(fname,typ) (fopen((fname),"w+"))
+
+/* Open binary file for writing. */
+#define osfopwb(fname,typ) (fopen((fname),"wb"))
+
+/* Open source file for reading - use the appropriate text or binary
+ * mode. */
+#define osfoprs osfoprt
+
+/* Open binary file for reading. */
+osfildef *osfoprb(const char *fname, os_filetype_t typ);
+
+/* Open binary file for reading/writing.  If the file already exists,
+ * keep the existing contents.  Create a new file if it doesn't already
+ * exist. */
+osfildef*
+osfoprwb( const char* fname, os_filetype_t typ );
+
+/* Open binary file for reading/writing.  If the file already exists,
+ * truncate the existing contents.  Create a new file if it doesn't
+ * already exist. */
+#define osfoprwtb(fname,typ) (fopen((fname),"w+b"))
+
+/* Get a line of text from a text file. */
+#define osfgets fgets
+
+/* Write a line of text to a text file. */
+#define osfputs fputs
+
+/* Write bytes to file. */
+#define osfwb(fp,buf,bufl) (fwrite((buf),(bufl),1,(fp))!=1)
+
+/* Flush buffered writes to a file. */
+#define osfflush fflush
+
+/* Read bytes from file. */
+int osfrb(osfildef *fp, void *buf, size_t count);
+
+/* Read bytes from file and return the number of bytes read. */
+#define osfrbc(fp,buf,bufl) (fread((buf),1,(bufl),(fp)))
+
+/* Get the current seek location in the file. */
+#define osfpos ftell
+
+/* Seek to a location in the file. */
+#define osfseek fseek
+
+/* Close a file. */
+#define osfcls delete
+
+/* Delete a file. */
+#define osfdel remove
+
+/* Access a file - determine if the file exists.
+ *
+ * We map this to the access() function.  It should be available in
+ * virtually every system out there, as it appears in many standards
+ * (SVID, AT&T, POSIX, X/OPEN, BSD 4.3, DOS, MS Windows, maybe more). */
+#define osfacc(fname) (access((fname), F_OK))
+
+/* Rename a file. */
+#define os_rename_file(from, to) (rename(from, to) == 0)
+
+/* Get a file's stat() type. */
+struct os_file_stat_t;
+int os_file_stat( const char* fname, int follow_links,
+                  struct os_file_stat_t* s );
+
+/* Get a character from a file. */
+#define osfgetc fgetc
+
+/* Set busy cursor.
+ *
+ * We don't have a mouse cursor so there's no need to implement this. */
+#define os_csr_busy(a)
+
+/* Update progress display.
+ *
+ * We don't provide any kind of "compilation progress display", so we
+ * just define this as an empty macro.
+ */
+#define os_progress(fname,linenum)
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/appctx.h b/engines/glk/tads/tads2/appctx.h
new file mode 100644
index 0000000..3b3f499
--- /dev/null
+++ b/engines/glk/tads/tads2/appctx.h
@@ -0,0 +1,248 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_APPCTX
+#define GLK_TADS_TADS2_APPCTX
+
+#include "common/scummsys.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/**
+ *   Application container context.  The TADS run-time is a subsystem that
+ *   can be invoked from different types of applications; in fact, even
+ *   when only the standard stand-alone run-time is considered, multiple
+ *   application containers must be supported because of differences
+ *   between operating systems.  The application container context is an
+ *   optional mechanism that the main application can use to provide
+ *   structured interaction between itself and the TADS run-time subsystem.
+ *   
+ *   The function pointers contained herein are intended to allow the
+ *   run-time subsystem to call the host system to notify it of certain
+ *   events, or obtain optional services from the host system.  Any of
+ *   these function pointers can be null, in which case the run-time
+ *   subsystem will skip calling them.
+ *   
+ *   Note that each function has an associated callback context.  This
+ *   allows the host system to recover any necessary context information
+ *   when the callback is invoked.  
+ */
+struct appctxdef {
+    /**
+     *   Get the .GAM file name.  The run-time will call this only if it
+     *   can't find a game file to load through some other means first.
+     *   The run-time determines the game file first by looking at the
+     *   command line, then by checking to see if a .GAM file is attached
+     *   to the executable.  If none of these checks yields a game, the
+     *   run-time will call this routine to see if the host system wants
+     *   to provide a game.  This routine can be implemented on a GUI
+     *   system, for example, to display a dialog prompting the user to
+     *   select a game file to open.  A trivial implementation of this
+     *   routine (that merely returns false) is okay.
+     *   
+     *   This routine should return true (any non-zero value) if it
+     *   provides the name of a file to open, false (zero) if not.  
+     */
+    int (*get_game_name)(void *appctxdat, char *buf, size_t buflen);
+    void *get_game_name_ctx;
+
+    /**
+     *   Set the .GAM file name.  When the run-time determines the name of
+     *   the file it will use to read the game, it calls this routine.
+     *   The host system should note the game filename if it will need to
+     *   access the game file itself (for example, to load resources).  
+     */
+    void (*set_game_name)(void *appctxdat, const char *fname);
+    void *set_game_name_ctx;
+
+    /**
+     *   Set the root path for individual resources.  By default, we use the
+     *   directory containing the game file, but this can be used to override
+     *   that.  
+     */
+    void (*set_res_dir)(void *appctxdat, const char *fname);
+    void *set_res_dir_ctx;
+
+    /**
+     *   Set the resource map address in the game file.  If the .GAM
+     *   reader encounters a resource map in the file, it calls this
+     *   routine with the seek offset of the first resource.  Each
+     *   resource's address is given as an offset from this point.
+     *   
+     *   fileno is the file number assigned by the host system in
+     *   add_resfile.  File number zero is always the .GAM file.  
+     */
+    void (*set_resmap_seek)(void *appctxdat, unsigned long seekpos, int fileno);
+    void *set_resmap_seek_ctx;
+
+    /**
+     *   Add a resource entry.  The 'ofs' entry is the byte offset of the
+     *   start of the resource, relative to the seek position previously
+     *   set with set_resmap_seek.  'siz' is the size of the resource in
+     *   bytes; the resource is stored as contiguous bytes starting at the
+     *   given offset for the given size.  Note that resources may be
+     *   added before the resource map seek position is set, so the host
+     *   system must simply store the resource information for later use.
+     *   The 'fileno' is zero for the .GAM file, or the number assigned by
+     *   the host system in add_resfile for other resource files.  
+     */
+    void (*add_resource)(void *appctxdat, unsigned long ofs,
+                         unsigned long siz, const char *nm, size_t nmlen,
+                         int fileno);
+    void *add_resource_ctx;
+
+    /**
+     *   Add a resource link entry.  'fname' and 'fnamelen' give the name of
+     *   a local file containing the resource data; 'resname' and
+     *   'resnamelen' give the name of the resource as it appears within the
+     *   compiled game file.  This creates a link from a .GAM resource name
+     *   to a local filename, where the actual binary resource data reside,
+     *   so that we can retrieve a resource by .GAM resource name without
+     *   actually copying the data into the .GAM file.  This is used mostly
+     *   for debugging purposes: it allows the compiler to skip the step of
+     *   copying the resource data into the .GAM file, but still allows the
+     *   game to load resources by .GAM resource name, to create a testing
+     *   environment that's consistent with the full build version (where the
+     *   resources would actually be copied).  
+     */
+    void (*add_resource_link)(void *appctxdat,
+                              const char *fname, size_t fnamelen,
+                              const char *resname, size_t resnamelen);
+    void *add_resource_link_ctx;
+
+    /**
+     *   Add a resource path.  'path' is a string giving a directory prefix
+     *   in local system notation.
+     *   
+     *   This adds a directory to the list of directories that we'll search
+     *   when we're looking for an individual resource as an external file
+     *   (such as a ".jpg" image file or ".ogg" sound file).  This can be
+     *   called zero or more times; each call adds another directory to
+     *   search after any previous directories.  We'll always search the
+     *   default directory first (this is the directory containing the game
+     *   file); then we'll search directories added with this call in the
+     *   order in which the directories were added.  
+     */
+    void (*add_res_path)(void *appctxdat, const char *path, size_t len);
+    void *add_res_path_ctx;
+
+    /**
+     *   Find a resource entry.  If the resource can be found, this must
+     *   return an osfildef* handle to the resource, with its seek position
+     *   set to the first byte of the resource data, and set *res_size to
+     *   the size in bytes of the resource data in the file.  If the
+     *   resource cannot be found, returns null.  
+     */
+    osfildef *(*find_resource)(void *appctxdat,
+                               const char *resname, size_t resnamelen,
+                               unsigned long *res_size);
+    void *find_resource_ctx;
+
+    /**
+     *   Add a resource file.  The return value is a non-zero file number
+     *   assigned by the host system; we'll use this number in subsequent
+     *   calls to add_resource to add the resources from this file.
+     *   
+     *   After calling this routine to add the file, we'll parse the file
+     *   and add any resources using add_resource.  
+     */
+    int (*add_resfile)(void *appctxdat, const char *fname);
+    void *add_resfile_ctx;
+
+    /**
+     *   Determine if a resource exists.  Returns true if the resource can
+     *   be loaded, false if not.  The resource name is in the standard
+     *   URL-style format.  
+     */
+    int (*resfile_exists)(void *appctxdat, const char *res_name,
+                          size_t res_name_len);
+    void *resfile_exists_ctx;
+
+    /**
+     *   Resource file path.  If we should look for resource files in a
+     *   different location than the .GAM file, the host system can set
+     *   this to a path that we should use to look for resource files.  If
+     *   it's null, we'll look in the directory that contains the .GAM
+     *   file.  Note that if the path is provided, it must be set up with
+     *   a trailing path separator character, so that we can directly
+     *   append a name to this path to form a valid fully-qualified
+     *   filename.  
+     */
+    const char *ext_res_path;
+
+    /**
+     *   File safety level get/set.  During initialization, we'll call the
+     *   host system to tell it the file safety level selected by the user on
+     *   the command line; if the host system is saving preference
+     *   information, it should temporarily override its saved preferences
+     *   and use the command line setting (and it may, if appropriate, want
+     *   to save the command line setting as the saved preference setting,
+     *   depending on how it handles preferences).  During execution, any
+     *   time the game tries to open a file (using the fopen built-in
+     *   function), we'll call the host system to ask it for the current
+     *   setting, and use this new setting rather than the original command
+     *   line setting.
+     *   
+     *   Refer to bif.c for information on the meanings of the file safety
+     *   levels.  
+     */
+    void (*set_io_safety_level)(void *ctx, int read, int write);
+    void (*get_io_safety_level)(void *ctx, int *read, int *write);
+    void  *io_safety_level_ctx;
+
+    /**
+     *   Network safety level get/set.  This is analogous to the file safety
+     *   level scheme, but controls access to network resources.  There are
+     *   two components to the network safety setting: client and server.
+     *   The client component controls the game's ability to open network
+     *   connections to access information on remote machines, such as
+     *   opening http connections to access web sites.  The server component
+     *   controls the game's ability to create servers of its own and accept
+     *   incoming connections.  Each component can be set to one of the
+     *   following:
+     *   
+     *.     0 = no restrictions (least "safety"): all network access granted
+     *.     1 = 'localhost' access only
+     *.     2 = no network access
+     *   
+     *   This only applies to the TADS 3 VM.  TADS 2 doesn't support any
+     *   network features, so this doesn't apply.  
+     */
+    void (*set_net_safety_level)(void *ctx, int client_level, int srv_level);
+    void (*get_net_safety_level)(void *ctx, int *client_level, int *srv_level);
+    void *net_safety_level_ctx;
+
+    /**
+     *   Name of run-time application for usage messages.  If this is
+     *   null, the default run-time application name will be used for
+     *   usage messages. 
+     */
+    const char *usage_app_name;
+};
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/built_in.h b/engines/glk/tads/tads2/built_in.h
new file mode 100644
index 0000000..f5807e6
--- /dev/null
+++ b/engines/glk/tads/tads2/built_in.h
@@ -0,0 +1,216 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* Built-in functions interface
+ *
+ * Interface to run-time intrinsic function implementation
+ */
+
+#ifndef GLK_TADS_TADS2_BUILT_IN
+#define GLK_TADS_TADS2_BUILT_IN
+
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/text_io.h"
+#include "glk/tads/tads2/regex.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* forward definitions */
+struct vocidef;
+struct voccxdef;
+
+/* maximum number of file handles available */
+#define BIFFILMAX  10
+
+
+/* file contexts for the built-in file handling functions */
+struct biffildef {
+    osfildef *fp;                          /* underyling system file handle */
+    uint      flags;                                               /* flags */
+#define BIFFIL_F_BINARY    0x01                           /* file is binary */
+};
+
+/* built-in execution context */
+struct bifcxdef {
+    errcxdef *bifcxerr;                           /* error-handling context */
+    runcxdef *bifcxrun;                           /* code execution context */
+    tiocxdef *bifcxtio;                                 /* text I/O context */
+    long      bifcxrnd;                               /* random number seed */
+    int       bifcxseed1;                   /* first seed for new generator */
+    int       bifcxseed2;                  /* second seed for new generator */
+    int       bifcxseed3;                   /* third seed for new generator */
+    int       bifcxrndset;                   /* randomize() has been called */
+    biffildef bifcxfile[BIFFILMAX];          /* file handles for fopen, etc */
+    int       bifcxsafetyr;                 /* file I/O safety level - read */
+    int       bifcxsafetyw;                /* file I/O safety level - write */
+    char     *bifcxsavext;        /* saved game extension (null by default) */
+    appctxdef *bifcxappctx;                     /* host application context */
+    re_context bifcxregex;          /* regular expression searching context */
+
+	bifcxdef() : bifcxerr(nullptr), bifcxrun(nullptr), bifcxtio(nullptr),
+		bifcxrnd(0), bifcxseed1(0), bifcxseed2(0), bifcxseed3(0), bifcxrndset(0),
+		bifcxsafetyr(0), bifcxsafetyw(0), bifcxsavext(nullptr), bifcxappctx(nullptr) {
+	}
+};
+
+/*
+ *   argument list checking routines - can be disabled for faster
+ *   run-time 
+ */
+
+/* check for proper number of arguments */
+/* void bifcntargs(bifcxdef *ctx, int argcnt) */
+
+/* check that next argument has proper type */
+/* void bifchkarg(bifcxdef *ctx, dattyp typ); */
+
+#ifdef RUNFAST
+# define bifcntargs(ctx, parmcnt, argcnt)
+# define bifchkarg(ctx, typ)
+#else /* RUNFAST */
+# define bifcntargs(ctx, parmcnt, argcnt) \
+  (parmcnt == argcnt ? DISCARD 0 : \
+   (runsig(ctx->bifcxrun, ERR_BIFARGC), DISCARD 0))
+# define bifchkarg(ctx, typ) \
+  (runtostyp(ctx->bifcxrun) == typ ? DISCARD 0 : \
+   (runsig(ctx->bifcxrun, ERR_INVTBIF), DISCARD 0))
+#endif /* RUNFAST */
+
+/* determine if one object is a subclass of another */
+int bifinh(struct voccxdef *voc, struct vocidef *v, objnum cls);
+
+
+/* enumerate the built-in functions */
+void bifyon(bifcxdef *ctx, int argc);                   /* yorn - yes or no */
+void bifsfs(bifcxdef *ctx, int argc);                            /* setfuse */
+void bifrfs(bifcxdef *ctx, int argc);                            /* remfuse */
+void bifsdm(bifcxdef *ctx, int argc);                          /* setdaemon */
+void bifrdm(bifcxdef *ctx, int argc);                          /* remdaemon */
+void bifinc(bifcxdef *ctx, int argc);                            /* incturn */
+void bifqui(bifcxdef *ctx, int argc);                               /* quit */
+void bifsav(bifcxdef *ctx, int argc);                               /* save */
+void bifrso(bifcxdef *ctx, int argc);                            /* restore */
+void biflog(bifcxdef *ctx, int argc);                            /* logging */
+void bifres(bifcxdef *ctx, int argc);                            /* restart */
+void bifinp(bifcxdef *ctx, int argc);           /* input - get line from kb */
+void bifnfy(bifcxdef *ctx, int argc);                             /* notify */
+void bifunn(bifcxdef *ctx, int argc);                           /* unnotify */
+void biftrc(bifcxdef *ctx, int argc);                       /* trace on/off */
+void bifsay(bifcxdef *ctx, int argc);                                /* say */
+void bifcar(bifcxdef *ctx, int argc);                                /* car */
+void bifcdr(bifcxdef *ctx, int argc);                                /* cdr */
+void bifcap(bifcxdef *ctx, int argc);                               /* caps */
+void biflen(bifcxdef *ctx, int argc);                             /* length */
+void biffnd(bifcxdef *ctx, int argc);                               /* find */
+void bifsit(bifcxdef *ctx, int argc);           /* setit - set current 'it' */
+void bifsrn(bifcxdef *ctx, int argc);               /* randomize: seed rand */
+void bifrnd(bifcxdef *ctx, int argc);         /* rand - get a random number */
+void bifask(bifcxdef *ctx, int argc);                            /* askfile */
+void bifssc(bifcxdef *ctx, int argc);                           /* setscore */
+void bifsub(bifcxdef *ctx, int argc);                             /* substr */
+void bifcvs(bifcxdef *ctx, int argc);          /* cvtstr: convert to string */
+void bifcvn(bifcxdef *ctx, int argc);          /* cvtnum: convert to number */
+void bifupr(bifcxdef *ctx, int argc);                              /* upper */
+void biflwr(bifcxdef *ctx, int argc);                              /* lower */
+void biffob(bifcxdef *ctx, int argc);                           /* firstobj */
+void bifnob(bifcxdef *ctx, int argc);                            /* nextobj */
+void bifsvn(bifcxdef *ctx, int argc);                         /* setversion */
+void bifarg(bifcxdef *ctx, int argc);                             /* getarg */
+void biftyp(bifcxdef *ctx, int argc);                           /* datatype */
+void bifisc(bifcxdef *ctx, int argc);                            /* isclass */
+void bifund(bifcxdef *ctx, int argc);                               /* undo */
+void bifdef(bifcxdef *ctx, int argc);                            /* defined */
+void bifpty(bifcxdef *ctx, int argc);                           /* proptype */
+void bifoph(bifcxdef *ctx, int argc);                            /* outhide */
+void bifgfu(bifcxdef *ctx, int argc);                            /* getfuse */
+void bifruf(bifcxdef *ctx, int argc);                           /* runfuses */
+void bifrud(bifcxdef *ctx, int argc);                         /* rundaemons */
+void biftim(bifcxdef *ctx, int argc);                            /* gettime */
+void bifsct(bifcxdef *ctx, int argc);                          /* intersect */
+void bifink(bifcxdef *ctx, int argc);                           /* inputkey */
+void bifwrd(bifcxdef *ctx, int argc);                           /* objwords */
+void bifadw(bifcxdef *ctx, int argc);                            /* addword */
+void bifdlw(bifcxdef *ctx, int argc);                            /* delword */
+void bifgtw(bifcxdef *ctx, int argc);                           /* getwords */
+void bifnoc(bifcxdef *ctx, int argc);                             /* nocaps */
+void bifskt(bifcxdef *ctx, int argc);                           /* skipturn */
+void bifcls(bifcxdef *ctx, int argc);                        /* clearscreen */
+void bif1sc(bifcxdef *ctx, int argc);                            /* firstsc */
+void bifvin(bifcxdef *ctx, int argc);                           /* verbinfo */
+void bifcapture(bifcxdef *ctx, int argc);                     /* outcapture */
+
+void biffopen(bifcxdef *ctx, int argc);                            /* fopen */
+void biffclose(bifcxdef *ctx, int argc);                          /* fclose */
+void biffwrite(bifcxdef *ctx, int argc);                          /* fwrite */
+void biffread(bifcxdef *ctx, int argc);                            /* fread */
+void biffseek(bifcxdef *ctx, int argc);                            /* fseek */
+void biffseekeof(bifcxdef *ctx, int argc);                      /* fseekeof */
+void bifftell(bifcxdef *ctx, int argc);                            /* ftell */
+
+void bifsysinfo(bifcxdef *ctx, int argc);                     /* systemInfo */
+void bifmore(bifcxdef *ctx, int argc);                        /* morePrompt */
+void bifsetme(bifcxdef *ctx, int argc);                      /* parserSetMe */
+void bifgetme(bifcxdef *ctx, int argc);                      /* parserGetMe */
+
+void bifresearch(bifcxdef *ctx, int argc);                      /* reSearch */
+void bifregroup(bifcxdef *ctx, int argc);                     /* reGetGroup */
+
+void bifinpevt(bifcxdef *ctx, int argc);                      /* inputevent */
+void bifdelay(bifcxdef *ctx, int argc);                        /* timeDelay */
+
+void bifsetoutfilter(bifcxdef *ctx, int argc);           /* setOutputFilter */
+void bifexec(bifcxdef *ctx, int argc);                       /* execCommand */
+void bifgetobj(bifcxdef *ctx, int argc);                    /* parserGetObj */
+void bifparsenl(bifcxdef *ctx, int argc);            /* parserParseNounList */
+void bifprstok(bifcxdef *ctx, int argc);                  /* parserTokenize */
+void bifprstoktyp(bifcxdef *ctx, int argc);            /* parserGetTokTypes */
+void bifprsdict(bifcxdef *ctx, int argc);               /* parserDictLookup */
+void bifprsrslv(bifcxdef *ctx, int argc);           /* parserResolveObjects */
+void bifprsrplcmd(bifcxdef *ctx, int argc);         /* parserReplaceCommand */
+void bifexitobj(bifcxdef *ctx, int argc);                        /* exitobj */
+void bifinpdlg(bifcxdef *ctx, int argc);                     /* inputdialog */
+void bifresexists(bifcxdef *ctx, int argc);               /* resourceExists */
+
+/*
+ *   TADS/graphic functions - these are present in the text system, but
+ *   don't do anything.
+ */
+void bifgrp(bifcxdef *ctx, int argc);            /* g_readpic: read picture */
+void bifgsp(bifcxdef *ctx, int argc);            /* g_showpic: show picture */
+void bifgsh(bifcxdef *ctx, int argc);             /* g_sethot: set hot list */
+void bifgin(bifcxdef *ctx, int argc);                        /* g_inventory */
+void bifgco(bifcxdef *ctx, int argc);                          /* g_compass */
+void bifgov(bifcxdef *ctx, int argc);                          /* g_overlay */
+void bifgmd(bifcxdef *ctx, int argc);                             /* g_mode */
+void bifgmu(bifcxdef *ctx, int argc);                            /* g_music */
+void bifgpa(bifcxdef *ctx, int argc);                            /* g_pause */
+void bifgef(bifcxdef *ctx, int argc);                           /* g_effect */
+void bifgsn(bifcxdef *ctx, int argc);                            /* g_sound */
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/character_map.cpp b/engines/glk/tads/tads2/character_map.cpp
new file mode 100644
index 0000000..91daa86
--- /dev/null
+++ b/engines/glk/tads/tads2/character_map.cpp
@@ -0,0 +1,339 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/text_io.h"
+#include "glk/tads/osfrobtads.h"
+#include "common/algorithm.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Global variables for character mapping tables 
+ */
+
+unsigned char G_cmap_input[256];
+unsigned char G_cmap_output[256];
+char G_cmap_id[5];
+char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1];
+
+
+/* 
+ *   static variables 
+ */
+
+/* 
+ *   flag: true -> a character set has been explicitly loaded, so we
+ *   should ignore any game character set setting 
+ */
+static int S_cmap_loaded;
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Initialize the default character mappings 
+ */
+void cmap_init_default(void)
+{
+    size_t i;
+
+    /* initialize the input table */
+    for (i = 0 ; i < sizeof(G_cmap_input)/sizeof(G_cmap_input[0]) ; ++i)
+        G_cmap_input[i] = (unsigned char)i;
+
+    /* initialize the output table */
+    for (i = 0 ; i < sizeof(G_cmap_output)/sizeof(G_cmap_output[0]) ; ++i)
+        G_cmap_output[i] = (unsigned char)i;
+
+    /* we have a null ID */
+    memset(G_cmap_id, 0, sizeof(G_cmap_id));
+
+    /* indicate that it's the default */
+    strcpy(G_cmap_ldesc, "(native/no mapping)");
+
+    /* note that we have no character set loaded */
+    S_cmap_loaded = FALSE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Internal routine to load a character map from a file 
+ */
+static int cmap_load_internal(char *filename)
+{
+    osfildef *fp;
+    static char sig1[] = CMAP_SIG_S100;
+    char buf[256];
+    uchar lenbuf[2];
+    size_t len;
+    int sysblk;
+
+    /* if there's no mapping file, use the default mapping */
+    if (filename == 0)
+    {
+        /* initialize with the default mapping */
+        cmap_init_default();
+
+        /* return success */
+        return 0;
+    }
+    
+    /* open the file */
+    fp = osfoprb(filename, OSFTCMAP);
+    if (fp == 0)
+        return 1;
+
+    /* check the signature */
+    if (osfrb(fp, buf, sizeof(sig1))
+        || memcmp(buf, sig1, sizeof(sig1)) != 0)
+    {
+        osfcls(fp);
+        return 2;
+    }
+
+    /* load the ID */
+    G_cmap_id[4] = '\0';
+    if (osfrb(fp, G_cmap_id, 4))
+    {
+        osfcls(fp);
+        return 3;
+    }
+
+    /* load the long description */
+    if (osfrb(fp, lenbuf, 2)
+        || (len = osrp2(lenbuf)) > sizeof(G_cmap_ldesc)
+        || osfrb(fp, G_cmap_ldesc, len))
+    {
+        osfcls(fp);
+        return 4;
+    }
+
+    /* load the two tables - input, then output */
+    if (osfrb(fp, G_cmap_input, sizeof(G_cmap_input))
+        || osfrb(fp, G_cmap_output, sizeof(G_cmap_output)))
+    {
+        osfcls(fp);
+        return 5;
+    }
+
+    /* read the next section header */
+    if (osfrb(fp, buf, 4))
+    {
+        osfcls(fp);
+        return 6;
+    }
+
+    /* if it's "SYSI", read the system information string */
+    if (!memcmp(buf, "SYSI", 4))
+    {
+        /* read the length prefix, then the string */
+        if (osfrb(fp, lenbuf, 2)
+            || (len = osrp2(lenbuf)) > sizeof(buf)
+            || osfrb(fp, buf, len))
+        {
+            osfcls(fp);
+            return 7;
+        }
+
+        /* we have a system information block */
+        sysblk = TRUE;
+    }
+    else
+    {
+        /* there's no system information block */
+        sysblk = FALSE;
+    }
+
+    /* 
+     *   call the OS code, so that it can do any system-dependent
+     *   initialization for the new character mapping 
+     */
+    os_advise_load_charmap(G_cmap_id, G_cmap_ldesc, sysblk ? buf : "");
+
+    /* read the next section header */
+    if (sysblk && osfrb(fp, buf, 4))
+    {
+        osfcls(fp);
+        return 8;
+    }
+
+    /* see if we have an entity list */
+    if (!memcmp(buf, "ENTY", 4))
+    {
+        /* read the entities */
+        for (;;)
+        {
+            size_t blen;
+            unsigned int cval;
+            char expansion[CMAP_MAX_ENTITY_EXPANSION];
+            
+            /* read the next item's length and character value */
+            if (osfrb(fp, buf, 4))
+            {
+                osfcls(fp);
+                return 9; 
+            }
+
+            /* decode the values */
+            blen = osrp2(buf);
+            cval = osrp2(buf+2);
+
+            /* if we've reached the zero marker, we're done */
+            if (blen == 0 && cval == 0)
+                break;
+
+            /* read the string */
+            if (blen > CMAP_MAX_ENTITY_EXPANSION
+                || osfrb(fp, expansion, blen))
+            {
+                osfcls(fp);
+                return 10;
+            }
+
+            /* tell the output code about the expansion */
+            tio_set_html_expansion(cval, expansion, blen);
+        }
+    }
+
+    /* 
+     *   ignore anything else we find - if the file format is updated to
+     *   include extra information in the future, and this old code tries
+     *   to load an updated file, we'll just ignore the new information,
+     *   which should always be placed after the "SYSI" block (if present)
+     *   to ensure compatibility with past versions (such as this code)
+     */
+
+    /* no problems - close the file and return success */
+    osfcls(fp);
+    return 0;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Explicitly load a character set from a file.  This character set
+ *   mapping will override any implicit character set mapping that we read
+ *   from a game file.  This should be called when the player explicitly
+ *   loads a character set (via a command line option or similar action).  
+ */
+int cmap_load(char *filename)
+{
+    int err;
+    
+    /* try loading the file */
+    if ((err = cmap_load_internal(filename)) != 0)
+        return err;
+
+    /* 
+     *   note that we've explicitly loaded a character set, if they named
+     *   a character set (if not, this simply establishes the default
+     *   setting, so we haven't explicitly loaded anything) 
+     */
+    if (filename != 0)
+        S_cmap_loaded = TRUE;
+
+    /* success */
+    return 0;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Explicitly override any game character set and use no character set
+ *   instead. 
+ */
+void cmap_override(void)
+{
+    /* apply the default mapping */
+    cmap_init_default();
+
+    /* 
+     *   pretend we have a character map loaded, so that we don't try to
+     *   load another one if the game specifies a character set
+     */
+    S_cmap_loaded = TRUE;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Set the game's internal character set.  This is called when a game is
+ *   loaded, and the game specifies a character set. 
+ */
+void cmap_set_game_charset(errcxdef *ec,
+                           char *internal_id, char *internal_ldesc,
+                           char *argv0)
+{
+    char filename[OSFNMAX];
+    
+    /* 
+     *   If a character set is already explicitly loaded, ignore the
+     *   game's character set - the player asked us to use a particular
+     *   mapping, so ignore what the game wants.  (This will probably
+     *   result in incorrect display of non-ASCII character values, but
+     *   the player is most likely to use this to avoid errors when an
+     *   appropriate mapping file for the game is not available.  In this
+     *   case, the player informs us by setting the option that he or she
+     *   knows and accepts that the game will not look exactly right.)  
+     */
+    if (S_cmap_loaded)
+        return;
+
+    /* 
+     *   ask the operating system to name the mapping file -- this routine
+     *   will determine, if possible, the current native character set,
+     *   and apply a system-specific naming convention to tell us what
+     *   mapping file we should open 
+     */
+    os_gen_charmap_filename(filename, internal_id, argv0);
+
+    /* try loading the mapping file */
+    if (cmap_load_internal(filename))
+        errsig2(ec, ERR_CHRNOFILE,
+                ERRTSTR, errstr(ec, filename, strlen(filename)),
+                ERRTSTR, errstr(ec, internal_ldesc, strlen(internal_ldesc)));
+
+    /*
+     *   We were successful - the game's internal character set is now
+     *   mapped to the current native character set.  Even though we
+     *   loaded an ldesc from the mapping file, forget that and store the
+     *   internal ldesc that the game specified.  The reason we do this is
+     *   that it's possible that the player will dynamically switch native
+     *   character sets in the future, at which point we'll need to
+     *   re-load the mapping table, which could raise an error if a
+     *   mapping file for the new character set isn't available.  So, we
+     *   may need to provide the same explanation later that we needed to
+     *   provide here.  Save the game's character set ldesc for that
+     *   eventuality, since it describes exactly what the *game* wanted.  
+     */
+    strcpy(G_cmap_ldesc, internal_ldesc);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/character_map.h b/engines/glk/tads/tads2/character_map.h
new file mode 100644
index 0000000..c61406f
--- /dev/null
+++ b/engines/glk/tads/tads2/character_map.h
@@ -0,0 +1,131 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_CHARACTER_MAP
+#define GLK_TADS_CHARACTER_MAP
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+struct errcxdef;
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Initialize the default character mappings.  If no mapping file is to
+ *   be read, this function will establish identify mappings that leave
+ *   characters untranslated. 
+ */
+void cmap_init_default(void);
+
+
+/*
+ *   Load a character map file.  Returns zero on success, non-zero on
+ *   failure.  If filename is null, we'll use the default mapping.
+ */
+int cmap_load(char *filename);
+
+
+/*
+ *   Turn off character translation.  This overrides any game character
+ *   set that we find and simply uses the default translation. 
+ */
+void cmap_override(void);
+
+
+/*
+ *   Set the game's internal character set.  This should be called when a
+ *   game is loaded, and the game specifies an internal character set.  If
+ *   there is no character map file explicitly loaded, we will attempt to
+ *   load a character mapping file that maps this character set to the
+ *   current native character set.  Signals an error on failure.  This
+ *   routine will succeed (without doing anything) if a character set has
+ *   already been explicitly loaded, since an explicitly-loaded character
+ *   set overrides the automatic character set selection that we attempt
+ *   when loading a game.
+ *   
+ *   argv0 must be provided so that we know where to look for our mapping
+ *   file on systems where mapping files are stored in the same directory
+ *   as the TADS executables.  
+ */
+void cmap_set_game_charset(struct errcxdef *errctx,
+                           char *internal_id, char *internal_ldesc,
+                           char *argv0);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Mapping macros 
+ */
+
+/* map a native character (read externally) into an internal character */
+#define cmap_n2i(c) (G_cmap_input[(unsigned char)(c)])
+
+/* map an internal character into a native character (for display) */
+#define cmap_i2n(c) (G_cmap_output[(unsigned char)(c)])
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Global character mapping tables.  The character map is established at
+ *   start-up. 
+ */
+
+/* 
+ *   input-mapping table - for native character 'n', cmap_input[n] yields
+ *   the internal character code 
+ */
+extern unsigned char G_cmap_input[256];
+
+/*
+ *   output-mapping table - for internal character 'n', cmap_output[n]
+ *   yields the output character code 
+ */
+extern unsigned char G_cmap_output[256];
+
+/* the ID of the loaded character set */
+extern char G_cmap_id[5];
+
+/* the full name (for display purposes) of the loaded character set */
+#define CMAP_LDESC_MAX_LEN  40
+extern char G_cmap_ldesc[CMAP_LDESC_MAX_LEN + 1];
+
+/*
+ *   Maximum expansion for an HTML entity mapping 
+ */
+#define CMAP_MAX_ENTITY_EXPANSION  50
+
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   Signatures for character map files.  The signature is stored at the
+ *   beginning of the file.  
+ */
+
+/* single-byte character map version 1.0.0 */
+#define CMAP_SIG_S100  "TADS2 charmap S100\n\r\01a"
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/data.cpp b/engines/glk/tads/tads2/data.cpp
index c60fcb2..b19f45f 100644
--- a/engines/glk/tads/tads2/data.cpp
+++ b/engines/glk/tads/tads2/data.cpp
@@ -21,7 +21,8 @@
  */
 
 #include "glk/tads/tads2/data.h"
-#include "glk/tads/tads2/types.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/run.h"
 #include "glk/tads/tads2/vocabulary.h"
 #include "common/algorithm.h"
 
diff --git a/engines/glk/tads/tads2/data.h b/engines/glk/tads/tads2/data.h
index 8db2153..8d122c4 100644
--- a/engines/glk/tads/tads2/data.h
+++ b/engines/glk/tads/tads2/data.h
@@ -47,6 +47,7 @@ enum DataType {
 	DAT_REDIR   = 16,		///< redirection to different object
 	DAT_TPL2    = 17		///< new-style template
 };
+typedef DataType dattyp;
 
 class Data {
 private:
diff --git a/engines/glk/tads/tads2/debug.h b/engines/glk/tads/tads2/debug.h
new file mode 100644
index 0000000..52695cf
--- /dev/null
+++ b/engines/glk/tads/tads2/debug.h
@@ -0,0 +1,589 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_DEBUG
+#define GLK_TADS_TADS2_DEBUG
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/object.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* forward declarations */
+struct bifcxdef;
+struct toksdef;
+struct toktdef;
+struct tokcxdef;
+
+/* stack frame record */
+struct dbgfdef
+{
+    struct runsdef *dbgfbp;                        /* base pointer of frame */
+    objnum  dbgfself;             /* 'self' object (MCMONINV for functions) */
+    objnum  dbgftarg;                               /* actual target object */
+    prpnum  dbgfprop;                          /* property being evalutated */
+    int     dbgfargc;                                /* number of arguments */
+    int     dbgfbif;      /* set to built-in function number if in built-in */
+    uint    dbgffr;         /* offset in object of local frame symbol table */
+    uint    dbgflin;                      /* OPCLINE operand of latest line */
+};
+typedef struct dbgfdef dbgfdef;
+
+/* max number of frames to store in debug frame memory */
+#define DBGMAXFRAME  100
+
+/* maximum number of breakpoints set concurrently */
+#define DBGBPMAX 50
+
+/* breakpoint structure */
+struct dbgbpdef
+{
+    objnum dbgbpself;               /* the "self" object for the breakpoint */
+    objnum dbgbptarg;            /* actual target object for the breakpoint */
+    uint   dbgbpofs;                  /* offset in object of the breakpoint */
+    uint   dbgbpflg;                                    /* breakpoint flags */
+#define DBGBPFUSED  0x01                         /* breakpoint has been set */
+#define DBGBPFNAME  0x02                 /* name of address has been stored */
+#define DBGBPFCOND  0x04             /* breakpoint has a condition attached */
+#define DBGBPFDISA  0x08                          /* breakpoint is disabled */
+#define DBGBPFCONDNAME 0x10        /* condition name string has been stored */
+    uint   dbgbpnam;       /* offset of address name within dbgcxnam buffer */
+    uint   dbgbpcondnam;        /* offset of condition string within buffer */
+    objnum dbgbpcond;        /* object containing compiled condition for bp */
+};
+typedef struct dbgbpdef dbgbpdef;
+
+/* maximum number of watch expressions set concurrently */
+#define DBGWXMAX 30
+
+/* watch expression structure */
+struct dbgwxdef
+{
+    objnum  dbgwxobj;              /* object containing compiled expression */
+    objnum  dbgwxself;                         /* 'self' for the expression */
+    uint    dbgwxnam;   /* offset of expression text within dbgcxnam buffer */
+    uint    dbgwxflg;               /* flags for this watch expression slot */
+#define DBGWXFUSED  0x01                            /* watch slot is in use */
+#define DBGWXFNAME  0x02                   /* name of watch has been stored */
+};
+typedef struct dbgwxdef dbgwxdef;
+
+/* amount of space for bp names (original address strings from user) */
+#define DBGCXNAMSIZ 2048
+
+/* debug context */
+struct dbgcxdef
+{
+    struct tiocxdef *dbgcxtio;                          /* text i/o context */
+    struct tokthdef *dbgcxtab;                              /* symbol table */
+    struct mcmcxdef *dbgcxmem;              /* memory cache manager context */
+    struct errcxdef *dbgcxerr;                    /* error handling context */
+    struct    lindef *dbgcxlin;                    /* chain of line sources */
+    int       dbgcxfcn;                          /* number of frames in use */
+    int       dbgcxdep;          /* actual depth (if overflow frame buffer) */
+    int       dbgcxfid;                        /* source file serial number */
+    dbgfdef   dbgcxfrm[DBGMAXFRAME];                        /* stack frames */
+    int       dbgcxflg;                          /* flags for debug session */
+#define    DBGCXFSS   0x01                  /* single-stepping source lines */
+#define    DBGCXFSO   0x02          /* stepping over a function/method call */
+#define    DBGCXFOK   0x04                         /* debugger is linked in */
+#define    DBGCXFIND  0x08     /* in debugger - suppress stack trace on err */
+#define    DBGCXFGBP  0x10                  /* global breakpoints in effect */
+#define    DBGCXFTRC  0x20                        /* call tracing activated */
+#define    DBGCXFLIN2 0x40         /* new-style line records (line numbers) */
+    int       dbgcxsof;                    /* frame depth at step-over time */
+    dbgbpdef  dbgcxbp[DBGBPMAX];                             /* breakpoints */
+    dbgwxdef  dbgcxwx[DBGWXMAX];                       /* watch expressions */
+    struct    prscxdef *dbgcxprs;                        /* parsing context */
+    struct    runcxdef *dbgcxrun;                      /* execution context */
+    uint      dbgcxnamf;               /* next free byte of dbgcxnam buffer */
+    uint      dbgcxnams;                         /* size of dbgcxnam buffer */
+    char     *dbgcxnam;                       /* space for bp address names */
+    char     *dbgcxhstp;                             /* call history buffer */
+    uint      dbgcxhstl;                           /* history buffer length */
+    uint      dbgcxhstf;             /* offset of next free byte of history */
+
+    /* 
+     *   This member is for the use of the user interface code.  If the
+     *   user interface implementation needs to store additional context,
+     *   it can allocate a structure of its own (it should probably do
+     *   this in dbguini()) and store a pointer to that structure here.
+     *   Since the user interface entrypoints always have the debugger
+     *   context passed as a parameter, the user interface code can
+     *   recover its extra context information by following this pointer
+     *   and casting it to its private structure type.  The TADS code
+     *   won't do anything with this pointer except initialize it to null
+     *   when initializing the debugger context.  
+     */
+    void     *dbgcxui;
+};
+typedef struct dbgcxdef dbgcxdef;
+
+
+/* ======================================================================== */
+/*
+ *   Compiler interface.  These routines are called by the compiler to
+ *   inform the debug record generator about important events as
+ *   compilation proceeds. 
+ */
+
+
+/*
+ *   Tell the current line source that we're compiling an executable
+ *   line, and tell it the object number and offset of the code within the
+ *   object. 
+ */
+void dbgclin(struct tokcxdef *tokctx, objnum objn, uint ofs);
+
+/* size of information given to line source via lincmpinf method */
+#define DBGLINFSIZ   4
+
+
+
+/* ======================================================================== */
+/*
+ *   Run-time interface.  These routines are called by the run-time
+ *   system to apprise the debugger of important events during execution.
+ */
+
+
+/*
+ *   Determine if the debugger is present.  Returns true if so, false if
+ *   not.  This should return false for any stand-alone version of the
+ *   executable that isn't linked with the debugger.  If this returns
+ *   true, dbgucmd() must not have a trivial implementation -- dbgucmd()
+ *   must at least let the user quit out of the game.
+ *   
+ *   This can be switched at either link time or compile time.  If DBG_OFF
+ *   is defined, we'll force this to return false; otherwise, we'll let
+ *   the program define the appropriate implementation through the linker.
+ */
+#ifdef DBG_OFF
+#define dbgpresent() (FALSE)
+#else
+int dbgpresent();
+#endif
+
+
+/* add a debug tracing record */
+/* void dbgenter(dbgcxdef *ctx, runsdef *bp, objnum self, objnum target,
+                 prpnum prop, int binum, int argc); */ 
+
+/* tell debugger where the current line's local frame table is located */
+/* void dbgframe(dbgcxdef *ctx, uint ofsfr, ofslin); */
+
+/* 
+ *   Single-step interrupt: the run-time has reached a new source line.
+ *   ofs is the offset from the start of the object of the line record,
+ *   and p is the current execution pointer.  *p can be changed upon
+ *   return, in which case the run-time will continue from the new
+ *   position; however, the new address must be within the same function
+ *   or method as it was originally.
+ */
+/* void dbgssi(dbgcxdef *ctx, uint ofs, int instr,
+               int err, uchar *noreg *p); */
+
+/* pop debug trace level */
+/* void dbgleave(dbgcxdef *ctx, int exittype); */
+#define DBGEXRET   0                                /* return with no value */
+#define DBGEXVAL   1                                 /* return with a value */
+#define DBGEXPASS  2                       /* use 'pass' to exit a function */
+
+/* dump the stack into text output */
+/* void dbgdump(dbgcxdef *ctx); */
+
+/* reset debug stack (throw away entire contents) */
+/* void dbgrst(dbgcxdef *ctx); */
+
+/* activate debugger if possible; returns TRUE if no debugger is present */
+int dbgstart(dbgcxdef *ctx);
+
+/* add a string to the history buffer */
+void dbgaddhist(dbgcxdef *ctx, char *buf, int bufl);
+
+/*
+ *   Find a base pointer, given the object+offset of the frame.  If the
+ *   frame is not active, this routine signals ERR_INACTFR; otherwise, the
+ *   bp value for the frame is returned. 
+ */
+struct runsdef *dbgfrfind(dbgcxdef *ctx, objnum frobj, uint frofs);
+
+
+/* ======================================================================== */
+/*
+ *   User Interface Support routines.  These routines are called by the
+ *   user interface layer to get information from the debugger and perform
+ *   debugging operations. 
+ */
+
+
+/* get a symbol name; returns length of name */
+int dbgnam(dbgcxdef *ctx, char *outbuf, int typ, int val);
+
+/*
+ *   Get information about current line.  It is assumed that the caller
+ *   knows the size of the line information .
+ */
+void dbglget(dbgcxdef *ctx, uchar *buf);
+
+/*
+ *   Get information about a line in an enclosing stack frame.  Level 0 is
+ *   the current line, level 1 is the first enclosing frame, and so on.
+ *   Returns 0 on success, non-zero if the frame level is invalid.  
+ */
+int dbglgetlvl(dbgcxdef *ctx, uchar *buf, int level);
+
+/*
+ *   Set a breakpoint by symbolic address: "function" or
+ *   "object.property".  The string may contain whitespace characters
+ *   around each symbol; it must be null-terminated.  If an error occurs,
+ *   the error number is returned.  bpnum returns with the breakpoint
+ *   number if err == 0.  If the condition string is given (and is not an
+ *   empty string), the condition is compiled in the scope of the
+ *   breakpoint and attached as the breakpoint condition.  
+ */
+int dbgbpset(dbgcxdef *ctx, char *addr, int *bpnum);
+
+/* 
+ *   Set a breakpoint at an object + offset location.  If 'toggle' is
+ *   true, and there's already a breakpoint at the given location, we'll
+ *   clear the breakpoint; in this case, *did_set will return false to
+ *   indicate that an existing breakpoint was cleared rather than a new
+ *   breakpoint created.  *did_set will return true if a new breakpoint
+ *   was set.  
+ */
+int dbgbpat(dbgcxdef *ctx, objnum objn, objnum self,
+            uint ofs, int *bpnum, char *bpname, int toggle,
+            char *condition, int *did_set);
+
+/* 
+ *   Set a breakpoint at an object + offset location, optionally with a
+ *   condition, using an existing breakpoint slot.  If the slot is already
+ *   in use, we'll return an error.  
+ */
+int dbgbpatid(dbgcxdef *ctx, int bpnum, objnum target, objnum self,
+              uint ofs, char *bpname, int toggle, char *cond,
+              int *did_set);
+
+/*
+ *   Determine if there's a breakpoint at a given code location.  Fills in
+ *   *bpnum with the breakpoint identifier and returns true if a
+ *   breakpoint is found at the given location; returns false if there are
+ *   no breakpoints matching the description.  
+ */
+int dbgisbp(dbgcxdef *ctx, objnum target, objnum self, uint ofs, int *bpnum);
+
+/*
+ *   Determine if the given breakpoint is enabled 
+ */
+int dbgisbpena(dbgcxdef *ctx, int bpnum);
+
+/*
+ *   Delete a breakpoint by breakpoint number (as returned from
+ *   dbgbpset).  Returns error number, or 0 for success. 
+ */
+int dbgbpdel(dbgcxdef *ctx, int bpnum);
+
+/* disable or enable a breakpoint, by breakpoint number; returns error num */
+int dbgbpdis(dbgcxdef *ctx, int bpnum, int disable);
+
+/*
+ *   Set a new condition for the given breakpoint.  Replaces any existing
+ *   condition.  If an error occurs, we'll leave the old condition as it
+ *   was and return a non-zero error code; on success, we'll update the
+ *   condition and return zero. 
+ */
+int dbgbpsetcond(dbgcxdef *ctx, int bpnum, char *cond);
+
+/* list breakpoints, using user callback to do display */
+void dbgbplist(dbgcxdef *ctx,
+               void (*dispfn)(void *ctx, const char *str, int len),
+               void *dispctx);
+
+/* enumerate breakpoints */
+void dbgbpenum(dbgcxdef *ctx,
+               void (*cbfunc)(void *cbctx, int bpnum, const char *desc,
+                              const char *cond, int disabled), void *cbctx);
+
+/* call callback with lindef data for each breakpoint currently set */
+void dbgbpeach(dbgcxdef *ctx,
+               void (*fn)(void *, int, uchar *, uint),
+               void *fnctx);
+
+/* 
+ *   Get information on a specific breakpoint.  Returns zero on success,
+ *   non-zero on failure. 
+ */
+int dbgbpgetinfo(dbgcxdef *ctx, int bpnum, char *descbuf, size_t descbuflen,
+                 char *condbuf, size_t condbuflen);
+
+/* 
+ *   Evaluate an expression (a text string to be parsed) at a particular
+ *   stack context level; returns error number.  Invokes the callback
+ *   function repeatedly to display the value string, and ends the display
+ *   with a newline.  If showtype is true, we'll include a type name
+ *   prefix, otherwise we'll simply display the value.  
+ */
+int dbgeval(dbgcxdef *ctx, char *expr,
+            void (*dispfn)(void *dispctx, const char *str, int strl),
+            void *dispctx, int level, int showtype);
+
+/*
+ *   Evaluate an expression, extended version.  For aggregate values
+ *   (objects, lists), we'll invoke a callback function for each value
+ *   contained by the aggregate value, passing the callback the name and
+ *   relationship of the subitem.  The relationship is simply the operator
+ *   that should be used to join the parent expression and the subitem
+ *   name to form the full subitem expression; for objects, it's ".", and
+ *   for lists it's null (because for lists the subitem names will include
+ *   brackets).  'speculative' is passed to dbgcompile; see the comments
+ *   there for information on the purpose of this flag.  
+ */
+int dbgevalext(dbgcxdef *ctx, char *expr,
+               void (*dispfn)(void *dispctx, const char *str, int strl),
+               void *dispctx, int level, int showtype, dattyp *dat,
+               void (*aggcb)(void *aggctx, const char *subname,
+                             int subnamelen, const char *relationship),
+               void *aggctx, int speculative);
+
+/* 
+ *   enumerate local variables at a given stack context level by calling
+ *   the given function once for each local variable 
+ */
+void dbgenumlcl(dbgcxdef *ctx, int level,
+                void (*func)(void *ctx, const char *lclnam, size_t lclnamlen),
+                void *cbctx);
+
+/* 
+ *   Compile an expression in a given frame context.  Returns an error
+ *   number.  Allocates a new object to contain the compiled code, and
+ *   returns the object number in *objn; the caller is responsible for
+ *   freeing the object when done with it.
+ *   
+ *   If 'speculative' is set to true, we'll prohibit the expression from
+ *   making any assignments or calling any methods or functions.  This
+ *   mode can be used to try compiling an expression that the user could
+ *   conceivably be interested in but has not expressly evaluated; for
+ *   example, this can be used to implement "tooltip evaluation," where
+ *   the debugger automatically shows a little pop-up window with the
+ *   expression under the mouse cursor if the mouse cursor is left
+ *   hovering over some text for a few moments.  In such cases, since the
+ *   user hasn't explicitly requested evaluation, it would be bad to make
+ *   any changes to game state, hence the prohibition of assignments or
+ *   calls.  
+ */
+int dbgcompile(dbgcxdef *ctx, char *expr, dbgfdef *fr, objnum *objn,
+               int speculative);
+
+/* display a stack traceback through a user callback */
+void dbgstktr(dbgcxdef *ctx,
+              void (*dispfn)(void *dispctx, const char *str, int strl),
+              void *dispctx, int level, int toponly, int include_markers);
+
+/* format a display of where execution is stopped into a buffer */
+void dbgwhere(dbgcxdef *ctx, char *buf);
+
+/* set a watch expression; returns error or 0 for success */
+int dbgwxset(dbgcxdef *ctx, char *expr, int *wxnum, int level);
+
+/* delete a watch expression */
+int dbgwxdel(dbgcxdef *ctx, int wxnum);
+
+/* update all watch expressions */
+void dbgwxupd(dbgcxdef *ctx,
+              void (*dispfn)(void *dispctx, const char *txt, int len),
+              void *dispctx);
+
+/* switch to a new active lindef */
+void dbgswitch(struct lindef **linp, struct lindef *newlin);
+
+
+
+/* ======================================================================== */
+/*
+ *   User Interface Routines.  The routines are called by the debugger
+ *   to perform user interaction.
+ */
+
+/* 
+ *   Debugger user interface initialization, phase one.  TADS calls this
+ *   routine during startup, before reading the .GAM file, to let the user
+ *   interface perform any initialization it requires before the .GAM file
+ *   is loaded.  
+ */
+void dbguini(dbgcxdef *ctx, const char *game_filename);
+
+/*
+ *   Debugger user interface initialization, phase two.  TADS calls this
+ *   routine during startup, after read the .GAM file.  The debugger user
+ *   interface code can perform any required initialization that depends
+ *   on the .GAM file having been read.  
+ */
+void dbguini2(dbgcxdef *ctx);
+
+/*
+ *   Determine if the debugger can resume from a run-time error.  This
+ *   reflects the capabilities of the user interface of the debugger.  In
+ *   particular, if the UI provides a way to change the instruction
+ *   pointer, then the debugger can resume from an error, since the user
+ *   can always move past the run-time error and continue execution.  If
+ *   the UI doesn't let the user change the instruction pointer, resuming
+ *   from an error won't work, since the program will keep hitting the
+ *   same error and re-entering the debugger.  If this returns false, the
+ *   run-time will trap to the debugger on an error, but will simply abort
+ *   the current command when the debugger returns.  If this returns true,
+ *   the run-time will trap to the debugger on an error with the
+ *   instruction pointer set back to the start of the line containing the
+ *   error, and will thus re-try the same line of code when the debugger
+ *   returns, unless the debugger explicitly moves the instruction pointer
+ *   before returning.  
+ */
+int dbgu_err_resume(dbgcxdef *ctx);
+
+/*
+ *   Find a source file.  origname is the name of the source file as it
+ *   appears in the game's debugging information; this routine should
+ *   figure out where the file actually is, and put the fully-qualified
+ *   path to the file in fullname.  The debugger calls this after it
+ *   exhausts all of its other methods of finding a source file (such as
+ *   searching the include path).
+ *   
+ *   Return true if the source file should be considered valid, false if
+ *   not.  Most implementations will simply return true if the file was
+ *   found, false if not; however, this approach will cause the debugger
+ *   to terminate with an error at start-up if the user hasn't set up the
+ *   debugger's include path correctly before running the debugger.  Some
+ *   implementations, in particular GUI implementations, may wish to wait
+ *   to find a file until the file is actually needed, rather than pester
+ *   the user with file search dialogs repeatedly at start-up.
+ *   
+ *   must_find_file specifies how to respond if we can't find the file.
+ *   If must_find_file is true, we should always return false if we can't
+ *   find the file.  If must_find_file is false, however, we can
+ *   optionally return true even if we can't find the file.  Doing so
+ *   indicates that the debugger UI will defer locating the file until it
+ *   is actually needed.
+ *   
+ *   If this routine returns true without actually finding the file, it
+ *   should set fullname[0] to '\0' to indicate that fullname doesn't
+ *   contain a valid filename.  
+ */
+int dbgu_find_src(const char *origname, int origlen,
+                  char *fullname, size_t full_len, int must_find_file);
+
+
+/* 
+ *   Debugger user interface main command loop.  If err is non-zero, the
+ *   debugger was entered because a run-time error occurred; otherwise, if
+ *   bphit is non-zero, it's the number of the breakpoint that was
+ *   encountered; otherwise, the debugger was entered through a
+ *   single-step of some kind.  exec_ofs is the byte offset within the
+ *   target object of the next instruction to be executed.  This can be
+ *   changed upon return, in which case execution will continue from the
+ *   new offset, but the offset must be within the same method of the same
+ *   object (or within the same function) as it was upon entry. 
+ */
+void dbgucmd(dbgcxdef *ctx, int bphit, int err, unsigned int *exec_ofs);
+
+/*
+ *   Debugger UI - quitting game.  The runtime calls this routine just
+ *   before the play loop is about to terminate after the game code has
+ *   called the "quit" built-in function.  If the debugger wants, it can
+ *   take control here (just as with dbgucmd()) for as long as it wants.
+ *   If the debugger wants to restart the game, it should call bifrst().
+ *   If this routine returns without signalling a RUN_RESTART error, TADS
+ *   will terminate.  If a RUN_RESTART error is signalled, TADS will
+ *   resume the play loop.  
+ */
+void dbguquitting(dbgcxdef *ctx);
+
+/* 
+ *   debugger user interface termination - this routine is called when the
+ *   debugger is about to terminate, so that the user interface can close
+ *   itself down (close windows, release memory, etc) 
+ */
+void dbguterm(dbgcxdef *ctx);
+
+/*
+ *   Debugger user interface: display an error.  This is called mainly so
+ *   that the debugger can display an error using special output
+ *   formatting if the error occurs while debugging. 
+ */
+void dbguerr(dbgcxdef *ctx, int errnum, char *msg);
+
+/* turn hidden output tracing on/off */
+void trchid(void);
+void trcsho(void);
+
+
+/* ======================================================================== */
+/*
+ *   optional debugger macros - these compile to nothing when compiling a
+ *   version for use without the debugger 
+ */
+
+#ifdef DBG_OFF
+# define dbgenter(ctx, bp, self, target, prop, binum, argc)
+# define dbgleave(ctx, exittype)
+# define dbgdump(ctx)
+# define dbgrst(ctx) ((void)0)
+# define dbgframe(ctx, frofs, linofs)
+# define dbgssi(ctx, ofs, instr, err, p)
+#else /* DBG_OFF */
+# define dbgenter(ctx, bp, self, target, prop, binum, argc) \
+   dbgent(ctx, bp, self, target, prop, binum, argc)
+# define dbgleave(ctx, exittype) dbglv(ctx, exittype)
+# define dbgdump(ctx) dbgds(ctx)
+# define dbgrst(ctx) ((ctx)->dbgcxfcn = (ctx)->dbgcxdep = 0)
+# define dbgframe(ctx, frofs, linofs) \
+   (((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgffr = (frofs)), \
+    ((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgflin = (linofs)))
+# define dbgssi(ctx, ofs, instr, err, p) dbgss(ctx, ofs, instr, err, p)
+#endif /* DBG_OFF */
+
+/* ======================================================================== */
+/* private internal routines */
+
+void dbgent(dbgcxdef *ctx, struct runsdef *bp, objnum self, objnum target,
+            prpnum prop, int binum, int argc);
+
+void dbglv(dbgcxdef *ctx, int exittype);
+
+void dbgds(dbgcxdef *ctx);
+
+void dbgss(dbgcxdef *ctx, uint ofs, int instr, int err, uchar *noreg *p);
+
+void dbgpval(struct dbgcxdef *ctx, struct runsdef *val,
+             void (*dispfn)(void *, const char *, int),
+             void *dispctx, int showtype);
+
+int dbgtabsea(struct toktdef *tab, char *name, int namel, int hash,
+              struct toksdef *ret);
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp
new file mode 100644
index 0000000..793ddcc
--- /dev/null
+++ b/engines/glk/tads/tads2/error.cpp
@@ -0,0 +1,32 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/error.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/error.h b/engines/glk/tads/tads2/error.h
new file mode 100644
index 0000000..9683106
--- /dev/null
+++ b/engines/glk/tads/tads2/error.h
@@ -0,0 +1,390 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* Error handling
+ * This package defines a set of macros that allows code to raise and
+ * handle exceptions.A macro is provided which signals an error, which
+ * does a non - local goto to the innermost enclosing exception handler.
+ * A set of macros sets up exception handling code.
+ * 
+ * To catch exceptions that occur inside a block of code(i.e., in the
+ * code or in any subroutines called by the code), begin the block with
+ * ERRBEGIN.At the end of the protected code, place the exception
+ * handler, which starts with ERRCATCH.At the end of the exception
+ * handler, place ERREND.If no exception occurs, execution goes
+ * through the protected code, then resumes at the code following
+ * the ERREND.
+ * 
+ * The exception handler can signal another error, which will cause
+ * the next enclosing frame to catch the error.Alternatively, if
+ * the exception handler doesn't signal an error or return, execution
+ * continues at the code following the ERREND.Exceptions that are
+ * signalled during exception handling will be caught by the next
+ * enclosing frame, unless the exception handler code is itself
+ * protected by another ERRBEGIN - ERREND block.
+ * 
+ * To signal an error, use errsig().
+ * 
+ * To use a string argument in a signalled error, cover the string
+ * with errstr(ctx, str, len); for example:
+ * 
+ * errsig1(ctx, ERR_XYZ, ERRTSTR, errstr(ctx, buf, strlen(buf)));
+ * 
+ * This copies the string into a buffer that is unaffected by
+ * stack resetting during error signalling.
+ */
+
+#ifndef GLK_TADS_TADS2_ERROR
+#define GLK_TADS_TADS2_ERROR
+
+#include "glk/tads/tads2/lib.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/*
+ *   for compatility with old facility-free mechanism, signal with
+ *   facility "TADS" 
+ */
+#define errsig(ctx, err) errsigf(ctx, "TADS", err)
+#define errsig1(c, e, t, a) errsigf1(c,"TADS",e,t,a)
+#define errsig2(c, e, t1, a1, t2, a2) errsigf2(c,"TADS",e,t1,a1,t2,a2)
+#define errlog(c, e) errlogf(c, "TADS", e)
+#define errlog1(c, e, t, a) errlogf1(c,"TADS",e,t,a)
+#define errlog2(c, e, t1, a1, t2, a2) errlogf2(c,"TADS",e,t1,a1,t2,a2)
+
+
+
+/*---------------------------- TADS Error Codes ----------------------------*/
+/* memory/cache manager errors */
+#define ERR_NOMEM   1                                      /* out of memory */
+#define ERR_FSEEK   2                              /* error seeking in file */
+#define ERR_FREAD   3                            /* error reading from file */
+#define ERR_NOPAGE  4                                 /* no more page slots */
+#define ERR_REALCK  5           /* attempting to reallocate a locked object */
+#define ERR_SWAPBIG 6     /* swapfile limit reached - out of virtual memory */
+#define ERR_FWRITE  7                                 /* error writing file */
+#define ERR_SWAPPG  8                     /* exceeded swap page table limit */
+#define ERR_CLIUSE  9      /* requested client object number already in use */
+#define ERR_CLIFULL 10                      /* client mapping table is full */
+#define ERR_NOMEM1  11   /* swapping/garbage collection failed to find enuf */
+#define ERR_NOMEM2  12            /* no memory to resize (expand) an object */
+#define ERR_OPSWAP  13                          /* unable to open swap file */
+#define ERR_NOHDR   14                     /* can't get a new object header */
+#define ERR_NOLOAD  15   /* mcm cannot find object to load (internal error) */
+#define ERR_LCKFRE  16     /* attempting to free a locked object (internal) */
+#define ERR_INVOBJ  17                                    /* invalid object */
+#define ERR_BIGOBJ  18  /* object too big - exceeds memory allocation limit */
+
+/* lexical analysis errors */
+#define ERR_INVTOK  100                                    /* invalid token */
+#define ERR_STREOF  101                /* end of file while scanning string */
+#define ERR_TRUNC   102                      /* symbol too long - truncated */
+#define ERR_NOLCLSY 103                   /* no space in local symbol table */
+#define ERR_PRPDIR  104               /* invalid preprocessor (#) directive */
+#define ERR_INCNOFN 105                /* no filename in #include directive */
+#define ERR_INCSYN  106                          /* invalid #include syntax */
+#define ERR_INCSEAR 107                         /* can't find included file */
+#define ERR_INCMTCH 108       /* no matching delimiter in #include filename */
+#define ERR_MANYSYM 109                    /* out of space for symbol table */
+#define ERR_LONGLIN 110                                    /* line too long */
+#define ERR_INCRPT  111           /* header file already included - ignored */
+#define ERR_PRAGMA  112                         /* unknown pragma (ignored) */
+#define ERR_BADPELSE 113                                /* unexpected #else */
+#define ERR_BADENDIF 114                               /* unexpected #endif */
+#define ERR_BADELIF 115                                 /* unexpected #elif */
+#define ERR_MANYPIF 116                             /* #if nesting too deep */
+#define ERR_DEFREDEF  117                 /* #define symbol already defined */
+#define ERR_PUNDEF  118                        /* #undef symbol not defined */
+#define ERR_NOENDIF 119                                   /* missing #endif */
+#define ERR_MACNEST 120                         /* macros nested too deeply */
+#define ERR_BADISDEF 121         /* invalid argument for defined() operator */
+#define ERR_PIF_NA  122                           /* #if is not implemented */
+#define ERR_PELIF_NA 123                        /* #elif is not implemented */
+#define ERR_P_ERROR 124                              /* error directive: %s */
+#define ERR_LONG_FILE_MACRO 125              /* __FILE__ expansion too long */
+#define ERR_LONG_LINE_MACRO 126              /* __LINE__ expansion too long */
+
+/* undo errors */
+#define ERR_UNDOVF  200                /* operation is too big for undo log */
+#define ERR_NOUNDO  201                         /* no more undo information */
+#define ERR_ICUNDO  202          /* incomplete undo (no previous savepoint) */
+
+/* parser errors */
+#define ERR_REQTOK  300    /* expected token (arg 1) - found something else */
+#define ERR_REQSYM  301                                  /* expected symbol */
+#define ERR_REQPRP  302                         /* expected a property name */
+#define ERR_REQOPN  303                                 /* expected operand */
+#define ERR_REQARG  304       /* expected comma or closing paren (arg list) */
+#define ERR_NONODE  305                      /* no space for new parse node */
+#define ERR_REQOBJ  306                             /* epxected object name */
+#define ERR_REQEXT  307           /* redefining symbol as external function */
+#define ERR_REQFCN  308                    /* redefining symbol as function */
+#define ERR_NOCLASS 309  /* can't use CLASS with function/external function */
+#define ERR_REQUNO  310                          /* required unary operator */
+#define ERR_REQBIN  311                         /* required binary operator */
+#define ERR_INVBIN  312                          /* invalid binary operator */
+#define ERR_INVASI  313                               /* invalid assignment */
+#define ERR_REQVAR  314                           /* required variable name */
+#define ERR_LCLSYN  315        /* required comma or semicolon in local list */
+#define ERR_REQRBR  316   /* required right brace (eof before end of group) */
+#define ERR_BADBRK  317                          /* 'break' without 'while' */
+#define ERR_BADCNT  318                       /* 'continue' without 'while' */
+#define ERR_BADELS  319                              /* 'else' without 'if' */
+#define ERR_WEQASI  320 /* warning: possible use of '=' where ':=' intended */
+#define ERR_EOF     321                           /* unexpected end of file */
+#define ERR_SYNTAX  322                             /* general syntax error */
+#define ERR_INVOP   323                             /* invalid operand type */
+#define ERR_NOMEMLC 324             /* no memory for new local symbol table */
+#define ERR_NOMEMAR 325              /* no memory for argument symbol table */
+#define ERR_FREDEF  326   /* redefining a function which is already defined */
+#define ERR_NOSW    327      /* 'case' or 'default' and not in switch block */
+#define ERR_SWRQCN  328           /* constant required in switch case value */
+#define ERR_REQLBL  329                        /* label required for 'goto' */
+#define ERR_NOGOTO  330                       /* 'goto' label never defined */
+#define ERR_MANYSC  331                 /* too many superclasses for object */
+#define ERR_OREDEF  332                      /* redefining symbol as object */
+#define ERR_PREDEF  333               /* property being redefined in object */
+#define ERR_BADPVL  334                           /* invalid property value */
+#define ERR_BADVOC  335                    /* bad vocabulary property value */
+#define ERR_BADTPL  336       /* bad template property value (need sstring) */
+#define ERR_LONGTPL 337             /* template base property name too long */
+#define ERR_MANYTPL 338     /* too many templates (internal compiler limit) */
+#define ERR_BADCMPD 339   /* bad value for compound word (sstring required) */
+#define ERR_BADFMT  340     /* bad value for format string (sstring needed) */
+#define ERR_BADSYN  341     /* invalid value for synonym (sstring required) */
+#define ERR_UNDFSYM 342                                 /* undefined symbol */
+#define ERR_BADSPEC 343                                 /* bad special word */
+#define ERR_NOSELF  344                 /* "self" not valid in this context */
+#define ERR_STREND  345            /* warning: possible unterminated string */
+#define ERR_MODRPLX 346    /* modify/replace not allowed with external func */
+#define ERR_MODFCN  347                 /* modify not allowed with function */
+#define ERR_MODFWD  348     /* modify/replace not allowed with forward func */
+#define ERR_MODOBJ  349    /* modify can only be used with a defined object */
+#define ERR_RPLSPEC 350                 /* warning - replacing specialWords */
+#define ERR_SPECNIL 351        /* nil only allowed with modify specialWords */
+#define ERR_BADLCL  353   /* 'local' statement must precede executable code */
+#define ERR_IMPPROP 354          /* implied verifier '%s' is not a property */
+#define ERR_BADTPLF 355                    /* invalid command template flag */
+#define ERR_NOTPLFLG 356      /* flags are not allowed with old file format */
+#define ERR_AMBIGBIN 357          /* warning: operator '%s' could be binary */
+#define ERR_PIA     358           /* warning: possibly incorrect assignment */
+#define ERR_BADSPECEXPR 359               /* invalid speculation evaluation */
+
+/* code generation errors */
+#define ERR_OBJOVF  400     /* object cannot grow any bigger - code too big */
+#define ERR_NOLBL   401                  /* no more temporary labels/fixups */
+#define ERR_LBNOSET 402                 /* (internal error) label never set */
+#define ERR_INVLSTE 403                /* invalid datatype for list element */
+#define ERR_MANYDBG 404  /* too many debugger line records (internal limit) */
+
+/* vocabulary setup errors */
+#define ERR_VOCINUS 450            /* vocabulary being redefined for object */
+#define ERR_VOCMNPG 451          /* too many vocwdef pages (internal limit) */
+#define ERR_VOCREVB 452                             /* redefining same verb */
+#define ERR_VOCREVB2 453            /* redefining same verb - two arguments */
+
+/* set-up errors */
+#define ERR_LOCNOBJ 500           /* location of object %s is not an object */
+#define ERR_CNTNLST 501                /* contents of object %s is not list */
+#define ERR_SUPOVF  502           /* overflow trying to build contents list */
+#define ERR_RQOBJNF 503                     /* required object %s not found */
+#define ERR_WRNONF  504                    /* warning - object %s not found */
+#define ERR_MANYBIF 505     /* too many built-in functions (internal error) */
+
+/* fio errors */
+#define ERR_OPWGAM  600                  /* unable to open game for writing */
+#define ERR_WRTGAM  601                       /* error writing to game file */
+#define ERR_FIOMSC  602              /* too many sc's for writing in fiowrt */
+#define ERR_UNDEFF  603                               /* undefined function */
+#define ERR_UNDEFO  604                                 /* undefined object */
+#define ERR_UNDEF   605                          /* undefined symbols found */
+#define ERR_OPRGAM  606                  /* unable to open game for reading */
+#define ERR_RDGAM   607                          /* error reading game file */
+#define ERR_BADHDR  608     /* file has invalid header - not TADS game file */
+#define ERR_UNKRSC  609               /* unknown resource type in .gam file */
+#define ERR_UNKOTYP 610              /* unknown object type in OBJ resource */
+#define ERR_BADVSN  611     /* file saved by different incompatible version */
+#define ERR_LDGAM   612                   /* error loading object on demand */
+#define ERR_LDBIG   613  /* object too big for load region (prob. internal) */
+#define ERR_UNXEXT  614                 /* did not expect external function */
+#define ERR_WRTVSN  615      /* compiler cannot write the requested version */
+#define ERR_VNOCTAB 616         /* format version cannot be used with -ctab */
+#define ERR_BADHDRRSC 617        /* invalid resource file header in file %s */
+#define ERR_RDRSC   618                /* error reading resource file "xxx" */
+
+/* character mapping errors */
+#define ERR_CHRNOFILE 700          /* unable to load character mapping file */
+
+/* user interrupt */
+#define ERR_USRINT  990       /* user requested cancel of current operation */
+
+/* run-time errors */
+#define ERR_STKOVF  1001                                  /* stack overflow */
+#define ERR_HPOVF   1002                                   /* heap overflow */
+#define ERR_REQNUM  1003                          /* numeric value required */
+#define ERR_STKUND  1004                                 /* stack underflow */
+#define ERR_REQLOG  1005                          /* logical value required */
+#define ERR_INVCMP  1006      /* invalid datatypes for magnitude comparison */
+#define ERR_REQSTR  1007                           /* string value required */
+#define ERR_INVADD  1008              /* invalid datatypes for '+' operator */
+#define ERR_INVSUB  1009       /* invalid datatypes for binary '-' operator */
+#define ERR_REQVOB  1010                            /* require object value */
+#define ERR_REQVFN  1011                       /* required function pointer */
+#define ERR_REQVPR  1012                  /* required property number value */
+
+/* non-error conditions:  run-time EXIT, ABORT, ASKIO, ASKDO */
+#define ERR_RUNEXIT 1013                       /* 'exit' statement executed */
+#define ERR_RUNABRT 1014                      /* 'abort' statement executed */
+#define ERR_RUNASKD 1015                      /* 'askdo' statement executed */
+#define ERR_RUNASKI 1016             /* 'askio' executed; int arg 1 is prep */
+#define ERR_RUNQUIT 1017                                 /* 'quit' executed */
+#define ERR_RUNRESTART 1018                             /* 'reset' executed */
+#define ERR_RUNEXITOBJ 1019                           /* 'exitobj' executed */
+
+#define ERR_REQVLS  1020                             /* list value required */
+#define ERR_LOWINX  1021              /* index value too low (must be >= 1) */
+#define ERR_HIGHINX 1022  /* index value too high (must be <= length(list)) */
+#define ERR_INVTBIF 1023              /* invalid type for built-in function */
+#define ERR_INVVBIF 1024             /* invalid value for built-in function */
+#define ERR_BIFARGC 1025           /* wrong number of arguments to built-in */
+#define ERR_ARGC    1026      /* wrong number of arguments to user function */
+#define ERR_FUSEVAL 1027     /* string/list not allowed for fuse/daemon arg */
+#define ERR_BADSETF 1028      /* internal error in setfuse/setdaemon/notify */
+#define ERR_MANYFUS 1029                                  /* too many fuses */
+#define ERR_MANYDMN 1030                                /* too many daemons */
+#define ERR_MANYNFY 1031                              /* too many notifiers */
+#define ERR_NOFUSE  1032                       /* fuse not found in remfuse */
+#define ERR_NODMN   1033                   /* daemon not found in remdaemon */
+#define ERR_NONFY   1034                  /* notifier not found in unnotify */
+#define ERR_BADREMF 1035    /* internal error in remfuse/remdaemon/unnotify */
+#define ERR_DMDLOOP 1036     /* load-on-demand loop: property not being set */
+#define ERR_UNDFOBJ 1037             /* undefined object in vocabulary tree */
+#define ERR_BIFCSTR 1038            /* c-string conversion overflows buffer */
+#define ERR_INVOPC  1039                                  /* invalid opcode */
+#define ERR_RUNNOBJ 1040     /* runtime error: property taken of non-object */
+#define ERR_EXTLOAD 1041           /* unable to load external function "%s" */
+#define ERR_EXTRUN  1042          /* error executing external function "%s" */
+#define ERR_CIRCSYN 1043                                /* circular synonym */
+#define ERR_DIVZERO 1044                                  /* divide by zero */
+#define ERR_BADDEL  1045      /* can only delete objects created with "new" */
+#define ERR_BADNEWSC 1046    /* superclass for "new" cannot be a new object */
+#define ERR_VOCSTK  1047              /* insufficient space in parser stack */
+#define ERR_BADFILE 1048                             /* invalid file handle */
+
+#define ERR_RUNEXITPRECMD 1049                    /* exited from preCommand */
+
+/* run-time parser errors */
+#define ERR_PRS_SENT_UNK  1200         /* sentence structure not recognized */
+#define ERR_PRS_VERDO_FAIL 1201                         /* verDoVerb failed */
+#define ERR_PRS_VERIO_FAIL 1202                         /* verIoVerb failed */
+#define ERR_PRS_NO_VERDO  1203            /* no verDoVerb for direct object */
+#define ERR_PRS_NO_VERIO  1204            /* no verIoVerb for direct object */
+#define ERR_PRS_VAL_DO_FAIL  1205        /* direct object validation failed */
+#define ERR_PRS_VAL_IO_FAIL  1206      /* indirect object validation failed */
+
+/* compiler/runtime/debugger driver errors */
+#define ERR_USAGE   1500                                   /* invalid usage */
+#define ERR_OPNINP  1501                        /* error opening input file */
+#define ERR_NODBG   1502                 /* game not compiled for debugging */
+#define ERR_ERRFIL  1503               /* unable to open error capture file */
+#define ERR_PRSCXSIZ 1504              /* parse pool + local size too large */
+#define ERR_STKSIZE 1505                            /* stack size too large */
+#define ERR_OPNSTRFIL 1506             /* error opening string capture file */
+#define ERR_INVCMAP 1507                      /* invalid character map file */
+
+/* debugger errors */
+#define ERR_BPSYM   2000                 /* symbol not found for breakpoint */
+#define ERR_BPPROP  2002             /* breakpoint symbol is not a property */
+#define ERR_BPFUNC  2003             /* breakpoint symbol is not a function */
+#define ERR_BPNOPRP 2004              /* property is not defined for object */
+#define ERR_BPPRPNC 2005                            /* property is not code */
+#define ERR_BPSET   2006         /* breakpoint already set at this location */
+#define ERR_BPNOTLN 2007     /* breakpoint is not at a line (OPCLINE instr) */
+#define ERR_MANYBP  2008                            /* too many breakpoints */
+#define ERR_BPNSET  2009            /* breakpoint to be deleted was not set */
+#define ERR_DBGMNSY 2010  /* too many symbols in debug expression (int lim) */
+#define ERR_NOSOURC 2011                   /* unable to find source file %s */
+#define ERR_WTCHLCL 2012        /* illegal to assign to local in watch expr */
+#define ERR_INACTFR 2013 /* inactive frame (expression value not available) */
+#define ERR_MANYWX  2014                      /* too many watch expressions */
+#define ERR_WXNSET  2015                              /* watchpoint not set */
+#define ERR_EXTRTXT 2016               /* extraneous text at end of command */
+#define ERR_BPOBJ   2017              /* breakpoint symbol is not an object */
+#define ERR_DBGINACT 2018                         /* debugger is not active */
+#define ERR_BPINUSE 2019                      /* breakpoint is already used */
+#define ERR_RTBADSPECEXPR 2020            /* invalid speculative expression */
+#define ERR_NEEDLIN2 2021    /* -ds2 information not found - must recompile */
+
+/* usage error messages */
+#define ERR_TCUS1   3000                          /* first tc usage message */
+#define ERR_TCUSL   3024                           /* last tc usage message */
+#define ERR_TCTGUS1 3030                         /* first tc toggle message */
+#define ERR_TCTGUSL 3032
+#define ERR_TCZUS1  3040            /* first tc -Z suboptions usage message */
+#define ERR_TCZUSL  3041
+#define ERR_TC1US1  3050            /* first tc -1 suboptions usage message */
+#define ERR_TC1USL  3058
+#define ERR_TCMUS1  3070            /* first tc -m suboptions usage message */
+#define ERR_TCMUSL  3076
+#define ERR_TCVUS1  3080                /* first -v suboption usage message */
+#define ERR_TCVUSL  3082
+#define ERR_TRUSPARM 3099
+#define ERR_TRUS1   3100                          /* first tr usage message */
+#define ERR_TRUSL   3117
+#define ERR_TRUSFT1 3118                       /* first tr "footer" message */
+#define ERR_TRUSFTL 3119                        /* last tr "footer" message */
+#define ERR_TRSUS1  3150            /* first tr -s suboptions usage message */
+#define ERR_TRSUSL  3157
+#define ERR_TDBUSPARM 3199
+#define ERR_TDBUS1  3200                         /* first tdb usage message */
+#define ERR_TDBUSL  3214                          /* last tdb usage message */
+
+/* TR 16-bit MSDOS-specific usage messages */
+#define ERR_TRUS_DOS_1 3300
+#define ERR_TRUS_DOS_L 3300
+
+/* TR 32-bit MSDOS console mode usage messages */
+#define ERR_TRUS_DOS32_1  3310
+#define ERR_TRUS_DOS32_L  3312
+
+/* TADS/Graphic errors */
+#define ERR_GNOFIL  4001                     /* can't find graphics file %s */
+#define ERR_GNORM   4002                              /* can't find room %s */
+#define ERR_GNOOBJ  4003                   /* can't find hot spot object %s */
+#define ERR_GNOICN  4004                       /* can't find icon object %s */
+
+
+/*
+ *   Special error flag - this is returned from execmd() when preparseCmd
+ *   returns a command list.  This indicates to voc1cmd that it should try
+ *   the command over again, using the words in the new list. 
+ */
+#define ERR_PREPRSCMDREDO  30000             /* preparseCmd returned a list */
+#define ERR_PREPRSCMDCAN   30001    /* preparseCmd returned 'nil' to cancel */
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/error_handling.cpp b/engines/glk/tads/tads2/error_handling.cpp
new file mode 100644
index 0000000..a2300ee
--- /dev/null
+++ b/engines/glk/tads/tads2/error_handling.cpp
@@ -0,0 +1,32 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "error_handling.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h
new file mode 100644
index 0000000..a225bbe
--- /dev/null
+++ b/engines/glk/tads/tads2/error_handling.h
@@ -0,0 +1,338 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* Library error handling definitions
+ * All of the functions and macros in here are named ERRxxx because
+ * this file was based on the TADS err.h, which used the ERRxxx naming
+ * convention, and it would be a lot of trouble to change.
+ * 
+ * This package defines a set of macros that allows code to raise and
+ * handle exceptions.  A macro is provided which signals an error, which
+ * does a non-local goto to the innermost enclosing exception handler.
+ * A set of macros sets up exception handling code.
+ * 
+ * To catch exceptions that occur inside a block of code (i.e., in the
+ * code or in any subroutines called by the code), begin the block with
+ * ERRBEGIN.  At the end of the protected code, place the exception
+ * handler, which starts with ERRCATCH.  At the end of the exception
+ * handler, place ERREND.  If no exception occurs, execution goes
+ * through the protected code, then resumes at the code following
+ * the ERREND.
+ * 
+ * The exception handler can signal another error, which will cause
+ * the next enclosing frame to catch the error.  Alternatively, if
+ * the exception handler doesn't signal an error or return, execution
+ * continues at the code following the ERREND.  Exceptions that are
+ * signalled during exception handling will be caught by the next
+ * enclosing frame, unless the exception handler code is itself
+ * protected by another ERRBEGIN-ERREND block.
+ * 
+ * To signal an error, use errsig().
+ * 
+ * To use a string argument in a signalled error, cover the string
+ * with errstr(ctx, str, len); for example:
+ * 
+ * errsig1(ctx, ERR_XYZ, ERRTSTR, errstr(ctx, buf, strlen(buf)));
+ * 
+ * This copies the string into a buffer that is unaffected by
+ * stack resetting during error signalling.
+ */
+
+#ifndef GLK_TADS_TADS2_ERROR_HANDLING
+#define GLK_TADS_TADS2_ERROR_HANDLING
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/osfrobtads.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+// TODO: Clean away use of jmp_buf, since ScummVM doesn't allow it
+struct jmp_buf {};
+
+/**
+ * Maximum length of a facility identifier
+ */
+#define ERRFACMAX    6
+
+union erradef {
+    int   erraint;                                      /* integer argument */
+    char *errastr;                                  /* text string argument */
+};
+
+struct errdef {
+    struct errdef *errprv;                          /* previous error frame */
+    int            errcode;        /* error code of exception being handled */
+    char           errfac[ERRFACMAX+1];        /* facility of current error */
+    erradef        erraav[10];                      /* parameters for error */
+    int            erraac;                   /* count of parameters in argc */
+    jmp_buf        errbuf;           /* jump buffer for current error frame */
+};
+
+#define ERRBUFSIZ 512
+
+/**
+ * Seek location record for an error message by number
+ */
+struct errmfdef {
+    uint  errmfnum;                                         /* error number */
+    ulong errmfseek;                       /* seek location of this message */
+};
+typedef struct errmfdef errmfdef;
+
+struct errcxdef {
+    errdef   *errcxptr;                              /* current error frame */
+    void    (*errcxlog)(void *, char *fac, int err, int argc, erradef *);
+                                         /* error logging callback function */
+    void     *errcxlgc;               /* context for error logging callback */
+    int       errcxofs;                        /* offset in argument buffer */
+    char      errcxbuf[ERRBUFSIZ];            /* space for argument strings */
+    osfildef *errcxfp;                /* message file, if one is being used */
+    errmfdef *errcxseek;              /* seek locations of messages in file */
+    uint      errcxsksz;                         /* size of errcxseek array */
+    ulong     errcxbase;   /* offset in physical file of logical error file */
+    struct appctxdef *errcxappctx;              /* host application context */
+};
+
+/**
+ * Begin protected code
+ */
+#define ERRBEGIN(ctx) \
+  { \
+    errdef fr_; \
+    if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \
+    { \
+      fr_.errprv = (ctx)->errcxptr; \
+      (ctx)->errcxptr = &fr_;
+
+/**
+ * End protected code, begin error handler
+ */
+#define ERRCATCH(ctx, e) \
+      assert(1==1 && (ctx)->errcxptr != fr_.errprv); \
+      (ctx)->errcxptr = fr_.errprv; \
+    } \
+    else \
+    { \
+      assert(2==2 && (ctx)->errcxptr != fr_.errprv); \
+      (e) = fr_.errcode; \
+      (ctx)->errcxptr = fr_.errprv;
+        
+/* retrieve argument (int, string) in current error frame */
+#define errargint(argnum) (fr_.erraav[argnum].erraint)
+#define errargstr(argnum) (fr_.erraav[argnum].errastr)
+
+    
+#define ERREND(ctx) \
+    } \
+  }
+
+/* end protected code, begin cleanup (no handling; just cleaning up) */
+#define ERRCLEAN(ctx) \
+      assert((ctx)->errcxptr != fr_.errprv); \
+      (ctx)->errcxptr = fr_.errprv; \
+    } \
+    else \
+    { \
+      assert((ctx)->errcxptr != fr_.errprv); \
+      (ctx)->errcxptr = fr_.errprv;
+
+#define ERRENDCLN(ctx) \
+      errrse(ctx); \
+    } \
+  }
+
+
+
+/* argument types for errors with arguments */
+#define ERRTINT  erraint
+#define ERRTSTR  errastr
+
+/* set argument count in error frame */
+#define errargc(ctx,cnt) ((ctx)->errcxptr->erraac=(cnt))
+
+/* enter string argument; returns pointer to argument used in errargv */
+#ifdef ERR_NO_MACRO
+char *errstr(errcxdef *ctx, const char *str, int len);
+#else /* ERR_NO_MACRO */
+  
+#define errstr(ctx,str,len) \
+  ((memcpy(&(ctx)->errcxbuf[(ctx)->errcxofs],str,(size_t)len), \
+   (ctx)->errcxofs += (len), \
+   (ctx)->errcxbuf[(ctx)->errcxofs++] = '\0'), \
+   &(ctx)->errcxbuf[(ctx)->errcxofs-(len)-1])
+
+#endif /* ERR_NO_MACRO */
+
+/* set argument in error frame argument vector */
+#define errargv(ctx,index,typ,arg) \
+  ((ctx)->errcxptr->erraav[index].typ=(arg))
+
+/* signal an error with argument count already set */
+#ifdef ERR_NO_MACRO
+void errsign(errcxdef *ctx, int e, char *facility);
+#else /* ERR_NO_MACRO */
+# ifdef DEBUG
+void errjmp(jmp_buf buf, int e);
+#  define errsign(ctx, e, fac) \
+   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
+    (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
+    (ctx)->errcxofs=0, errjmp((ctx)->errcxptr->errbuf, e))
+# else /* DEBUG */
+#  define errsign(ctx, e, fac) \
+   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
+    (ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
+    (ctx)->errcxofs=0, longjmp((ctx)->errcxptr->errbuf, e))
+# endif /* DEBUG */
+#endif /* ERR_NO_MACRO */
+
+
+/* signal an error with no arguments */
+#ifdef ERR_NO_MACRO
+void errsigf(errcxdef *ctx, char *facility, int err);
+#else /* ERR_NO_MACRO */
+#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac))
+#endif /* ERR_NO_MACRO */
+  
+/* signal an error with one argument */
+#define errsigf1(ctx, fac, e, typ1, arg1) \
+  (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errsign(ctx,e,fac))
+
+/* signal an error with two arguments */
+#define errsigf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
+  (errargv(ctx,0,typ1,arg1), errargv(ctx,1,typ2,arg2), \
+   errargc(ctx,2), errsign(ctx,e,fac))
+
+/* resignal the current error - only usable within exception handlers */
+#ifdef ERR_NO_MACRO
+void errrse1(errcxdef *ctx, errdef *fr);
+# define errrse(ctx) errrse1(ctx, &fr_)
+#else /* ERR_NO_MACRO */
+
+/* void errrse(errcxdef *ctx); */
+# define errrse(ctx) \
+  (errargc(ctx, fr_.erraac),\
+   memcpy((ctx)->errcxptr->erraav, fr_.erraav, \
+    (size_t)(fr_.erraac*sizeof(erradef))),\
+   errsign(ctx, fr_.errcode, fr_.errfac))
+
+#endif /* ERR_NO_MACRO */
+
+/*
+ *   For use in an error handler (ERRCATCH..ERREND) only: Copy the
+ *   parameters from the error currently being handled to the enclosing
+ *   frame.  This is useful when "keeping" an error being handled - i.e.,
+ *   the arguments will continue to be used outside of the
+ *   ERRCATCH..ERREND code. 
+ */
+/* void errkeepargs(errcxdef *ctx); */
+#define errkeepargs(ctx) errcopyargs(ctx, &fr_)
+
+/* 
+ *   copy the parameters for an error from another frame into the current
+ *   frame - this can be used when we want to be able to display an error
+ *   that occurred in an inner frame within code that is protected by a
+ *   new enclosing error frame 
+ */
+/* void errcopyargs(errcxdef *ctx, errdef *fr); */
+#define errcopyargs(ctx, fr) \
+   (errargc((ctx), (fr)->erraac), \
+    memcpy((ctx)->errcxptr->erraav, (fr)->erraav, \
+           (size_t)((fr)->erraac*sizeof(erradef))))
+
+/* log error that's been caught, using arguments already caught */
+#define errclog(ctx) \
+ ((*(ctx)->errcxlog)((ctx)->errcxlgc,fr_.errfac,fr_.errcode,\
+  fr_.erraac,fr_.erraav))
+
+/* log an error that's been set up but not signalled yet */
+#define errprelog(ctx, err) \
+ ((*(ctx)->errcxlog)((ctx)->errcxlgc,(ctx)->errcxptr->errfac,\
+   err,(ctx)->errcxptr->erraac,\
+   (ctx)->errcxptr->erraav))
+
+/* log an error (no signalling, just reporting) */
+#ifdef ERR_NO_MACRO
+void errlogn(errcxdef *ctx, int err, char *facility);
+#else /* ERR_NO_MACRO */
+
+#define errlogn(ctx,err,fac) \
+ ((ctx)->errcxofs=0,\
+  (*(ctx)->errcxlog)((ctx)->errcxlgc,fac,err,(ctx)->errcxptr->erraac,\
+  (ctx)->errcxptr->erraav))
+
+#endif /* ERR_NO_MACRO */
+
+/* log an error with no arguments */
+#ifdef ERR_NO_MACRO
+void errlogf(errcxdef *ctx, char *facility, int err);
+#else /* ERR_NO_MACRO */
+
+/* void errlogf(errcxdef *ctx, char *facility, int err); */
+#define errlogf(ctx,fac,err) (errargc(ctx,0),errlogn(ctx,err,fac))
+
+#endif /* ERR_NO_MACRO */
+
+/* log an error with one argument */
+#define errlogf1(ctx, fac, e, typ1, arg1) \
+ (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errlogn(ctx,e,fac))
+  
+/* log an error with two arguments */
+#define errlogf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
+ (errargv(ctx,0,typ1,arg1),errargv(ctx,1,typ2,arg2),\
+  errargc(ctx,2),errlogn(ctx,e,fac))
+
+
+/*
+ *   Format an error message, sprintf-style, using arguments in an
+ *   erradef array (which is passed to the error-logging callback).
+ *   Returns the length of the output string, even if the actual
+ *   output string was truncated because the outbuf was too short.
+ *   (If called with outbufl == 0, nothing will be written out, but
+ *   the size of the buffer needed, minus the terminating null byte,
+ *   will be computed and returned.)
+ */
+int errfmt(char *outbuf, int outbufl, char *fmt, int argc,
+           erradef *argv);
+  
+/* get the text of an error */
+void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err);
+  
+/* initialize error subsystem, opening error message file if necessary */
+void errini(errcxdef *ctx, osfildef *fp);
+
+/* allocate and initialize error context, free error context */
+errcxdef *lerini();
+void      lerfre(errcxdef *ctx);
+
+/* error message structure - number + text */
+struct errmdef {
+    uint   errmerr;                                         /* error number */
+    char  *errmtxt;                                /* text of error message */
+};
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp
new file mode 100644
index 0000000..581601f
--- /dev/null
+++ b/engines/glk/tads/tads2/file_io.cpp
@@ -0,0 +1,32 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/file_io.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/file_io.h b/engines/glk/tads/tads2/file_io.h
new file mode 100644
index 0000000..8599c49
--- /dev/null
+++ b/engines/glk/tads/tads2/file_io.h
@@ -0,0 +1,137 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * file i/o interface
+ */
+
+#ifndef GLK_TADS_TADS2_FILE_IO
+#define GLK_TADS_TADS2_FILE_IO
+
+#include "glk/tads/tads2/lib.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* forward declarations */
+struct voccxdef;
+
+/* load-on-demand context (passed in by mcm in load callback) */
+typedef struct fiolcxdef fiolcxdef;
+struct fiolcxdef
+{
+    osfildef *fiolcxfp;                        /* file pointer of load file */
+    errcxdef *fiolcxerr;                          /* error handling context */
+    ulong     fiolcxst;                          /* starting offset in file */
+    uint      fiolcxflg;                   /* flags from original load file */
+    uint      fiolcxseed;                                    /* fioxor seed */
+    uint      fiolcxinc;                                /* fioxor increment */
+};
+
+/* write game to binary file */
+void fiowrt(struct mcmcxdef *mctx, voccxdef *vctx,
+            struct tokcxdef *tokctx, struct tokthdef *tab,
+            uchar *fmts, uint fmtl, char *fname, uint flags, objnum preinit,
+            int extc, uint prpcnt, char *filever);
+
+/* flag values for use with fiowrt */
+#define FIOFSYM   0x01               /* include symbol table in output file */
+#define FIOFLIN   0x02          /* include source file tracking information */
+#define FIOFPRE   0x04        /* preinit needs to be run after reading game */
+#define FIOFCRYPT 0x08           /* "encrypt" objects prior to writing them */
+#define FIOFBIN   0x10                        /* writing precompiled header */
+#define FIOFFAST  0x20                     /* fast-load records are in file */
+#define FIOFCASE  0x40    /* case folding was turned on in original compile */
+#define FIOFLIN2  0x80                            /* new-style line records */
+
+/* read game from binary file; sets up loader callback context */
+void fiord(struct mcmcxdef *mctx, voccxdef *vctx,
+           struct tokcxdef *tctx,
+           char *fname, char *exename,
+           struct fiolcxdef *setupctx, objnum *preinit, uint *flagp,
+           struct tokpdef *path, uchar **fmtsp, uint *fmtlp,
+           uint *pcntptr, int flags, struct appctxdef *appctx,
+           char *argv0);
+
+/* shut down load-on-demand subsystem, close load file */
+void fiorcls(fiolcxdef *ctx);
+
+/* loader callback - load an object on demand */
+void OS_LOADDS fioldobj(void *ctx, mclhd handle, uchar *ptr,
+                        ushort siz);
+
+/* 
+ *   Save a game - returns TRUE on failure.  We'll save the file to
+ *   'fname'.  'game_fname' is the name of the game file; if this is not
+ *   null, we'll save it to the saved game file so that the player can
+ *   later start the game by specifying only the saved game file to the
+ *   run-time.  'game_fname' can be null, in which case we'll omit the
+ *   game file information.  
+ */
+int fiosav(voccxdef *vctx, char *fname, char *game_fname);
+
+/*
+ *   fiorso() result codes 
+ */
+#define FIORSO_SUCCESS          0                                /* success */
+#define FIORSO_FILE_NOT_FOUND   1                         /* file not found */
+#define FIORSO_NOT_SAVE_FILE    2                  /* not a saved game file */
+#define FIORSO_BAD_FMT_VSN      3       /* incompatible file format version */
+#define FIORSO_BAD_GAME_VSN     4  /* file saved by another game or version */
+#define FIORSO_READ_ERROR       5            /* error reading from the file */
+#define FIORSO_NO_PARAM_FILE    6   /* no parameter file (for restore(nil)) */
+
+/* restore a game - returns TRUE on failure */
+int fiorso(voccxdef *vctx, char *fname);
+
+/*
+ *   Look in a saved game file to determine if it has information on which
+ *   GAM file created it.  If the GAM file information is available, this
+ *   routine returns true and stores the game file name in the given
+ *   buffer; if the information isn't available, we'll return false.  
+ */
+int fiorso_getgame(char *saved_file, char *buf, size_t buflen);
+
+/* encrypt/decrypt an object */
+void fioxor(uchar *p, uint siz, uint seed, uint inc);
+
+/* strings stored in binary game file for identification and validation */
+
+/* file header string */
+#define FIOFILHDR    "TADS2 bin\012\015\032"
+
+/* resource file header string */
+#define FIOFILHDRRSC "TADS2 rsc\012\015\032"
+
+/* CURRENT file format version string */
+#define FIOVSNHDR  "v2.2.0"
+
+/* other file format versions that can be READ by this version */
+#define FIOVSNHDR2 "v2.0.0"
+#define FIOVSNHDR3 "v2.0.1"
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/ler.cpp b/engines/glk/tads/tads2/ler.cpp
deleted file mode 100644
index d996977..0000000
--- a/engines/glk/tads/tads2/ler.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "ler.h"
-
-namespace Glk {
-namespace TADS {
-namespace TADS2 {
-
-#define TRDLOGERR_PREFIX "\n[An error has occurred within TADS: "
-
-int errcxdef::errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv) {
-	int    outlen = 0;
-	int    argi   = 0;
-	int    len;
-	char   buf[20];
-	const char  *p = nullptr;
-	char   fmtchar;
-
-	while (*fmt != '\0' && outbufl > 1) {
-		switch(*fmt) {
-		case '\\':
-			++fmt;
-			len = 1;
-			switch(*fmt) {
-			case '\0':
-				--fmt;
-				break;
-			case '\n':
-				p = "\n";
-				break;
-			case '\t':
-				p = "\t";
-				break;
-			default:
-				p = fmt;
-				break;
-			}
-			break;
-
-		case '%':
-			++fmt;
-			fmtchar = *fmt;
-			if (argi >= argc) fmtchar = 1;          // too many - ignore it
-			switch(fmtchar) {
-			case '\0':
-				--fmt;
-				len = 0;
-				break;
-
-			case '%':
-				p = "%";
-				len = 1;
-				break;
-
-			case 'd':
-				sprintf(buf, "%d", argv[argi].erraint);
-				len = strlen(buf);
-				p = buf;
-				break;
-
-			case 'u':
-				sprintf(buf, "%u", argv[argi].erraint);
-				len = strlen(buf);
-				p = buf;
-				break;
-
-			case 's':
-				p = argv[argi].errastr;
-				len = strlen(p);
-				break;
-
-			default:
-				p = "";
-				len = 0;
-				--argi;
-				break;
-			}
-			++argi;
-			break;
-
-		default:
-			p = fmt;
-			len = 1;
-			break;
-		}
-
-		/* copy output that was set up above */
-		if (len != 0) {
-			if (outbufl >= len) {
-				memcpy(outbuf, p, (size_t)len);
-				outbufl -= len;
-				outbuf += len;
-			} else if (outbufl > 1) {
-				memcpy(outbuf, p, (size_t)outbufl - 1);
-				outbufl = 1;
-			}
-			outlen += len;
-		}
-		++fmt;
-	}
-
-	// add a null terminator
-	if (outbufl != 0)
-		*outbuf++ = '\0';
-
-	// return the length
-	return outlen;
-}
-
-void errcxdef::errcxlog(void *ctx0, char *fac, int err, int argc, erradef *argv) {
-#ifdef TODO
-	errcxdef *ctx = (errcxdef *)ctx0;
-	char      buf[256];
-	char      msg[256];
-
-	// display the prefix message to the console and log file
-	sprintf(buf, TRDLOGERR_PREFIX, fac, err);
-	trdptf("%s", buf);
-	out_logfile_print(buf, false);
-
-	/* display the error message text to the console and log file */
-	errmsg(ctx, msg, (uint)sizeof(msg), err);
-	errfmt(buf, (int)sizeof(buf), msg, argc, argv);
-	trdptf("%s]\n", buf);
-	out_logfile_print(buf, false);
-	out_logfile_print("]", true);
-#endif
-}
-
-} // End of namespace TADS2
-} // End of namespace TADS
-} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/ler.h b/engines/glk/tads/tads2/ler.h
deleted file mode 100644
index 57e3e63..0000000
--- a/engines/glk/tads/tads2/ler.h
+++ /dev/null
@@ -1,617 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#ifndef GLK_TADS_TADS2_LER
-#define GLK_TADS_TADS2_LER
-
-#include "common/scummsys.h"
-#include "common/stream.h"
-#include "common/algorithm.h"
-
-namespace Glk {
-namespace TADS {
-namespace TADS2 {
-
-// maximum length of a facility identifier
-#define ERRFACMAX    6
-
-enum ErrorCode {
-	/* memory/cache manager errors */
-	ERR_NOMEM    =  1,                                     /* out of memory */
-	ERR_FSEEK    =  2,                             /* error seeking in file */
-	ERR_FREAD    =  3,                           /* error reading from file */
-	ERR_NOPAGE   =  4,                                /* no more page slots */
-	ERR_REALCK   =  5,          /* attempting to reallocate a locked object */
-	ERR_SWAPBIG  =  6,    /* swapfile limit reached - out of virtual memory */
-	ERR_FWRITE   =  7,                                /* error writing file */
-	ERR_SWAPPG   =  8,                    /* exceeded swap page table limit */
-	ERR_CLIUSE   =  9,     /* requested client object number already in use */
-	ERR_CLIFULL  = 10,                      /* client mapping table is full */
-	ERR_NOMEM1   = 11,   /* swapping/garbage collection failed to find enuf */
-	ERR_NOMEM2   = 12,            /* no memory to resize (expand) an object */
-	ERR_OPSWAP   = 13,                          /* unable to open swap file */
-	ERR_NOHDR    = 14,                     /* can't get a new object header */
-	ERR_NOLOAD   = 15,   /* mcm cannot find object to load (internal error) */
-	ERR_LCKFRE   = 16,     /* attempting to free a locked object (internal) */
-	ERR_INVOBJ   = 17,                                    /* invalid object */
-	ERR_BIGOBJ   = 18,  /* object too big - exceeds memory allocation limit */
-
-	/* lexical analysis errors */
-	ERR_INVTOK   = 100,                                    /* invalid token */
-	ERR_STREOF   = 101,                /* end of file while scanning string */
-	ERR_TRUNC    = 102,                      /* symbol too long - truncated */
-	ERR_NOLCLSY  = 103,                   /* no space in local symbol table */
-	ERR_PRPDIR   = 104,               /* invalid preprocessor (#) directive */
-	ERR_INCNOFN  = 105,                /* no filename in #include directive */
-	ERR_INCSYN   = 106,                          /* invalid #include syntax */
-	ERR_INCSEAR  = 107,                         /* can't find included file */
-	ERR_INCMTCH  = 108,       /* no matching delimiter in #include filename */
-	ERR_MANYSYM  = 109,                    /* out of space for symbol table */
-	ERR_LONGLIN  = 110,                                    /* line too long */
-	ERR_INCRPT   = 111,           /* header file already included - ignored */
-	ERR_PRAGMA   = 112,                         /* unknown pragma (ignored) */
-	ERR_BADPELSE = 113,                                 /* unexpected #else */
-	ERR_BADENDIF = 114,                                /* unexpected #endif */
-	ERR_BADELIF  = 115,                                 /* unexpected #elif */
-	ERR_MANYPIF  = 116,                             /* #if nesting too deep */
-	ERR_DEFREDEF = 117,                           /* symbol already defined */
-	ERR_PUNDEF   = 118,                        /* #undef symbol not defined */
-	ERR_NOENDIF  = 119,                                   /* missing #endif */
-	ERR_MACNEST  = 120,                         /* macros nested too deeply */
-	ERR_BADISDEF = 121,          /* invalid argument for defined() operator */
-	ERR_PIF_NA   = 122,                           /* #if is not implemented */
-	ERR_PELIF_NA = 123,                         /* #elif is not implemented */
-	ERR_P_ERROR  = 124,                              /* error directive: %s */
-	ERR_LONG_FILE_MACRO = 125,               /* __FILE__ expansion too long */
-	ERR_LONG_LINE_MACRO = 126,               /* __LINE__ expansion too long */
-
-	/* undo errors */
-	ERR_UNDOVF   = 200,                /* operation is too big for undo log */
-	ERR_NOUNDO   = 201,                         /* no more undo information */
-	ERR_ICUNDO   = 202,          /* incomplete undo (no previous savepoint) */
-
-	/* parser errors */
-	ERR_REQTOK   = 300,    /* expected token (arg 1) - found something else */
-	ERR_REQSYM   = 301,                                  /* expected symbol */
-	ERR_REQPRP   = 302,                         /* expected a property name */
-	ERR_REQOPN   = 303,                                 /* expected operand */
-	ERR_REQARG   = 304,       /* expected comma or closing paren (arg list) */
-	ERR_NONODE   = 305,                      /* no space for new parse node */
-	ERR_REQOBJ   = 306,                             /* epxected object name */
-	ERR_REQEXT   = 307,           /* redefining symbol as external function */
-	ERR_REQFCN   = 308,                    /* redefining symbol as function */
-	ERR_NOCLASS  = 309,  /* can't use CLASS with function/external function */
-	ERR_REQUNO   = 310,                          /* required unary operator */
-	ERR_REQBIN   = 311,                         /* required binary operator */
-	ERR_INVBIN   = 312,                          /* invalid binary operator */
-	ERR_INVASI   = 313,                               /* invalid assignment */
-	ERR_REQVAR   = 314,                           /* required variable name */
-	ERR_LCLSYN   = 315,        /* required comma or semicolon in local list */
-	ERR_REQRBR   = 316,   /* required right brace (eof before end of group) */
-	ERR_BADBRK   = 317,                          /* 'break' without 'while' */
-	ERR_BADCNT   = 318,                       /* 'continue' without 'while' */
-	ERR_BADELS   = 319,                              /* 'else' without 'if' */
-	ERR_WEQASI   = 320, /* warning: possible use of '=' where ':=' intended */
-	ERR_EOF      = 321,                           /* unexpected end of file */
-	ERR_SYNTAX   = 322,                             /* general syntax error */
-	ERR_INVOP    = 323,                             /* invalid operand type */
-	ERR_NOMEMLC  = 324,             /* no memory for new local symbol table */
-	ERR_NOMEMAR  = 325,              /* no memory for argument symbol table */
-	ERR_FREDEF   = 326,   /* redefining a function which is already defined */
-	ERR_NOSW     = 327,      /* 'case' or 'default' and not in switch block */
-	ERR_SWRQCN   = 328,           /* constant required in switch case value */
-	ERR_REQLBL   = 329,                        /* label required for 'goto' */
-	ERR_NOGOTO   = 330,                       /* 'goto' label never defined */
-	ERR_MANYSC   = 331,                 /* too many superclasses for object */
-	ERR_OREDEF   = 332,                      /* redefining symbol as object */
-	ERR_PREDEF   = 333,               /* property being redefined in object */
-	ERR_BADPVL   = 334,                           /* invalid property value */
-	ERR_BADVOC   = 335,                    /* bad vocabulary property value */
-	ERR_BADTPL   = 336,       /* bad template property value (need sstring) */
-	ERR_LONGTPL  = 337,             /* template base property name too long */
-	ERR_MANYTPL  = 338,     /* too many templates (internal compiler limit) */
-	ERR_BADCMPD  = 339,   /* bad value for compound word (sstring required) */
-	ERR_BADFMT   = 340,     /* bad value for format string (sstring needed) */
-	ERR_BADSYN   = 341,     /* invalid value for synonym (sstring required) */
-	ERR_UNDFSYM  = 342,                                 /* undefined symbol */
-	ERR_BADSPEC  = 343,                                 /* bad special word */
-	ERR_NOSELF   = 344,                 /* "self" not valid in this context */
-	ERR_STREND   = 345,            /* warning: possible unterminated string */
-	ERR_MODRPLX  = 346,    /* modify/replace not allowed with external func */
-	ERR_MODFCN   = 347,                 /* modify not allowed with function */
-	ERR_MODFWD   = 348,     /* modify/replace not allowed with forward func */
-	ERR_MODOBJ   = 349,    /* modify can only be used with a defined object */
-	ERR_RPLSPEC  = 350,                 /* warning - replacing specialWords */
-	ERR_SPECNIL  = 351,        /* nil only allowed with modify specialWords */
-	ERR_BADLCL   = 353,   /* 'local' statement must precede executable code */
-	ERR_IMPPROP  = 354,          /* implied verifier '%s' is not a property */
-	ERR_BADTPLF  = 355,                    /* invalid command template flag */
-	ERR_NOTPLFLG  = 356,      /* flags are not allowed with old file format */
-	ERR_AMBIGBIN  = 357,          /* warning: operator '%s' could be binary */
-	ERR_PIA       = 358,          /* warning: possibly incorrect assignment */
-	ERR_BADSPECEXPR = 359,                /* invalid speculation evaluation */
-
-	/* code generation errors */
-	ERR_OBJOVF   = 400,     /* object cannot grow any bigger - code too big */
-	ERR_NOLBL    = 401,                  /* no more temporary labels/fixups */
-	ERR_LBNOSET  = 402,                 /* (internal error) label never set */
-	ERR_INVLSTE  = 403,                /* invalid datatype for list element */
-	ERR_MANYDBG  = 404,  /* too many debugger line records (internal limit) */
-
-	/* vocabulary setup errors */
-	ERR_VOCINUS  = 450,            /* vocabulary being redefined for object */
-	ERR_VOCMNPG  = 451,          /* too many vocwdef pages (internal limit) */
-	ERR_VOCREVB  = 452,                             /* redefining same verb */
-	ERR_VOCREVB2 = 453,             /* redefining same verb - two arguments */
-
-	/* set-up errors */
-	ERR_LOCNOBJ  = 500,           /* location of object %s is not an object */
-	ERR_CNTNLST  = 501,                /* contents of object %s is not list */
-	ERR_SUPOVF   = 502,           /* overflow trying to build contents list */
-	ERR_RQOBJNF  = 503,                     /* required object %s not found */
-	ERR_WRNONF   = 504,                    /* warning - object %s not found */
-	ERR_MANYBIF  = 505,     /* too many built-in functions (internal error) */
-
-	/* fio errors */
-	ERR_OPWGAM    = 600,                 /* unable to open game for writing */
-	ERR_WRTGAM    = 601,                      /* error writing to game file */
-	ERR_FIOMSC    = 602,             /* too many sc's for writing in fiowrt */
-	ERR_UNDEFF    = 603,                              /* undefined function */
-	ERR_UNDEFO    = 604,                                /* undefined object */
-	ERR_UNDEF     = 605,                         /* undefined symbols found */
-	ERR_OPRGAM    = 606,                 /* unable to open game for reading */
-	ERR_RDGAM     = 607,                         /* error reading game file */
-	ERR_BADHDR    = 608,    /* file has invalid header - not TADS game file */
-	ERR_UNKRSC    = 609,              /* unknown resource type in .gam file */
-	ERR_UNKOTYP   = 610,             /* unknown object type in OBJ resource */
-	ERR_BADVSN    = 611,    /* file saved by different incompatible version */
-	ERR_LDGAM     = 612,                  /* error loading object on demand */
-	ERR_LDBIG     = 613, /* object too big for load region (prob. internal) */
-	ERR_UNXEXT    = 614,                /* did not expect external function */
-	ERR_WRTVSN    = 615,     /* compiler cannot write the requested version */
-	ERR_VNOCTAB   = 616,        /* format version cannot be used with -ctab */
-	ERR_BADHDRRSC = 617,         /* invalid resource file header in file %s */
-	ERR_RDRSC     = 618,               /* error reading resource file "xxx" */
-
-	/* character mapping errors */
-	ERR_CHRNOFILE = 700,           /* unable to load character mapping file */
-
-	/* user interrupt */
-	ERR_USRINT    = 990,      /* user requested cancel of current operation */
-
-	/* run-time errors */
-	ERR_STKOVF    = 1001,                                 /* stack overflow */
-	ERR_HPOVF     = 1002,                                  /* heap overflow */
-	ERR_REQNUM    = 1003,                         /* numeric value required */
-	ERR_STKUND    = 1004,                                /* stack underflow */
-	ERR_REQLOG    = 1005,                         /* logical value required */
-	ERR_INVCMP    = 1006,     /* invalid datatypes for magnitude comparison */
-	ERR_REQSTR    = 1007,                          /* string value required */
-	ERR_INVADD    = 1008,             /* invalid datatypes for '+' operator */
-	ERR_INVSUB    = 1009,      /* invalid datatypes for binary '-' operator */
-	ERR_REQVOB    = 1010,                           /* require object value */
-	ERR_REQVFN    = 1011,                      /* required function pointer */
-	ERR_REQVPR    = 1012,                 /* required property number value */
-
-	/* non-error conditions:  run-time EXIT, ABORT, ASKIO, ASKDO */
-	ERR_RUNEXIT    = 1013,                     /* 'exit' statement executed */
-	ERR_RUNABRT    = 1014,                    /* 'abort' statement executed */
-	ERR_RUNASKD    = 1015,                    /* 'askdo' statement executed */
-	ERR_RUNASKI    = 1016,           /* 'askio' executed; int arg 1 is prep */
-	ERR_RUNQUIT    = 1017,                               /* 'quit' executed */
-	ERR_RUNRESTART = 1018,                              /* 'reset' executed */
-	ERR_RUNEXITOBJ = 1019,                            /* 'exitobj' executed */
-
-	ERR_REQVLS     = 1020,                             /* list value required */
-	ERR_LOWINX     = 1021,              /* index value too low (must be >= 1) */
-	ERR_HIGHINX    = 1022,  /* index value too high (must be <= length(list)) */
-	ERR_INVTBIF    = 1023,              /* invalid type for built-in function */
-	ERR_INVVBIF    = 1024,             /* invalid value for built-in function */
-	ERR_BIFARGC    = 1025,           /* wrong number of arguments to built-in */
-	ERR_ARGC       = 1026,      /* wrong number of arguments to user function */
-	ERR_FUSEVAL    = 1027,     /* string/list not allowed for fuse/daemon arg */
-	ERR_BADSETF    = 1028,      /* internal error in setfuse/setdaemon/notify */
-	ERR_MANYFUS    = 1029,                                  /* too many fuses */
-	ERR_MANYDMN    = 1030,                                /* too many daemons */
-	ERR_MANYNFY    = 1031,                              /* too many notifiers */
-	ERR_NOFUSE     = 1032,                       /* fuse not found in remfuse */
-	ERR_NODMN      = 1033,                   /* daemon not found in remdaemon */
-	ERR_NONFY      = 1034,                  /* notifier not found in unnotify */
-	ERR_BADREMF    = 1035,    /* internal error in remfuse/remdaemon/unnotify */
-	ERR_DMDLOOP    = 1036,     /* load-on-demand loop: property not being set */
-	ERR_UNDFOBJ    = 1037,             /* undefined object in vocabulary tree */
-	ERR_BIFCSTR    = 1038,            /* c-string conversion overflows buffer */
-	ERR_INVOPC     = 1039,                                  /* invalid opcode */
-	ERR_RUNNOBJ    = 1040,     /* runtime error: property taken of non-object */
-	ERR_EXTLOAD    = 1041,           /* unable to load external function "%s" */
-	ERR_EXTRUN     = 1042,          /* error executing external function "%s" */
-	ERR_CIRCSYN    = 1043,                                /* circular synonym */
-	ERR_DIVZERO    = 1044,                                  /* divide by zero */
-	ERR_BADDEL     = 1045,      /* can only delete objects created with "new" */
-	ERR_BADNEWSC   = 1046,     /* superclass for "new" cannot be a new object */
-	ERR_VOCSTK     = 1047,              /* insufficient space in parser stack */
-	ERR_BADFILE    = 1048,                             /* invalid file handle */
-
-	ERR_RUNEXITPRECMD = 1049,                       /* exited from preCommand */
-
-	/* run-time parser errors */
-	ERR_PRS_SENT_UNK    = 1200,          /* sentence structure not recognized */
-	ERR_PRS_VERDO_FAIL  = 1201,                           /* verDoVerb failed */
-	ERR_PRS_VERIO_FAIL  = 1202,                           /* verIoVerb failed */
-	ERR_PRS_NO_VERDO    = 1203,             /* no verDoVerb for direct object */
-	ERR_PRS_NO_VERIO    = 1204,             /* no verIoVerb for direct object */
-	ERR_PRS_VAL_DO_FAIL = 1205,            /* direct object validation failed */
-	ERR_PRS_VAL_IO_FAIL = 1206,          /* indirect object validation failed */
-
-	/* compiler/runtime/debugger driver errors */
-	ERR_USAGE     = 1500,                                /* invalid usage */
-	ERR_OPNINP    = 1501,                     /* error opening input file */
-	ERR_NODBG     = 1502,              /* game not compiled for debugging */
-	ERR_ERRFIL    = 1503,            /* unable to open error capture file */
-	ERR_PRSCXSIZ  = 1504,            /* parse pool + local size too large */
-	ERR_STKSIZE   = 1505,                         /* stack size too large */
-	ERR_OPNSTRFIL = 1506,            /* error opening string capture file */
-	ERR_INVCMAP   = 1507,                   /* invalid character map file */
-
-	/* debugger errors */
-	ERR_BPSYM         = 2000,                 /* symbol not found for breakpoint */
-	ERR_BPPROP        = 2002,             /* breakpoint symbol is not a property */
-	ERR_BPFUNC        = 2003,             /* breakpoint symbol is not a function */
-	ERR_BPNOPRP       = 2004,              /* property is not defined for object */
-	ERR_BPPRPNC       = 2005,                            /* property is not code */
-	ERR_BPSET         = 2006,         /* breakpoint already set at this location */
-	ERR_BPNOTLN       = 2007,     /* breakpoint is not at a line (OPCLINE instr) */
-	ERR_MANYBP        = 2008,                            /* too many breakpoints */
-	ERR_BPNSET        = 2009,            /* breakpoint to be deleted was not set */
-	ERR_DBGMNSY       = 2010,  /* too many symbols in debug expression (int lim) */
-	ERR_NOSOURC       = 2011,                   /* unable to find source file %s */
-	ERR_WTCHLCL       = 2012,        /* illegal to assign to local in watch expr */
-	ERR_INACTFR       = 2013, /* inactive frame (expression value not available) */
-	ERR_MANYWX        = 2014,                      /* too many watch expressions */
-	ERR_WXNSET        = 2015,                              /* watchpoint not set */
-	ERR_EXTRTXT       = 2016,               /* extraneous text at end of command */
-	ERR_BPOBJ         = 2017,              /* breakpoint symbol is not an object */
-	ERR_DBGINACT      = 2018,                          /* debugger is not active */
-	ERR_BPINUSE       = 2019,                      /* breakpoint is already used */
-	ERR_RTBADSPECEXPR = 2020,                  /* invalid speculative expression */
-	ERR_NEEDLIN2      = 2021,     /* -ds2 information not found - must recompile */
-
-	/* usage error messages */
-	ERR_TCUS1         = 3000,                          /* first tc usage message */
-	ERR_TCUSL         = 3024,                           /* last tc usage message */
-	ERR_TCTGUS1       = 3030,                         /* first tc toggle message */
-	ERR_TCTGUSL       = 3032,
-	ERR_TCZUS1        = 3040,            /* first tc -Z suboptions usage message */
-	ERR_TCZUSL        = 3041,
-	ERR_TC1US1        = 3050,            /* first tc -1 suboptions usage message */
-	ERR_TC1USL        = 3058,
-	ERR_TCMUS1        = 3070,            /* first tc -m suboptions usage message */
-	ERR_TCMUSL        = 3076,
-	ERR_TCVUS1        = 3080,                /* first -v suboption usage message */
-	ERR_TCVUSL        = 3082,
-	ERR_TRUSPARM      = 3099,
-	ERR_TRUS1         = 3100,                          /* first tr usage message */
-	ERR_TRUSL         = 3117,
-	ERR_TRUSFT1       = 3118,                       /* first tr "footer" message */
-	ERR_TRUSFTL       = 3119,                        /* last tr "footer" message */
-	ERR_TRSUS1        = 3150,            /* first tr -s suboptions usage message */
-	ERR_TRSUSL        = 3157,
-	ERR_TDBUSPARM     = 3199,
-	ERR_TDBUS1        = 3200,                         /* first tdb usage message */
-	ERR_TDBUSL        = 3214,                          /* last tdb usage message */
-
-	/* TR 16-bit MSDOS-specific usage messages */
-	ERR_TRUS_DOS_1   = 3300,
-	ERR_TRUS_DOS_L   = 3300,
-
-	/* TR 32-bit MSDOS console mode usage messages */
-	ERR_TRUS_DOS32_1 = 3310,
-	ERR_TRUS_DOS32_L = 3312,
-
-	/* TADS/Graphic errors */
-	ERR_GNOFIL       = 4001,                      /* can't find graphics file %s */
-	ERR_GNORM        = 4002,                               /* can't find room %s */
-	ERR_GNOOBJ       = 4003,                    /* can't find hot spot object %s */
-	ERR_GNOICN       = 4004                         /* can't find icon object %s */
-};
-
-/*
- *   Special error flag - this is returned from execmd() when preparseCmd
- *   returns a command list.  This indicates to voc1cmd that it should try
- *   the command over again, using the words in the new list.
- */
-#define ERR_PREPRSCMDREDO  30000             /* preparseCmd returned a list */
-#define ERR_PREPRSCMDCAN   30001    /* preparseCmd returned 'nil' to cancel */
-
-union erradef {
-	int   erraint;		// integer argument
-	char *errastr;		// text string argument
-};
-
-struct errdef {
-	errdef *       errprv;               // previous error frame
-	int            errcode;              // error code of exception being handled
-	char           errfac[ERRFACMAX+1];  // facility of current error
-	erradef        erraav[10];           // parameters for error
-	int            erraac;               // count of parameters in argc
-//    jmp_buf        errbuf;               // jump buffer for current error frame
-};
-
-#define ERRBUFSIZ 512
-
-class TADS2;
-
-// seek location record for an error message by number
-struct errmfdef {
-	uint  errmfnum;   // error number
-	size_t errmfseek; // seek location of this message
-};
-
-class errcxdef {
-public:
-	errdef   *errcxptr;               // current error frame
-	void     *errcxlgc;               // context for error logging callback
-	int       errcxofs;               // offset in argument buffer
-	char      errcxbuf[ERRBUFSIZ];    // space for argument strings
-	Common::SeekableReadStream *errcxfp;  // message file, if one is being used
-	errmfdef *errcxseek;              // seek locations of messages in file
-	uint      errcxsksz;              // size of errcxseek array
-	size_t    errcxbase;              // offset in physical file of logical error file
-	TADS2 *   errcxappctx;            // host application context
-public:
-	/**
-	 *   Format an error message, sprintf-style, using arguments in an
-	 *   erradef array (which is passed to the error-logging callback).
-	 *   Returns the length of the output string, even if the actual
-	 *   output string was truncated because the outbuf was too short.
-	 *   (If called with outbufl == 0, nothing will be written out, but
-	 *   the size of the buffer needed, minus the terminating null byte,
-	 *   will be computed and returned.)
-	 */
-	static int errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv);
- public:
-	errcxdef() : errcxptr(nullptr), errcxlgc(nullptr), errcxofs(0),
-		errcxseek(nullptr), errcxsksz(0), errcxbase(0), errcxappctx(nullptr) {
-		Common::fill(&errcxbuf[0], &errcxbuf[ERRBUFSIZ], '\0');
-	}
-
-	/**
-	 * Error logging method
-	 */
-	void errcxlog(void *ctx0, char *fac, int err, int argc, erradef *argv);
-};
-
-// begin protected code
-#define ERRBEGIN(ctx) \
-  { \
-	errdef fr_; \
-	if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \
-	{ \
-	  fr_.errprv = (ctx)->errcxptr; \
-	  (ctx)->errcxptr = &fr_;
-
-// end protected code, begin error handler
-#define ERRCATCH(ctx, e) \
-	  assert(1==1 && (ctx)->errcxptr != fr_.errprv); \
-	  (ctx)->errcxptr = fr_.errprv; \
-	} \
-	else \
-	{ \
-	  assert(2==2 && (ctx)->errcxptr != fr_.errprv); \
-	  (e) = fr_.errcode; \
-	  (ctx)->errcxptr = fr_.errprv;
-
-// retrieve argument (int, string) in current error frame
-#define errargint(argnum) (fr_.erraav[argnum].erraint)
-#define errargstr(argnum) (fr_.erraav[argnum].errastr)
-
-
-#define ERREND(ctx) \
-	} \
-  }
-
-// end protected code, begin cleanup (no handling; just cleaning up)
-#define ERRCLEAN(ctx) \
-	  assert((ctx)->errcxptr != fr_.errprv); \
-	  (ctx)->errcxptr = fr_.errprv; \
-	} \
-	else \
-	{ \
-	  assert((ctx)->errcxptr != fr_.errprv); \
-	  (ctx)->errcxptr = fr_.errprv;
-
-#define ERRENDCLN(ctx) \
-	  errrse(ctx); \
-	} \
-  }
-
-
-
-// argument types for errors with arguments
-#define ERRTINT  erraint
-#define ERRTSTR  errastr
-
-// set argument count in error frame
-#define errargc(ctx,cnt) ((ctx)->errcxptr->erraac=(cnt))
-
-// enter string argument; returns pointer to argument used in errargv
-#ifdef ERR_NO_MACRO
-char *errstr(errcxdef *ctx, const char *str, int len);
-#else /* ERR_NO_MACRO */
-
-#define errstr(ctx,str,len) \
-  ((memcpy(&(ctx)->errcxbuf[(ctx)->errcxofs],str,(size_t)len), \
-   (ctx)->errcxofs += (len), \
-   (ctx)->errcxbuf[(ctx)->errcxofs++] = '\0'), \
-   &(ctx)->errcxbuf[(ctx)->errcxofs-(len)-1])
-
-#endif /* ERR_NO_MACRO */
-
-/* set argument in error frame argument vector */
-#define errargv(ctx,index,typ,arg) \
-  ((ctx)->errcxptr->erraav[index].typ=(arg))
-
-// signal an error with argument count already set
-#ifdef ERR_NO_MACRO
-void errsign(errcxdef *ctx, int e, char *facility);
-#else /* ERR_NO_MACRO */
-# ifdef DEBUG
-void errjmp(jmp_buf buf, int e);
-#  define errsign(ctx, e, fac) \
-   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
-	(ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
-	(ctx)->errcxofs=0, errjmp((ctx)->errcxptr->errbuf, e))
-# else /* DEBUG */
-#  define errsign(ctx, e, fac) \
-   (strncpy((ctx)->errcxptr->errfac, fac, ERRFACMAX),\
-	(ctx)->errcxptr->errfac[ERRFACMAX]='\0',\
-	(ctx)->errcxofs=0, longjmp((ctx)->errcxptr->errbuf, e))
-# endif /* DEBUG */
-#endif /* ERR_NO_MACRO */
-
-
-// signal an error with no arguments
-#ifdef ERR_NO_MACRO
-void errsigf(errcxdef *ctx, char *facility, int err);
-#else /* ERR_NO_MACRO */
-#define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac))
-#endif /* ERR_NO_MACRO */
-
-// signal an error with one argument
-#define errsigf1(ctx, fac, e, typ1, arg1) \
-  (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errsign(ctx,e,fac))
-
-// signal an error with two arguments
-#define errsigf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
-  (errargv(ctx,0,typ1,arg1), errargv(ctx,1,typ2,arg2), \
-   errargc(ctx,2), errsign(ctx,e,fac))
-
-// resignal the current error - only usable within exception handlers
-#ifdef ERR_NO_MACRO
-void errrse1(errcxdef *ctx, errdef *fr);
-# define errrse(ctx) errrse1(ctx, &fr_)
-#else /* ERR_NO_MACRO */
-
-// void errrse(errcxdef *ctx);
-# define errrse(ctx) \
-  (errargc(ctx, fr_.erraac),\
-   memcpy((ctx)->errcxptr->erraav, fr_.erraav, \
-	(size_t)(fr_.erraac*sizeof(erradef))),\
-   errsign(ctx, fr_.errcode, fr_.errfac))
-
-#endif /* ERR_NO_MACRO */
-
-/**
- *   For use in an error handler (ERRCATCH..ERREND) only: Copy the
- *   parameters from the error currently being handled to the enclosing
- *   frame.  This is useful when "keeping" an error being handled - i.e.,
- *   the arguments will continue to be used outside of the
- *   ERRCATCH..ERREND code.
- */
-#define errkeepargs(ctx) errcopyargs(ctx, &fr_)
-
-/**
- *   copy the parameters for an error from another frame into the current
- *   frame - this can be used when we want to be able to display an error
- *   that occurred in an inner frame within code that is protected by a
- *   new enclosing error frame
- */
-#define errcopyargs(ctx, fr) \
-   (errargc((ctx), (fr)->erraac), \
-	memcpy((ctx)->errcxptr->erraav, (fr)->erraav, \
-		   (size_t)((fr)->erraac*sizeof(erradef))))
-
-// log error that's been caught, using arguments already caught
-#define errclog(ctx) \
- ((*(ctx)->errcxlog)((ctx)->errcxlgc,fr_.errfac,fr_.errcode,\
-  fr_.erraac,fr_.erraav))
-
-// log an error that's been set up but not signalled yet
-#define errprelog(ctx, err) \
- ((*(ctx)->errcxlog)((ctx)->errcxlgc,(ctx)->errcxptr->errfac,\
-   err,(ctx)->errcxptr->erraac,\
-   (ctx)->errcxptr->erraav))
-
-// log an error (no signalling, just reporting)
-#ifdef ERR_NO_MACRO
-void errlogn(errcxdef *ctx, int err, char *facility);
-#else /* ERR_NO_MACRO */
-
-#define errlogn(ctx,err,fac) \
- ((ctx)->errcxofs=0,\
-  (*(ctx)->errcxlog)((ctx)->errcxlgc,fac,err,(ctx)->errcxptr->erraac,\
-  (ctx)->errcxptr->erraav))
-
-#endif /* ERR_NO_MACRO */
-
-// log an error with no arguments
-#ifdef ERR_NO_MACRO
-void errlogf(errcxdef *ctx, char *facility, int err);
-#else /* ERR_NO_MACRO */
-
-// void errlogf(errcxdef *ctx, char *facility, int err);
-#define errlogf(ctx,fac,err) (errargc(ctx,0),errlogn(ctx,err,fac))
-
-#endif /* ERR_NO_MACRO */
-
-// log an error with one argument
-#define errlogf1(ctx, fac, e, typ1, arg1) \
- (errargv(ctx,0,typ1,arg1),errargc(ctx,1),errlogn(ctx,e,fac))
-
-// log an error with two arguments
-#define errlogf2(ctx, fac, e, typ1, arg1, typ2, arg2) \
- (errargv(ctx,0,typ1,arg1),errargv(ctx,1,typ2,arg2),\
-  errargc(ctx,2),errlogn(ctx,e,fac))
-
-/**
- * For compatility with old facility-free mechanism, signal with facility "TADS"
- */
-#define errsig(ctx, err) errsigf(ctx, "TADS", err)
-#define errsig1(c, e, t, a) errsigf1(c,"TADS",e,t,a)
-//#define errsig2(c, e, t1, a1, t2, a2) errsigf2(c,"TADS",e,t1,a1,t2,a2)
-#define errlog(c, e) errlogf(c, "TADS", e)
-#define errlog1(c, e, t, a) errlogf1(c,"TADS",e,t,a)
-#define errlog2(c, e, t1, a1, t2, a2) errlogf2(c,"TADS",e,t1,a1,t2,a2)
-
-#define errsig2(c, e, t1, a1, t2, a2) error("Error occurred")
-
-// get the text of an error
-void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err);
-
-// initialize error subsystem, opening error message file if necessary
-void errini(errcxdef *ctx, Common::SeekableReadStream *fp);
-
-// allocate and initialize error context, free error context
-errcxdef *lerini();
-void      lerfre(errcxdef *ctx);
-
-// error message structure - number + text
-struct errmdef {
-	uint   errmerr;		// error number
-	char  *errmtxt;     // text of error message
-};
-
-} // End of namespace TADS2
-} // End of namespace TADS
-} // End of namespace Glk
-
-#endif
diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h
new file mode 100644
index 0000000..605f072
--- /dev/null
+++ b/engines/glk/tads/tads2/lib.h
@@ -0,0 +1,164 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_LIB
+#define GLK_TADS_TADS2_LIB
+
+#include "common/scummsys.h"
+#include "common/algorithm.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* short-hand for various types */
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned long  ulong;
+
+typedef byte  ub1;
+typedef signed char    sb1;
+typedef char           b1;
+typedef unsigned int   ub2;
+typedef signed int     sb2;
+typedef int            b2;
+typedef unsigned long  ub4;
+typedef signed long    sb4;
+typedef long           b4;
+typedef int            eword;
+typedef int32          int32_t;
+typedef uint32         uint32_t;
+
+
+/* maximum/minimum portable values for various types */
+#define UB4MAXVAL  0xffffffffUL
+#define UB2MAXVAL  0xffffU
+#define UB1MAXVAL  0xffU
+#define SB4MAXVAL  0x7fffffffL
+#define SB2MAXVAL  0x7fff
+#define SB1MAXVAL  0x7f
+#define SB4MINVAL  (-(0x7fffffff)-1)
+#define SB2MINVAL  (-(0x7fff)-1)
+#define SB1MINVAL  (-(0x7f)-1)
+
+/* clear a struture */
+#define CLRSTRUCT(x) memset(&(x), 0, (size_t)sizeof(x))
+#define CPSTRUCT(dst,src) memcpy(&(dst), &(src), (size_t)sizeof(dst))
+
+/* TRUE and FALSE */
+#define TRUE true
+#define FALSE false
+
+
+/* bitwise operations */
+#define bit(va, bt) ((va) & (bt))
+#define bis(va, bt) ((va) |= (bt))
+#define bic(va, bt) ((va) &= ~(bt))
+
+/*
+ *   noreg/NOREG - use for variables changed in error-protected code that
+ *   are used in error handling code.  Use 'noreg' on the declaration like
+ *   a storage class qualifier.  Use 'NOREG' as an argument call, passing
+ *   the addresses of all variables declared noreg.  For non-ANSI
+ *   compilers, a routine osnoreg(/o_ void *arg0, ... _o/) must be
+ *   defined.
+ */
+#ifdef OSANSI
+# define noreg volatile
+# define NOREG(arglist)
+#else /* OSANSI */
+# define noreg
+# define NOREG(arglist) osnoreg arglist ;
+#endif /* OSANSI */
+
+/* 
+ *   Linting directives.  You can define these before including this file
+ *   if you have a fussy compiler.  
+ */
+#ifdef LINT
+# ifndef NOTREACHED
+#  define NOTREACHED return
+# endif
+# ifndef NOTREACHEDV
+#  define NOTREACHEDV(t) return((t)0)
+# endif
+# ifndef VARUSED
+#  define VARUSED(v) varused(v)
+# endif
+void varused();
+#else /* LINT */
+# ifndef NOTREACHED
+#  define NOTREACHED
+# endif
+# ifndef NOTREACHEDV
+#  define NOTREACHEDV(t)
+# endif
+# ifndef VARUSED
+#  define VARUSED(v)
+# endif
+#endif /* LINT */
+
+/* conditionally compile code if debugging is enabled */
+#ifdef DEBUG
+# define IF_DEBUG(x) x
+#else /* DEBUG */
+# define IF_DEBUG(x)
+#endif /* DEBUG */
+
+#ifndef offsetof
+# define offsetof(s_name, m_name) (size_t)&(((s_name *)0)->m_name)
+#endif /* offsetof */
+
+/*
+ *   Define our own version of isspace(), so that we don't try to interpret
+ *   anything outside of the normal ASCII set as spaces. 
+ */
+#define t_isspace(c) \
+    (((unsigned char)(c)) <= 127 && isspace((unsigned char)(c)))
+
+
+/* round a size to worst-case alignment boundary */
+#define osrndsz(s) (((s)+3) & ~3)
+
+/* round a pointer to worst-case alignment boundary */
+#define osrndpt(p) ((uchar *)((((unsigned long)(p)) + 3) & ~3))
+
+/* read unaligned portable unsigned 2-byte value, returning int */
+#define osrp2(p) READ_LE_UINT16(p)
+
+/* read unaligned portable signed 2-byte value, returning int */
+#define osrp2s(p) READ_LE_INT16(p)
+
+/* write int to unaligned portable 2-byte value */
+#define oswp2(p, i) WRITE_LE_UINT16(p, i)
+#define oswp2s(p, i) WRITE_LE_INT16(p, i)
+
+/* read unaligned portable 4-byte value, returning unsigned long */
+#define osrp4(p) READ_LE_UINT32(p)
+#define osrp4s(p) READ_LE_INT32(p)
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/line_source.h b/engines/glk/tads/tads2/line_source.h
new file mode 100644
index 0000000..a7e5454
--- /dev/null
+++ b/engines/glk/tads/tads2/line_source.h
@@ -0,0 +1,129 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* Line source definitions
+ *
+ * A line source is a mechanism for reading source text.  The tokenizer
+ * reads its input from a line source.  This is the basic class
+ * definition; individual line sources will define the functions and
+ * class data needed.
+ */
+
+#ifndef GLK_TADS_TADS2_LINE_SOURCE
+#define GLK_TADS_TADS2_LINE_SOURCE
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/object.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/**
+ * Line source superclass structure
+ */
+struct lindef {
+    int   (*lingetp)(lindef *lin);                         /* get next line */
+    void  (*linclsp)(lindef *lin);                     /* close line source */
+    void  (*linppos)(lindef *lin, char *buf, uint buflen);
+            /* write printable rep of position to buf (for error reporting) */
+    void  (*linglop)(lindef *lin, uchar *buf);
+                       /* generate a line record for an OPCLINE instruction */
+    int   (*linwrtp)(lindef *lin, osfildef *fp);
+            /* write line source information to binary file; TRUE ==> error */
+    void  (*lincmpp)(lindef *lin, uchar *buf);
+          /* give location of compiled code for current line to line source */
+    void  (*linactp)(lindef *lin);                      /* activate for dbg */
+    void  (*lindisp)(lindef *lin);                           /* disactivate */
+    void  (*lintellp)(lindef *lin, uchar *pos);             /* get position */
+    void  (*linseekp)(lindef *lin, uchar *pos);                     /* seek */
+    int   (*linreadp)(lindef *lin, uchar *buf, uint siz);          /* fread */
+    void  (*linpaddp)(lindef *lin, uchar *pos, long delta);
+                                       /* add an offset to a position value */
+    int   (*linqtopp)(lindef *lin, uchar *pos);                  /* at top? */
+    int   (*lingetsp)(lindef *lin, uchar *buf, uint siz);       /* get line */
+    void  (*linnamp)(lindef *lin, char *buf);            /* get source name */
+    void  (*linfindp)(lindef *lin, char *buf, objnum *objp,
+                      uint *ofsp);              /* find nearest line record */
+    void  (*lingotop)(lindef *lin, int where);               /* seek global */
+    long  (*linofsp)(lindef *lin);            /* byte offset in line source */
+    void  (*linrenp)(lindef *lin, objnum oldnum, objnum newnum);
+                                       /* renumber an object (for "modify") */
+    void  (*lindelp)(lindef *lin, objnum objn);
+                                        /* delete an object (for "replace") */
+    ulong (*linlnump)(lindef *lin);          /* get the current line number */
+#define  LINGOTOP   OSFSK_SET                   /* go to top of line source */
+#define  LINGOEND   OSFSK_END                   /* go to end of line source */
+    lindef *linpar;                        /* parent of current line source */
+    lindef *linnxt;                       /* next line in line source chain */
+    int     linid;           /* serial number of line source (for debugger) */
+    char   *linbuf;                              /* pointer to current line */
+    ushort  linflg;                                                /* flags */
+#define  LINFEOF   0x01                    /* line source is at end of file */
+#define  LINFMORE  0x02             /* there's more to the line than linlen */
+#define  LINFDBG   0x04          /* debug record already generated for line */
+#define  LINFNOINC 0x08        /* ignore # directives from this line source */
+#define  LINFCMODE 0x10                  /* line source is parsed in C-mode */
+    ushort  linlen;                                   /* length of the line */
+    ushort  linlln;           /* length of line record generated by lingloc */
+};
+
+/**
+ *   Maximum allowed value for linlln, in bytes.  This allows subsystems
+ *   that need to maintain local copies of seek locations to know how big
+ *   an area to allocate for them.  
+ */
+#define LINLLNMAX   20
+
+/* macros to cover calls to functions */
+#define linget(lin) ((*((lindef *)(lin))->lingetp)((lindef *)(lin)))
+#define lincls(lin) ((*((lindef *)(lin))->linclsp)((lindef *)(lin)))
+#define linppos(lin, buf, buflen) \
+ ((*((lindef *)(lin))->linppos)((lindef *)(lin), buf, buflen))
+#define linglop(lin, buf) ((*((lindef *)(lin))->linglop)(lin, buf))
+#define linwrt(lin, fp) ((*((lindef *)(lin))->linwrtp)(lin, fp))
+#define lincmpinf(lin, buf) ((*((lindef *)(lin))->lincmpp)(lin, buf))
+#define linactiv(lin) ((*((lindef *)(lin))->linactp)(lin))
+#define lindisact(lin) ((*((lindef *)(lin))->lindisp)(lin))
+#define lintell(lin, pos) ((*((lindef *)(lin))->lintellp)(lin, pos))
+#define linseek(lin, pos) ((*((lindef *)(lin))->linseekp)(lin, pos))
+#define linread(lin, buf, siz) ((*((lindef *)(lin))->linreadp)(lin, buf, siz))
+#define linpadd(lin, pos, delta) \
+  ((*((lindef *)(lin))->linpaddp)(lin, pos, delta))
+#define linqtop(lin, pos) ((*((lindef *)(lin))->linqtopp)(lin, pos))
+#define lingets(lin, buf, siz) ((*((lindef *)(lin))->lingetsp)(lin, buf, siz))
+#define linnam(lin, buf) ((*((lindef *)(lin))->linnamp)(lin, buf))
+#define linlnum(lin) ((*((lindef *)(lin))->linlnump)(lin))
+#define linfind(lin, buf, objp, ofsp) \
+  ((*((lindef *)(lin))->linfindp)(lin, buf, objp, ofsp))
+#define lingoto(lin, where) ((*((lindef *)(lin))->lingotop)(lin, where))
+#define linofs(lin) ((*((lindef *)(lin))->linofsp)(lin))
+#define linrenum(lin, oldnum, newnum) \
+  ((*((lindef *)(lin))->linrenp)(lin, oldnum, newnum))
+#define lindelnum(lin, objn) ((*((lindef *)(lin))->lindelp)(lin, objn))
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/memory_cache.cpp b/engines/glk/tads/tads2/memory_cache.cpp
new file mode 100644
index 0000000..3bab3b0
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache.cpp
@@ -0,0 +1,31 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/memory_cache.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache.h b/engines/glk/tads/tads2/memory_cache.h
new file mode 100644
index 0000000..ec0383b
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache.h
@@ -0,0 +1,403 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * Memory cache manager
+ */
+
+#ifndef GLK_TADS_TADS2_MEMORY_CACHE
+#define GLK_TADS_TADS2_MEMORY_CACHE
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/memory_cache_loader.h"
+#include "glk/tads/tads2/memory_cache_swap.h"
+#include "glk/tads/osfrobtads.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* if the os layer doesn't want long mcm macros, we don't either */
+#ifdef OS_MCM_NO_MACRO
+# define MCM_NO_MACRO
+#endif
+
+/*
+ *   mcmon - cache object number.  Each object in the cache is referenced
+ *   by an object number, which is a number of this type.
+ */
+typedef ushort mcmon;
+
+/* invalid object number - used to indicate N/A or no object */
+#define MCMONINV ((mcmon)~0)
+
+/* invalid page number */
+#define MCMPINV ((uint)~0)
+
+union mcmodefloc {
+	mcsseg  mcmolocs;                            /* swap segment handle */
+	mclhd   mcmolocl;                        /* load file object handle */
+};
+
+/*
+ *   mcmodef - cache object definition.  Each cache object is managed by
+ *   this structure.  Pointers for a doubly-linked list are provided;
+ *   these are used to maintain a recent-use list for objects in memory,
+ *   and to maintain a free list for cache object entries not in use.  A
+ *   set of flag bits is provided, as is the size of the object and its
+ *   location (which is a memory pointer for objects in memory, a swap
+ *   file handle for swapped-out objects, or a load-file handle for
+ *   objects that are unloaded). 
+ */
+struct mcmodef {
+    uchar  *mcmoptr;               /* memory pointer (if object is present) */
+	mcmodefloc mcmoloc;                       /* object's external location */
+#define  mcmoswh  mcmoloc.mcmolocs
+#define  mcmoldh  mcmoloc.mcmolocl
+    mcmon   mcmonxt;                            /* next object in this list */
+    mcmon   mcmoprv;                        /* previous object in this list */
+    ushort  mcmoflg;                         /* flags for this cache object */
+#define  MCMOFDIRTY  0x01                     /* object has been written */
+#define  MCMOFNODISC 0x02       /* not in load file (can't be discarded) */
+#define  MCMOFLOCK   0x04                            /* object is locked */
+#define  MCMOFPRES   0x08                 /* object is present in memory */
+#define  MCMOFLRU    0x10                      /* object is in LRU chain */
+#define  MCMOFPAGE   0x20              /* object is a cache manager page */
+#define  MCMOFNOSWAP 0x40                /* object cannot be swapped out */
+#define  MCMOFFREE   0x80      /* entry refers to a free block of memory */
+#define  MCMOFREVRT 0x100           /* call revert callback upon loading */
+    uchar   mcmolcnt;                                         /* lock count */
+    ushort  mcmosiz;                                  /* size of the object */
+};
+
+/* heap header - allocate one of these in each heap */
+struct mcmhdef {
+    mcmhdef *mcmhnxt;                            /* next heap in this chain */
+};
+
+/* GLOBAL cache manager context:  tracks cache manager state */
+struct mcmcx1def {
+    mcmodef  **mcmcxtab;                        /* page table for the cache */
+    errcxdef  *mcmcxerr;                          /* error handling context */
+    mcmhdef   *mcmcxhpch;                             /* heap chain pointer */
+    mcscxdef   mcmcxswc;                            /* swap manager context */
+    mclcxdef   mcmcxldc;                                  /* loader context */
+    ulong      mcmcxmax; /* maximum amount of actual heap we can ever alloc */
+    mcmon      mcmcxlru;      /* least recently used object still in memory */
+    mcmon      mcmcxmru;                       /* most recently used object */
+    mcmon      mcmcxfre;                               /* head of free list */
+    mcmon      mcmcxunu;                             /* head of unused list */
+    ushort     mcmcxpage;                      /* last page table slot used */
+    ushort     mcmcxpgmx;        /* maximum number of pages we can allocate */
+    void     (*mcmcxcsw)(mcmcx1def *, mcmon, mcsseg, mcsseg);
+                         /* change swap handle in object to new swap handle */
+};
+
+/* CLIENT cache manager context: used by client to request mcm services */
+struct mcmcxdef {
+    mcmcx1def *mcmcxgl;                     /* global cache manager context */
+    uint       mcmcxflg;                                           /* flags */
+    uint       mcmcxmsz;                   /* maximum size of mapping table */
+    void     (*mcmcxldf)(void *ctx, mclhd handle, uchar *ptr,
+                         ushort siz);           /* callback to load objects */
+    void      *mcmcxldc;                       /* context for load callback */
+    void     (*mcmcxrvf)(void *ctx, mcmon objn);           /* revert object */
+    void      *mcmcxrvc;                     /* context for revert callback */
+    mcmon     *mcmcxmtb[1];                                /* mapping table */
+};
+
+/* context flags */
+#define MCMCXF_NO_PRP_DEL   0x0001  /* PRPFDEL is invalid in this game file */
+
+/* convert from a client object number to a global object number */
+/* mcmon mcmc2g(mcmcxdef *ctx, mcmon objn); */
+#define mcmc2g(ctx, objn) ((ctx)->mcmcxmtb[(objn)>>8][(objn)&255])
+
+/*
+ *   FREE LIST: this is a list, headed by context->mcmcxfre and chained
+ *   forward and back by mcmonxt and mcmoprv, consisting of free memory
+ *   blocks.  These refer to blocks in the heap that are not used by any
+ *   client objects. 
+ */
+/*
+ *   UNUSED LIST: this is a list, headed by context->mcmcxunu and chained
+ *   forward by mcmonxt (not back, because it's never necessary to take
+ *   anything out of the list except at the head, nor to search the list
+ *   backwards), of unused cache object entries.  These entries are not
+ *   associated with any client object or with any heap memory.  This list
+ *   is used to get a new cache object header, and deleted cache objects
+ *   are placed onto this list.  
+ */
+/*
+ *   LRU LIST: this is a list of in-memory blocks in ascending order of
+ *   recency of use by the client.  Each time a client unlocks a block,
+ *   the block is moved to the most recent position in the list (the end
+ *   of the list).  To make it fast to add a new object, we keep a pointer
+ *   to the end of the list as well as to the beginning.  The start of the
+ *   list is at context->mcmcxlru, and is the least recently unlocked
+ *   block still in memory.  The end of the list is at context->mcmcxmru,
+ *   and is the most recently unlocked block.  
+ */
+
+/*
+ *   initialize the cache manager, returning a context for cache manager
+ *   operations; a null pointer is returned if insufficient heap memory is
+ *   available for initialization.  The 'max' argument specifies the
+ *   maximum amount of actual low-level heap memory that the cache manager
+ *   can ever allocate on behalf of this context (of course, it can
+ *   overcommit the heap through swapping).  If 'max' is less than the
+ *   size of a single heap allocation, it is adjusted upwards to that
+ *   minimum.  
+ */
+mcmcx1def *mcmini(ulong max, uint pages, ulong swapsize,
+                  osfildef *swapfp, char *swapfilename, errcxdef *errctx);
+
+/* terminate the cache manager - frees the structure and all cache memory */
+void mcmterm(mcmcx1def *ctx);
+
+/* allocate a client context */
+mcmcxdef *mcmcini(mcmcx1def *globalctx, uint pages,
+                  void (*loadfn)(void *, mclhd, uchar *, ushort),
+                  void *loadctx,
+                  void (*revertfn)(void *, mcmon), void *revertctx);
+
+/* terminate a client context - frees the structure memory */
+void mcmcterm(mcmcxdef *ctx);
+
+
+/*
+ *   Lock a cache object, bringing it into memory if necessary.  Returns
+ *   a pointer to the memory containing the object.  A null pointer is
+ *   returned in case of error.  The object remains fixed in memory at the
+ *   returned location until unlocked.  Locks are stacked; that is, if
+ *   an object is locked twice in succession, it needs to be unlocked
+ *   twice in succession before it is actually unlocked.
+ */
+#ifdef MCM_NO_MACRO
+uchar *mcmlck(mcmcxdef *ctx, mcmon objnum);
+#else /* MCM_NO_MACRO */
+
+/* uchar *mcmlck(mcmcxdef *ctx, mcmon objnum); */
+#define mcmlck(ctx,num) \
+ ((mcmobje(ctx,num)->mcmoflg & MCMOFPRES ) ? \
+ ((mcmobje(ctx,num)->mcmoflg|=MCMOFLOCK), \
+  ++(mcmobje(ctx,num)->mcmolcnt), mcmobje(ctx,num)->mcmoptr) \
+ : mcmload(ctx,num))
+
+#endif /* MCM_NO_MACRO */
+
+/*
+ *   Unlock a cache object, allowing it to be moved and swapped.
+ *   Unlocking an object moves it to the end (i.e., most recently used)
+ *   position on the LRU chain, making it the least favorable to swap out
+ *   or discard.  This happens at unlock time (rather than lock time)
+ *   because presumably the client has been using the object the entire
+ *   time it was locked. For this reason, and to keep memory unfragmented
+ *   as much as possible, objects should not be kept locked except when
+ *   actually in use.  Note that locks nest; if an object is locked three
+ *   times without an intervening unlock, it must be unlocked three times
+ *   in a row.  An object can be unlocked even if it's not locked; doing
+ *   so has no effect.
+ */
+#ifdef MCM_NO_MACRO
+void mcmunlck(mcmcxdef *ctx, mcmon objnum);
+#else /* MCM_NO_MACRO */
+
+/* void mcmunlck(mcmcxdef *ctx, mcmon objnum); */
+#define mcmunlck(ctx,obj) \
+ ((mcmobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \
+  (--(mcmobje(ctx,obj)->mcmolcnt) ? (void)0 : \
+  ((mcmobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), \
+    mcmuse((ctx)->mcmcxgl,mcmc2g(ctx,obj)))) : (void)0)
+
+#endif /* MCM_NO_MACRO */
+
+/*
+ *   Allocate a new cache object.  The new object is locked upon return.
+ *   A pointer to the memory for the new object is returned, and
+ *   the object number is returned at *nump.  A null pointer is returned
+ *   if the object cannot be allocated.
+ */
+/* uchar *mcmalo(mcmcxdef *ctx, ushort siz, mcmon *nump); */
+#define mcmalo(ctx, siz, nump) mcmalo0(ctx, siz, nump, MCMONINV, FALSE)
+
+/*
+ *   Reserve space for an object, giving it a particular client object
+ *   number.  This doesn't actually allocate any space for the object, but
+ *   just sets it up so that it can be loaded by the client when it's
+ *   needed.  
+ */
+void mcmrsrv(mcmcxdef *ctx, ushort siz, mcmon clinum, mclhd loadhd);
+
+/*
+ *   Allocate a new cache object, and associate it with a particular
+ *   client object number.  An error is signalled if the client object
+ *   number is already in use.
+ */
+/* uchar *mcmalonum(mcmcxdef *ctx, ushort siz, mcmon num); */
+#define mcmalonum(ctx, siz, num) mcmalo0(ctx, siz, (mcmon *)0, num, FALSE)
+
+/*
+ *   Reallocate an existing object.  The object's size is increased
+ *   or reduced according to newsize.  The object is locked if it is
+ *   not already, and the address of the object's memory is returned.
+ *   Note that the object can move when reallocated, even if it was
+ *   locked before the call.
+ */
+uchar *mcmrealo(mcmcxdef *ctx, mcmon objnum, ushort newsize);
+
+/*
+ *   Touch a cache object (rendering it dirty).  When an object is
+ *   written, the client must touch it to ensure that the version in
+ *   memory is not discarded.  The cache manager attempts to optimize
+ *   activity by not writing objects that can be reconstructed from the
+ *   load or swap file.  Touching the object informs the cache manager
+ *   that the object is different from any version it has in a swap or
+ *   load file.  
+ */
+/* void mcmtch(mcmcxdef *ctx, mcmon objnum); */
+#define mcmtch(ctx,obj) \
+  (mcmobje(ctx,obj)->mcmoflg |= MCMOFDIRTY)
+/* was: (mcmobje(ctx,obj)->mcmoflg |= (MCMOFDIRTY | MCMOFNODISC)) */
+
+/* get size of a cache manager object - object need not be locked */
+/* ushort mcmobjsiz(mcmcxdef *ctx, mcmon objn); */
+#define mcmobjsiz(ctx, objn) (mcmobje(ctx, objn)->mcmosiz)
+
+/* determine if object has ever been touched */
+/* int mcmobjdirty(mcmcxdef *ctx, mcmon objn); */
+#define mcmobjdirty(ctx, objn) \
+ (mcmobje(ctx, objn)->mcmoflg & (MCMOFDIRTY | MCMOFNODISC))
+
+/* get object's memory pointer - object must be locked for valid result */
+/* uchar *mcmobjptr(mcmcxdef *ctx, mcmon objn); */
+#define mcmobjptr(ctx, objn) (mcmobje(ctx, objn)->mcmoptr)
+
+/*
+ *   Free an object.  The memory occupied by the object is discarded, and
+ *   the object may no longer be referenced.  
+ */
+void mcmfre(mcmcxdef *ctx, mcmon obj);
+
+/*
+ *   "Revert" an object - convert it back to original state.  This
+ *   routine just invokes a client callback to do the actual reversion
+ *   work.  The callback is called immediately if the object is already
+ *   present in memory, but is deferred until the object is loaded/swapped
+ *   in if the object is not in memory. 
+ */
+/* void mcmrevert(mcmcxdef *ctx, mcmon objn); */
+#define mcmrevert(ctx, objn) \
+ ((mcmobje(ctx, objn)->mcmoflg & MCMOFPRES) ? \
+  ((*(ctx)->mcmcxrvf)((ctx)->mcmcxrvc, objn), DISCARD 0) \
+  : DISCARD (mcmobje(ctx, objn)->mcmoflg |= MCMOFREVRT))
+
+/* get current size of object cache */
+ulong mcmcsiz(mcmcxdef *ctx);
+
+/* change an object's swap handle (used by swapper) */
+/* void mcmcsw(mcmcx1def *ctx, ushort objn, mcsseg swapn, mcsseg oldswn); */
+#define mcmcsw(ctx, objn, swapn, oldswapn) \
+   ((*(ctx)->mcmcxcsw)(ctx, objn, swapn, oldswapn))
+
+/* ------------------------------- PRIVATE ------------------------------- */
+
+/* Unlock an object by its global handle */
+#ifdef MCM_NO_MACRO
+void mcmgunlck(mcmcx1def *ctx, mcmon objnum);
+#else /* MCM_NO_MACRO */
+
+/* void mcmgunlck(mcmcx1def *ctx, mcmon objnum); */
+#define mcmgunlck(ctx,obj) \
+ ((mcmgobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \
+  (--(mcmgobje(ctx,obj)->mcmolcnt) ? (void)0 : \
+    ((mcmgobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), mcmuse(ctx,obj))) : \
+  (void)0)
+
+#endif /* MCM_NO_MACRO */
+
+/* real memory allocator; clients use cover macros */
+uchar *mcmalo0(mcmcxdef *ctx, ushort siz, mcmon *nump, mcmon clinum,
+               int noclitrans);
+
+/* free an object by global object number */
+void mcmgfre(mcmcx1def *ctx, mcmon obj);
+
+/* "use" an object (move to most-recent position in LRU chain) */
+void mcmuse(mcmcx1def *ctx, mcmon n);
+
+/*
+ *   Load or swap in a cache object which is currently unloaded, locking
+ *   it before returning.  Returns a pointer to the memory containing
+ *   the object, or a null pointer in case of error.  The object
+ *   remains fixed in memory at the returned location until unlocked.
+ */
+uchar *mcmload(mcmcxdef *ctx, mcmon objnum);
+
+/*
+ *   Size of each chunk of memory we'll request from the heap manager.
+ *   To cut down on wasted memory from the heap manager, we'll always make
+ *   our requests in this large size, then sub-allocate out of these
+ *   chunks. 
+ */
+#define MCMCHUNK  32768
+
+/*
+ *   number of cache entries in a page - make this a power of 2 to keep
+ *   the arithmetic to find a cache object entry simple 
+ */
+#define MCMPAGECNT 256
+
+/*
+ *   size of a page, in bytes
+ */
+#define MCMPAGESIZE (MCMPAGECNT * sizeof(mcmodef))
+
+/*
+ *   When allocating memory, and we find a free block satisfying the
+ *   request, we will split the free block if doing so would result in
+ *   enough space in the second block.  MCMSPLIT specifies the minimum
+ *   size left over that will allow a split to occur.
+ */
+#define MCMSPLIT 64
+
+/* get an object cache entyr given a GLOBAL object number */
+#define mcmgobje(ctx,num) (&((ctx)->mcmcxtab[(num)>>8][(num)&255]))
+
+/* get an object cache entry given a CLIENT object number */
+/* mcmodef *mcmobje(mcmcxdef *ctx, mcmon objnum) */
+#define mcmobje(ctx,num) mcmgobje((ctx)->mcmcxgl,mcmc2g(ctx,num))
+
+/* allocate a block that will be locked for its entire lifetime */
+void *mcmptralo(mcmcxdef *ctx, ushort siz);
+
+/* free a block allocated with mcmptralo */
+void mcmptrfre(mcmcxdef *ctx, void *block);
+
+/* change an object's swap handle */
+void mcmcswf(mcmcx1def *ctx, mcmon objn, mcsseg swapn, mcsseg oldswapn);
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/memory_cache_heap.cpp b/engines/glk/tads/tads2/memory_cache_heap.cpp
new file mode 100644
index 0000000..fae3c5e
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_heap.cpp
@@ -0,0 +1,51 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/error.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* global to keep track of all allocations */
+IF_DEBUG(ulong mchtotmem;)
+
+uchar *mchalo(errcxdef *ctx, size_t siz, char *comment) {
+    uchar *ret;
+
+    VARUSED(comment);
+    IF_DEBUG(mchtotmem += siz;)
+
+    ret = (uchar *)osmalloc(siz);
+    if (ret)
+        return(ret);
+    else {
+        errsig(ctx, ERR_NOMEM);
+        NOTREACHEDV(uchar *);
+        return 0;
+    }
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache_heap.h b/engines/glk/tads/tads2/memory_cache_heap.h
new file mode 100644
index 0000000..269cdec
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_heap.h
@@ -0,0 +1,60 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * Memory cache heap manager
+ *
+ * This is the low-level heap manager, which maintains a list of non-relocatable,
+ * non-swappable blocks of memory.  The cache manager uses the heap manager for
+ * its basic storage needs.
+ */
+
+#ifndef GLK_TADS_TADS2_MEMORY_CACHE_HEAP
+#define GLK_TADS_TADS2_MEMORY_CACHE_HEAP
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/error_handling.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/**
+ *   Allocate a block of memory; returns pointer to the block.
+ *   An out-of-memory error is signalled if insufficient memory
+ *   is available.  The comment is for debugging purposes only.
+ */
+uchar *mchalo(errcxdef *ctx, size_t siz, char *comment);
+
+/* allocate a structure */
+#define MCHNEW(errctx, typ, comment) \
+ ((typ *)mchalo(errctx, sizeof(typ), comment))
+
+/* free a block of memory */
+/* void mchfre(uchar *ptr); */
+#define mchfre(ptr) (osfree(ptr))
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/memory_cache_loader.cpp b/engines/glk/tads/tads2/memory_cache_loader.cpp
new file mode 100644
index 0000000..f403299
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_loader.cpp
@@ -0,0 +1,31 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/memory_cache_loader.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache_loader.h b/engines/glk/tads/tads2/memory_cache_loader.h
new file mode 100644
index 0000000..c7ec22d
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_loader.h
@@ -0,0 +1,57 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * Memory cache swap manager
+ *
+ * The cache swap manager provides swap file services to the memory
+ * cache manager.  The cache manager calls the swap manager to write
+ * objects to the swap file and read in previously swapped-out objects.
+ */
+
+#ifndef GLK_TADS_TADS2_MEMORY_CACHE_LOADER
+#define GLK_TADS_TADS2_MEMORY_CACHE_LOADER
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/error_handling.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/**
+ * Loader context
+ */
+struct mclcxdef {
+    errcxdef *mclcxerr;			/* error handling context */
+};
+
+/**
+ * Loader handle
+ */
+typedef ulong mclhd;			/* essentially a seek address */
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/memory_cache_swap.cpp b/engines/glk/tads/tads2/memory_cache_swap.cpp
new file mode 100644
index 0000000..114a725
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_swap.cpp
@@ -0,0 +1,31 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/memory_cache_swap.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache_swap.h b/engines/glk/tads/tads2/memory_cache_swap.h
new file mode 100644
index 0000000..f1b9849
--- /dev/null
+++ b/engines/glk/tads/tads2/memory_cache_swap.h
@@ -0,0 +1,122 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * Memory cache swap manager
+ *
+ * The cache swap manager provides swap file services to the memory
+ * cache manager.  The cache manager calls the swap manager to write
+ * objects to the swap file and read in previously swapped-out objects.
+ */
+
+#ifndef GLK_TADS_TADS2_MEMORY_CACHE_SWAP
+#define GLK_TADS_TADS2_MEMORY_CACHE_SWAP
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/osfrobtads.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* Forward declarations */
+struct mcmcx1def;
+
+/**
+ * swap segment descriptor
+ */
+struct mcsdsdef {
+    ulong    mcsdsptr;                         /* seek pointer in swap file */
+    ushort   mcsdssiz;                         /* size of this swap segment */
+    ushort   mcsdsosz;                 /* size of object written to segment */
+    uint     mcsdsobj;                                  /* client object ID */
+    ushort   mcsdsflg;                                             /* flags */
+#define      MCSDSFINUSE   0x01                        /* segment is in use */
+};
+
+/**
+ * mcsseg - swap segment handle.  All swap-file segments are addressed
+ * through this handle type.  
+ */
+typedef ushort mcsseg;
+
+/**
+ * Swap manager context
+ */
+struct mcscxdef {
+    osfildef   *mcscxfp;                                /* swap file handle */
+    char       *mcscxfname;                            /* name of swap file */
+    errcxdef   *mcscxerr;                         /* error handling context */
+    ulong       mcscxtop;              /* top of swap file allocated so far */
+    ulong       mcscxmax;        /* maximum size of swap file we're allowed */
+    mcsdsdef  **mcscxtab;                     /* swap descriptor page table */
+    mcsseg      mcscxmsg;               /* maximum segment allocated so far */
+    mcmcx1def *mcscxmem;                   /* memory manager context */
+};
+
+#define MCSSEGINV ((mcsseg)~0)      /* invalid segment ID - error indicator */
+
+/* initialize swapper - returns 0 for success, other for error */
+void mcsini(struct mcscxdef *ctx, struct mcmcx1def *gmemctx, ulong maxsiz,
+            osfildef *fp, char *swapfilename, struct errcxdef *errctx);
+
+/* close swapper (release memory areas) */
+void mcsclose(struct mcscxdef *ctx);
+
+/**
+ *   Swap an object out.  The caller specifies the location and size of
+ *   the object, as well as a unique handle (arbitrary, up to the caller;
+ *   the only requirement is that it be unique among all caller objects
+ *   and always the same for a particular caller's object) and the
+ *   previous swap handle if the object ever had one.  If the object is
+ *   not dirty (it hasn't been written since being swapped in), and the
+ *   swap manager hasn't reused the swap slot, the swap manager doesn't
+ *   need to write the memory, since it already has a copy on disk;
+ *   instead, it can just mark the slot as back in use.  If the caller
+ *   doesn't wish to take advantage of this optimization, always pass in
+ *   dirty == TRUE, which will force a write regardless of the object ID.
+ */
+mcsseg mcsout(struct mcscxdef *ctx, uint objid, uchar *objptr,
+              ushort objsize, mcsseg oldswapseg, int dirty);
+            
+/* Swap an object in */
+void mcsin(struct mcscxdef *ctx, mcsseg swapseg, uchar *objptr, ushort size);
+
+
+/* number of page pointers in page table (max number of pages) */
+#define MCSPAGETAB 256
+
+/* number of swap descriptors in a page */
+#define MCSPAGECNT 256
+
+/* find swap descriptor corresponding to swap segment number */
+#define mcsdsc(ctx,seg) (&(ctx)->mcscxtab[(seg)>>8][(seg)&255])
+
+/* write out a swap segment */
+void mcswrt(mcscxdef *ctx, mcsdsdef *desc, uchar *buf, ushort bufl);
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp
new file mode 100644
index 0000000..2ba1161
--- /dev/null
+++ b/engines/glk/tads/tads2/object.cpp
@@ -0,0 +1,35 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/object.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/object.h b/engines/glk/tads/tads2/object.h
new file mode 100644
index 0000000..5e2499c
--- /dev/null
+++ b/engines/glk/tads/tads2/object.h
@@ -0,0 +1,396 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_OBJECT
+#define GLK_TADS_TADS2_OBJECT
+
+#include "glk/tads/tads.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/property.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/**
+ * object number
+ */
+typedef ushort objnum;
+
+/**
+ * For non-class objects, we'll leave some space free in the object so
+ * that a few properties can be added without having to resize the
+ * object.  Class objects will probably never have anything added, so
+ * there's no need for extra space.  
+ */
+#define OBJEXTRA 64
+
+#   define   OBJFCLS   0x01                            /* object is a class */
+
+/**
+ * The object structure is actually laid out portably, using unaligned
+ * 2-byte arrays, stored least significant byte first, for each ushort
+ * (including the objnum array for the superclasses).  The actual
+ * entries are at these offsets on all machines: 
+ *
+ *      objws      0
+ *      objflg     2
+ *      objnsc     4
+ *      objnprop   6
+ *      objfree    8
+ *      objrst     10
+ *      objstat    12
+ *      objsc[0]   14
+ *      objsc[1]   16
+ *      etc
+ *      
+ * If the OBJFINDEX flag is set, the object has a property index.
+ * The index occurs after the last superclass (so it's where the
+ * property data would go if there were no index), and the property
+ * data follows.  Each index entry consists of a pair of two-byte
+ * entries:  the first is the property number, and the second is
+ * its offset within the object.  For performance reasons, an index
+ * is only built on a class object -- whenever a property is changed
+ * within an object, the entire index must be rebuilt, because the
+ * locations of many properties within the object can be changed by
+ * a single property change in the object.  The index is ordered by
+ * property number, so it can be searched using a binary search.
+ * Furthermore, "ignored" properties are excluded from the index;
+ * only the active instance of a particular property is stored.
+ * The index must be maintained by all routines that can change
+ * property information:  setp, delp, revert, etc.
+ * 
+ * Preceding the index table is a two-byte entry that gives the
+ * offset of the properties.  Since the properties immediately
+ * follow the index, this can be used to deduce how large a space
+ * is available for the index itself.
+ */
+typedef uchar objdef;
+#define OBJDEFSIZ 14   /* "sizeof(objdef)" - size of object header w/o sc's */
+
+/* object flags */
+#define OBJFCLASS  1                                   /* object is a class */
+#define OBJFINDEX  2                         /* object has a property index */
+#define OBJFMOD    4      /* object has been modified by a newer definition */
+
+/* undo context */
+struct objucxdef {
+    mcmcxdef *objucxmem;                           /* cache manager context */
+    errcxdef *objucxerr;                                   /* error context */
+    ushort    objucxsiz;                         /* size of the undo buffer */
+    ushort    objucxhead;                  /* head (position of next write) */
+    ushort    objucxtail;               /* tail (position of oldest record) */
+    ushort    objucxprv;                           /* previous head pointer */
+    ushort    objucxtop;                      /* highest head value written */
+    void    (*objucxcun)(void *ctx, uchar *data);
+                                              /* apply a client undo record */
+    ushort  (*objucxcsz)(void *ctx, uchar *data);
+                                        /* get size of a client undo record */
+    void     *objucxccx;                             /* client undo context */
+    uchar     objucxbuf[1];                                  /* undo buffer */
+};
+
+/*
+ *   Undo records are kept in a circular buffer allocated as part of an
+ *   undo context.  Offsets within the buffer are kept for the head, tail,
+ *   and previous head records.  The head always points to the byte at
+ *   which the next undo record will be written.  The previous head points
+ *   to the most recently written undo record; it contains a back link to
+ *   the undo record before that, and so forth back through the entire
+ *   chain.  (These reverse links are necessary because undo records vary
+ *   in size depending on the data contained within.)  The tail points to
+ *   the oldest undo record that's still in the buffer.  Conceptually, the
+ *   head is always "above" the tail in the buffer; since the buffer is
+ *   circular, the tail may have a higher address, but this just means
+ *   that the buffer wraps around at the top.  When the head bumps into
+ *   the tail (i.e., the head address is physically below or equal to the
+ *   tail address, and the head is then advanced so that its address
+ *   becomes higher than the tail's), the tail is advanced by discarding
+ *   as many of the least recent undo records as necessary to make room
+ *   for the new head position.  When the head and the previous head point
+ *   to the same place, we have no undo records in the buffer.  
+ */
+/**
+ *   The first byte of an undo record specifies what action is to be
+ *   undone.  If a property was added, it is undone merely by deleting the
+ *   property.  If a property was changed, it is undone by setting the
+ *   property back to its old value.  An additional special flag indicates
+ *   a "savepoint."  Normally, all changes back to a savepoint will be
+ *   undone.  
+ */
+#define OBJUADD    1             /* a property was added (undo by deleting) */
+#define OBJUCHG    2   /* a property was changed (change back to old value) */
+#define OBJUSAV    3          /* savepoint marker (no property information) */
+#define OBJUOVR    4     /* override original property (set orig to IGNORE) */
+#define OBJUCLI    5                /* client undo record (any client data) */
+
+/*
+ *   After the control byte (OBJUxxx), the object number, property
+ *   number, datatype, and data value will follow; some or all of these
+ *   may be omitted, depending on the control byte. 
+ */
+
+/* get object flags */
+#define objflg(o) ((ushort)osrp2(((char *)(o)) + 2))
+
+/* get object flags */
+#define objsflg(o, val) oswp2(((char *)(o)) + 2, val)
+
+/* given an object pointer, get a pointer to the first prpdef */
+/* prpdef *objprp(objdef *objptr); */
+#define objprp(o) ((prpdef *)(objsc(o) + 2*objnsc(o)))
+
+/* given an object pointer, get number of properties in the prpdef */
+/* int objnprop(objdef *objptr); */
+#define objnprop(o) ((ushort)osrp2(((char *)(o)) + 6))
+
+/* set number of properties */
+/* void objsnp(objdef *objptr, int newnum); */
+#define objsnp(o,n) oswp2(((char *)(o)) + 6, n)
+
+/* given an object pointer, get offset of free space */
+/* int objfree(objdef *objptr); */
+#define objfree(o) ((ushort)osrp2(((char *)(o)) + 8))
+
+/* set free space pointer */
+/* void objsfree(objdef *objptr, int newfree); */
+#define objsfree(o,n) oswp2(((char *)(o)) + 8, n)
+
+/* get number of static properties */
+/* ushort objstat(objdef *objptr); */
+#define objstat(o) ((ushort)osrp2(((char *)(o)) + 10))
+
+/* set number of static properties */
+/* void objsetst(objdef *objptr, int newstat); */
+#define objsetst(o,n) oswp2(((char *)(o)) + 10, n)
+
+/* get reset size (size of static properties) */
+/* ushort objrst(objdef *objptr); */
+#define objrst(o) ((ushort)osrp2(((char *)(o)) + 12))
+
+/* set reset size */
+/* void objsetrst(objdef *objptr, uint newrst); */
+#define objsetrst(o,n) oswp2(((char *)(o)) + 12, n)
+ 
+/* given an object pointer, get first superclass pointer */
+/* uchar *objsc(objdef *objptr); */
+#define objsc(o) (((uchar *)(o)) + OBJDEFSIZ)
+
+/* given an object pointer, get number of superclasses */
+/* int objnsc(objdef *objptr); */
+#define objnsc(o) ((ushort)osrp2(((char *)(o)) + 4))
+
+/* set number of superclasses */
+/* void objsnsc(objdef *objptr, int num); */
+#define objsnsc(o,n) oswp2(((char *)(o)) + 4, n)
+
+/* given a prpdef, get the next prpdef */
+/* prpdef *objpnxt(prpdef *p); */
+#define objpnxt(p) \
+ ((prpdef *)(((uchar *)(p)) + PRPHDRSIZ + prpsize(p)))
+
+/* get pointer to free prpdef */
+/* prpdef *objpfre(objdef *objptr); */
+#define objpfre(o) ((prpdef *)(((uchar *)(o)) + objfree(o)))
+
+/* given a prpdef and an object pointer, compute the prpdef offset */
+/* uint objpofs(objdef *objptr, prpdef *propptr); */
+#define objpofs(o,p) ((uint)((p) ? (((uchar *)(p)) - ((uchar *)(o))) : 0))
+
+/* given an object pointer and a property offset, get prpdef pointer */
+/* prpdef *objofsp(objdef *objptr, uint propofs); */
+#define objofsp(o,ofs) ((prpdef *)((ofs) ? (((uchar *)(o)) + (ofs)) : 0))
+
+/*
+ *   Get the first superclass of an object.  If it doesn't have any
+ *   superclasses, return invalid.
+ */
+objnum objget1sc(mcmcxdef *ctx, objnum objn);
+
+/*
+ *   Get an object's property WITHOUT INHERITANCE.  If the object has the
+ *   indicated property set, the byte OFFSET of the prpdef within the
+ *   object is returned.  The offset will remain valid until any type of
+ *   operation that sets a property in the object (such as objdelp,
+ *   objsetp, or an undo operation).  An offset of zero means that the
+ *   property was not set in the object.
+ */
+uint objgetp(mcmcxdef *ctx, objnum objn, prpnum prop,
+             dattyp *typptr);
+
+/*
+ *   Get the *ending* offset of the given property's value, without any
+ *   inheritance.  Returns the byte offset one past the end of the
+ *   property's data.  
+ */
+uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop);
+
+/*
+ *   Get a property of an object, either from the object or from a
+ *   superclass (inherited).  If the inh flag is TRUE, we do not look
+ *   at all in the object itself, but restrict our search to inherited
+ *   properties only.  We return the byte OFFSET of the prpdef within
+ *   the object in which the prpdef is found; the superclass object
+ *   itself is NOT locked upon return, but we will NOT unlock the
+ *   object passed in.  If the offset is zero, the property was not
+ *   found.  The offset returned is valid until any operation that
+ *   sets a property in the object (such as objdelp, objsetp, or an
+ *   undo operation).
+ */
+uint objgetap(mcmcxdef *ctx, noreg objnum objn, prpnum prop,
+              objnum *orn, int inh);
+
+/*
+ *   expand an object by a requested amount, returning a pointer to the
+ *   object's new location if it must be moved.  The object will be
+ *   unlocked and relocked by this call.  On return, the actual amount
+ *   of space ADDED to the object will be returned.
+ */
+objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz);
+
+/*
+ *   Set an object's property, deleting the original value of the
+ *   property if it existed.  If an undo context is provided, write an
+ *   undo record for the change; if the undo context pointer is null, no
+ *   undo information is retained. 
+ */
+void objsetp(mcmcxdef *ctx, objnum obj, prpnum prop,
+             dattyp typ, void *val, objucxdef *undoctx);
+
+/* 
+ *   Delete a property.  If mark_only is true, we'll only mark the
+ *   property as deleted without actually reclaiming its space; this is
+ *   necessary when removing a code property (type DAT_CODE) any time
+ *   other code properties may follow, because p-code is not entirely
+ *   self-relative and thus can't always be relocated within an object. 
+ */
+void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only);
+
+/*
+ *   Set up for emitting code into an object.  Writes a property header
+ *   of type 'code', and returns the offset of the next free byte in the
+ *   object.  Call objendemt when done.  The datatype argument is
+ *   provided so that list generation can be done through the same
+ *   mechanism, since parser lists must be converted to run-time
+ *   lists via the code generator.
+ */
+uint objemt(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ);
+
+/* done emitting code into property, finish setting object info */
+void objendemt(mcmcxdef *ctx, objnum objn, prpnum prop, uint endofs);
+
+/*
+ *   Determine if undo records should be kept.  Undo records should be
+ *   kept only if a savepoint is present in the undo log.  If no savepoint
+ *   is present, adding undo records would be useless, since it will not
+ *   be possible to apply the undo information. 
+ */
+int objuok(objucxdef *undoctx);
+
+/*
+ *   Reserve space in an undo buffer, deleting old records as needed.
+ *   Returns a pointer to the reserved space. 
+ */
+uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz);
+
+/* advance the tail pointer in an undo buffer over the record it points to */
+void objutadv(objucxdef *undoctx);
+
+/* apply one undo record, and remove it from undo list */
+void obj1undo(mcmcxdef *mctx, objucxdef *undoctx);
+
+/*
+ *   Undo back to the most recent savepoint.  If there is no savepoint in
+ *   the undo list, NOTHING will be undone.  This prevents reaching an
+ *   inconsistent state in which some, but not all, of the operations
+ *   between two savepoints are undone: either all operations between two
+ *   savepoints will be undone, or none will. 
+ */
+void objundo(mcmcxdef *mctx, objucxdef *undoctx);
+
+/* set an undo savepoint */
+void objusav(objucxdef *undoctx);
+
+/* initialize undo context */
+objucxdef *objuini(mcmcxdef *memctx, ushort undosiz,
+                   void (*undocb)(void *ctx, uchar *data),
+                   ushort (*sizecb)(void *ctx, uchar *data),
+                   void *callctx);
+
+/* free the undo context - releases memory allocated by objuini() */
+void objuterm(objucxdef *undoctx);
+
+/* discard all undo context (for times such as restarting) */
+void objulose(objucxdef *undoctx);
+
+/*
+ *   Allocate and initialize a new object.  The caller specifies the
+ *   number of superclasses to be allocated, and the amount of space (in
+ *   bytes) for the object's property data.  The caller must fill in the
+ *   superclass array.  Upon return, the object is allocated and locked,
+ *   and is initialized with no properties.  A pointer to the object's
+ *   memory is returned, and *objnptr receives the object number.
+ */
+objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace,
+               objnum *objnptr, int classflg);
+            
+/* initialize an already allocated object */
+void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg);
+
+/*
+ *   Add space for additional superclasses to an object.  The object can
+ *   already have some properties set (if it doesn't, it can just be
+ *   reinitialized).
+ */
+void objaddsc(mcmcxdef *mctx, int sccnt, objnum objn);
+
+/*
+ *   Delete an object's properties and superclasses.  The 'mindel'
+ *   parameter specifies the minimum property number to be deleted.
+ *   Properties below this are considered "system" properties that are not
+ *   to be deleted.  This could be used by a development environment to
+ *   store the source for an object as a special system property in the
+ *   object; when the object is recompiled, all of the object's properties
+ *   and superclasses must be deleted except the source property, which is
+ *   retained even after recompilation. 
+ */
+void objclr(mcmcxdef *mctx, objnum objn, prpnum mindel);
+
+/* Build or rebuild an object's property index */
+void objindx(mcmcxdef *mctx, objnum objn);
+
+/* set up just-compiled object: mark static part and original properties */
+void objcomp(mcmcxdef *mctx, objnum objn, int for_debug);
+
+/* revert an object to original post-compilation state */
+void objrevert(void *mctx, mcmon objn);
+
+/* reset 'ignore' flags for a newly reconstructed object */
+void objsetign(mcmcxdef *mctx, objnum objn);
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/opcode_defs.h b/engines/glk/tads/tads2/opcode_defs.h
new file mode 100644
index 0000000..32519ae
--- /dev/null
+++ b/engines/glk/tads/tads2/opcode_defs.h
@@ -0,0 +1,218 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_OPCODE_DEFS
+#define GLK_TADS_TADS2_OPCODE_DEFS
+
+/*
+ * Opcode definitions
+ *
+ * Lifted largely from the old TADS, since the basic run - time interpreter's
+ * operation is essentially the same.
+ */
+
+#include "glk/tads/tads.h"
+#include "glk/tads/tads2/data.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+#define OPCPUSHNUM  1       /* push a constant numeric value */
+#define OPCPUSHOBJ  2       /* push an object */
+#define OPCNEG      3       /* unary negation */
+#define OPCNOT      4       /* logical negation */
+#define OPCADD      5       /* addition/list concatenation */
+#define OPCSUB      6       /* subtraction/list difference */
+#define OPCMUL      7       /* multiplication */
+#define OPCDIV      8       /* division */
+#define OPCAND      9       /* logical AND */
+#define OPCOR       10      /* logical OR */
+#define OPCEQ       11      /* equality */
+#define OPCNE       12      /* inequality */
+#define OPCGT       13      /* greater than */
+#define OPCGE       14      /* greater or equal */
+#define OPCLT       15      /* less than */
+#define OPCLE       16      /* less or equal */
+#define OPCCALL     17      /* call a function */
+#define OPCGETP     18      /* get property */
+#define OPCGETPDATA 19      /* get a property, allowing only data values */
+#define OPCGETLCL   20      /* get a local variable's value */
+#define OPCPTRGETPDATA 21   /* get property via pointer; only allow data */   
+#define OPCRETURN   22      /* return without a value */
+#define OPCRETVAL   23      /* return a value */
+#define OPCENTER    24      /* enter a function */
+#define OPCDISCARD  25      /* discard top of stack */
+#define OPCJMP      26      /* unconditional jump */
+#define OPCJF       27      /* jump if false */
+#define OPCPUSHSELF 28      /* push current object */
+#define OPCSAY      29      /* implicit printout for doublequote strings */
+#define OPCBUILTIN  30      /* call a built-in function */
+#define OPCPUSHSTR  31      /* push a string */
+#define OPCPUSHLST  32      /* push a list */
+#define OPCPUSHNIL  33      /* push the NIL value */
+#define OPCPUSHTRUE 34      /* push the TRUE value */
+#define OPCPUSHFN   35      /* push the address of a function */
+#define OPCGETPSELFDATA 36  /* push property of self; only allow data */
+
+#define OPCPTRCALL  38          /* call function pointed to by top of stack */
+#define OPCPTRINH   39          /* inherit pointer to property (stack=prop) */
+#define OPCPTRGETP  40          /* get property by pointer (stack=obj,prop) */
+
+#define OPCPASS     41      /* pass to inherited handler */
+#define OPCEXIT     42      /* exit turn, but continue with fuses/daemons */
+#define OPCABORT    43      /* abort turn, skipping fuses/daemons */
+#define OPCASKDO    44      /* ask for a direct object */
+#define OPCASKIO    45      /* ask for indirect object and set preposition */
+
+/* explicit superclass inheritance opcodes */
+#define OPCEXPINH   46      /* "inherited <superclass>.<property>" */
+#define OPCEXPINHPTR 47     /* "inherited <superclass>.<prop-pointer>" */
+
+/*
+ *   Special opcodes for peephole optimization.  These are essentially
+ *   pairs of operations that occur frequently so have been collapsed into
+ *   a single instruction.
+ */
+#define OPCCALLD    48      /* call function and discard value */
+#define OPCGETPD    49      /* evaluate property and discard any value */
+#define OPCBUILTIND 50      /* call built-in function and discard value */
+
+#define OPCJE       51      /* jump if equal */
+#define OPCJNE      52      /* jump if not equal */
+#define OPCJGT      53      /* jump if greater than */
+#define OPCJGE      54      /* jump if greater or equal */
+#define OPCJLT      55      /* jump if less than */
+#define OPCJLE      56      /* jump if less or equal */
+#define OPCJNAND    57      /* jump if not AND */
+#define OPCJNOR     58      /* jump if not OR */
+#define OPCJT       59      /* jump if true */
+
+#define OPCGETPSELF 60      /* get property of the 'self' object */
+#define OPCGETPSLFD 61      /* get property of 'self' and discard result */
+#define OPCGETPOBJ  62      /* get property of a given object */
+                            /*  note: differs from GETP in that object is */
+                            /*  encoded into the instruction */
+#define OPCGETPOBJD 63      /* get property of an object and discard result */
+#define OPCINDEX    64      /* get an indexed entry from a list */
+
+#define OPCPUSHPN   67      /* push a property number */
+
+#define OPCJST      68      /* jump and save top-of-stack if true */
+#define OPCJSF      69      /* jump and save top-of-stack if false */
+#define OPCJMPD     70      /* discard stack and then jump unconditionally */
+
+#define OPCINHERIT  71      /* inherit a property from superclass */
+#define OPCCALLEXT  72      /* call external function */
+#define OPCDBGRET   73      /* return to debugger (no stack frame leaving) */
+
+#define OPCCONS     74      /* construct list from top two stack elements */
+#define OPCSWITCH   75      /* switch statement */
+
+#define OPCARGC     76      /* get argument count */
+#define OPCCHKARGC  77      /* check actual arguments against formal count */
+
+#define OPCLINE     78      /* line record */
+#define OPCFRAME    79      /* local variable frame record */
+#define OPCBP       80      /* breakpoint - replaces an OPCLINE instruction */
+#define OPCGETDBLCL 81                                /* get debugger local */
+#define OPCGETPPTRSELF 82                 /* get property pointer from self */
+#define OPCMOD      83                                            /* modulo */
+#define OPCBAND     84                                        /* binary AND */
+#define OPCBOR      85                                         /* binary OR */
+#define OPCXOR      86                                        /* binary XOR */
+#define OPCBNOT     87                                   /* binary negation */
+#define OPCSHL      88                                    /* bit shift left */
+#define OPCSHR      89                                   /* bit shift right */
+
+#define OPCNEW      90                                 /* create new object */
+#define OPCDELETE   91                                     /* delete object */
+
+
+/* ----- opcodes 192 and above are reserved for assignment operations ----- */
+
+/*
+ASSIGNMENT OPERATIONS
+    When (opcode & 0xc0 == 0xc0), we have an assignment operation.
+    (Note that this means that opcodes from 0xc0 up are all reserved
+    for assignment operations.)  The low six bits of the opcode
+    specify exactly what kind of operation is to be performed:
+    
+    bits 0-1:  specifies destination type:
+               00    2-byte operand is local number
+               01    2-byte operand is property to set in obj at tos
+               10    tos is index, [sp-1] is list to be indexed and set
+               11    tos is property pointer, [sp-1] is object
+    
+    bits 2-4:  specifies assignment operation:
+               000   := (direct assignment)
+               001   += (add tos to destination)
+               010   -= (subtract tos from destination)
+               011   *= (multiply destination by tos)
+               100   /= (divide destination by tos)
+               101   ++ (increment tos)
+               110   -- (decrement tos)
+               111   *reserved*
+               
+    bit 5:     specifies what to do with value computed by assignment
+               0     leave on stack (implies pre increment/decrement)
+               1     discard (implies post increment/decrement)
+*/
+#define OPCASI_MASK      0xc0                     /* assignment instruction */
+
+#define OPCASIDEST_MASK  0x03              /* mask to get destination field */
+#define OPCASILCL        0x00                          /* assign to a local */
+#define OPCASIPRP        0x01               /* assign to an object.property */
+#define OPCASIIND        0x02             /* assign to an element of a list */
+#define OPCASIPRPPTR     0x03                /* assign property via pointer */
+
+#define OPCASITYP_MASK   0x1c          /* mask to get assignment type field */
+#define OPCASIDIR        0x00                          /* direct assignment */
+#define OPCASIADD        0x04                             /* assign and add */
+#define OPCASISUB        0x08                        /* assign and subtract */
+#define OPCASIMUL        0x0c                        /* assign and multiply */
+#define OPCASIDIV        0x10                          /* assign and divide */
+#define OPCASIINC        0x14                                  /* increment */
+#define OPCASIDEC        0x18                                  /* decrement */
+#define OPCASIEXT        0x1c                     /* other - extension flag */
+
+/* extended assignment flags - next byte when OPCASIEXT is used */
+#define OPCASIMOD        1                             /* modulo and assign */
+#define OPCASIBAND       2                         /* binary AND and assign */
+#define OPCASIBOR        3                          /* binary OR and assign */
+#define OPCASIXOR        4                         /* binary XOR and assign */
+#define OPCASISHL        5                         /* shift left and assign */
+#define OPCASISHR        6                        /* shift right and assign */
+
+
+#define OPCASIPRE_MASK   0x20                    /* mask for pre/post field */
+#define OPCASIPOST       0x00                       /* increment after push */
+#define OPCASIPRE        0x20                      /* increment before push */
+
+/* some composite opcodes for convenience */
+#define OPCSETLCL (OPCASI_MASK | OPCASILCL | OPCASIDIR)
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/os.cpp b/engines/glk/tads/tads2/os.cpp
index 15a291a..e51a4d9 100644
--- a/engines/glk/tads/tads2/os.cpp
+++ b/engines/glk/tads/tads2/os.cpp
@@ -26,6 +26,7 @@ namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
+#if 0
 OS::OS(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gameDesc),
 		status_mode(0) {
 	Common::fill(&status_left[0], &status_left[OSS_STATUS_STRING_LEN], '\0');
@@ -345,6 +346,7 @@ int OS::oss_getc_from_window(winid_t win) {
 	buffered_char = oss_convert_keystroke_to_tads(ev.val1);
 	return 0;
 }
+#endif
 
 } // End of namespace TADS2
 } // End of namespace TADS
diff --git a/engines/glk/tads/tads2/os.h b/engines/glk/tads/tads2/os.h
index 45ab804..5dcc83c 100644
--- a/engines/glk/tads/tads2/os.h
+++ b/engines/glk/tads/tads2/os.h
@@ -20,219 +20,5137 @@
  *
  */
 
+/* Portable interfaces to OS-specific functions
+ *
+ * This file defines interfaces to certain functions that must be called
+ * from portable code, but which must have system-specific implementations.
+ */
+
 #ifndef GLK_TADS_TADS2_OS
 #define GLK_TADS_TADS2_OS
 
-#include "glk/tads/tads.h"
-#include "glk/tads/tads2/types.h"
+#include "common/system.h"
+#include "glk/tads/osfrobtads.h"
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/appctx.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-/**
- * Operating system compatibility layer
- */
-class OS : public TADS {
-protected:
-	char status_left[OSS_STATUS_STRING_LEN];
-	char status_right[OSS_STATUS_STRING_LEN];
-	int status_mode;
-protected:
-	/**
-	 * Constructor
-	 */
-	OS(OSystem *syst, const GlkGameDescription &gameDesc);
-
-	/**
-	 * Terminates the game
-	 */
-	void os_terminate(int rc);
-
-	/**
-	 * \defgroup Type Conversions
-	 * @{
-	 */
-
-	/**
-	 * Change a TADS prompt type (OS_AFP_*) into a Glk prompt type.
-	 */
-	uint oss_convert_prompt_type(int type);
-	
-	/**
-	 * Change a TADS file type (OSFT*) into a Glk file type.
-	 */
-	uint oss_convert_file_type(int type);
-
-	/**
-	 * Change a fileref ID (frefid_t) to a special string and put it in the
-	 * buffer which is passed to it. The string is given by
-	 *   OSS_FILEREF_STRING_PREFIX + 'nnnnn' + OSS_FILEREF_STRING_SUFFIX
-	 * where 'nnnnn' is the frefid_t pointer converted into a string of decimal
-	 * numbers. This is only really practical for 32-bit pointers; if we use
-	 * 64-bit pointers I'll have to start using a hash table or use hex
-	 * numbers.
-	 */
-	uint oss_convert_fileref_to_string(frefid_t file_to_convert, char *buffer, int buf_len);
-
-	/**
-	 * Turn a filename or a special fileref string into an actual fileref.
-	 * Notice that, since Glk doesn't know paths, we take this opportunity to
-	 * call oss_check_path, which should do the OS-dependent path changing
-	 * in the event that the filename contains path information
-	 */
-	frefid_t oss_convert_string_to_fileref(char *buffer, uint usage);
-
-	/**
-	 * Tell us if the passed string is a hashed fileref or not
-	 */
-	bool oss_is_string_a_fileref(char *buffer);
-
-	/**
-	 * Change a Glk key into a TADS one, using the CMD_xxx codes
-	 */
-	unsigned char oss_convert_keystroke_to_tads(uint key);
-
-	/**@}*/
-
-	/**
-	 * \defgroup Directory/File methods
-	 * @{
-	 */
-
-	/**
-	 * If a filename contains path information, change dirs to that path.
-	 * Returns true if the path was fiddled with
-	 */
-	bool oss_check_path(char *filename);
-
-	/**
-	 * In case we changed directories in oss_check_path, change back to the
-	 * original executable directory
-	 */
-	void oss_revert_path();
-
-	/**
-	 * Open a stream, given a string, usage, and a filemode. tadsusage is the
-	 * TADS filemode (OSFT*); tbusage is either fileusage_TextMode or
-	 * fileusage_BinaryMode (from Glk).
-	 */
-	osfildef *oss_open_stream(char *buffer, uint tadsusage, uint tbusage,
-		uint fmode, uint rock);
-
-	/**
-	 * Get a pointer to the root name portion of a filename.  This is the part
-	 * of the filename after any path or directory prefix.  For example, on
-	 * Unix, given the string "/home/mjr/deep.gam", this function should return
-	 * a pointer to the 'd' in "deep.gam".  If the filename doesn't appear to
-	 * have a path prefix, it should simply return the argument unchanged.
-	 */
-	const char *os_get_root_name(const char *buf) const { return buf; }
-
-	/**
-	 * Open a file for access
-	 */
-	osfildef *osfoprb(const char *fname, uint typ = 0);
-
-	/**
-	 * Receive notification that a character mapping file has been loaded.  We
-	 * don't need to do anything with this information, since we we're relying
-	 * on the Glk layer and ScummVM backend to handle all that
-	 */
-	void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo) {
-		// No implementation needed
-	}
-
-	/**
-	 * Generate a filename for a character mapping table.  On Windows, the
-	 * filename is always simply "win" plus the internal ID plus ".tcp".
-	 */
-	void os_gen_charmap_filename(char *filename, const char *internal_id,
-		const char *argv0);
-
-	/**@}*/
-
-	/**
-	 * \defgroup The main text area print routines
-	 * @{
-	 */
-
-	/**
-	 * Process hilighting codes while printing a string
-	 */
-	void oss_put_string_with_hilite(winid_t win, const char *str, size_t len);
-
-	/**
-	 * Status line handling
-	 */
-	void oss_draw_status_line();
-
-	void oss_change_status_string(char *dest, const char *src, size_t len);
-	void oss_change_status_left(const char *str, size_t len);
-	void oss_change_status_right(const char *str);
-	int os_get_status() const { return status_mode; }
-
-	/**
-	 * Flush the output
-	 */
-	void os_flush();
-
-	/**
-	 * Print a null terminated string
-	 */
-	void os_printz(const char *str) { os_print(str, strlen(str)); }
-
-	/**
-	 * Print a string
-	 */
-	void os_print(const char *str, size_t len);
-
-	/**@}*/
-
-	/**
-	 * \defgroup Memory routines
-	 * @{
-	 */
-
-	/**
-	 * Compare two strings
-	 */
-	int memicmp(const char *s1, const char *s2, int len);
-
-	/**@}*/
-
-	/**
-	 * \defgroup Input routines
-	 * @{
-	 */
-
-	/**
-	 * Wait for a key to be hit
-	 */
-	void os_waitc(void) { os_getc(); }
-
-	/**
-	 * Get a character from the keyboard. For extended characters, return 0,
-	 * then return the extended key at the next call to this function
-	 */
-	int os_getc() {
-		return oss_getc_from_window(story_win);
-	}
-
-	/**
-	 * Accept a keystroke in the passed window
-	 */
-	int oss_getc_from_window(winid_t win);
-
-
-	/**
-	 * Print a message (with os_print) and wait for a key
-	 */
-	void os_expause();
-
-	/**@}*/
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   A note on character sets:
+ *   
+ *   Except where noted, all character strings passed to and from the
+ *   osxxx functions defined herein use the local operating system
+ *   representation.  On a Windows machine localized to Eastern Europe,
+ *   for example, the character strings passed to and from the osxxx
+ *   functions would use single-byte characters in the Windows code page
+ *   1250 representation.
+ *   
+ *   Callers that use multiple character sets must implement mappings to
+ *   and from the local character set when calling the osxxx functions.
+ *   The osxxx implementations are thus free to ignore any issues related
+ *   to character set conversion or mapping.
+ *   
+ *   The osxxx implementations are specifically not permitted to use
+ *   double-byte Unicode as the native character set, nor any other
+ *   character set where a null byte could appear as part of a non-null
+ *   character.  In particular, callers may assume that null-terminated
+ *   strings passed to and from the osxxx functions contain no embedded
+ *   null bytes.  Multi-byte character sets (i.e., character sets with
+ *   mixed single-byte and double-byte characters) may be used as long as
+ *   a null byte is never part of any multi-byte character, since this
+ *   would guarantee that a null byte could always be taken as a null
+ *   character without knowledge of the encoding or context.  
+ */
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   "Far" Pointers.  Most platforms can ignore this.  For platforms with
+ *   mixed-mode addressing models, where pointers of different sizes can
+ *   be used within a single program and hence some pointers require
+ *   qualification to indicate that they use a non-default addressing
+ *   model, the keyword OSFAR should be defined to the appropriate
+ *   compiler-specific extension keyword.
+ *   
+ *   If you don't know what I'm talking about here, you should just ignore
+ *   it, because your platform probably doesn't have anything this
+ *   sinister.  As of this writing, this applies only to MS-DOS, and then
+ *   only to 16-bit implementations that must interact with other 16-bit
+ *   programs via dynamic linking or other mechanisms.  
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   ANSI C99 exact-size integer types.
+ *   
+ *   C99 defines a set of integer types with exact bit sizes, named intXX_t
+ *   for a signed integer with XX bits, and uintXX_t for unsigned.  XX can be
+ *   8, 16, 32, or 64.  TADS uses the 16- and 32-bit sizes, so each platform
+ *   is responsible for defining the following types:
+ *   
+ *.    int16_t   - a signed integer type storing EXACTLY 16 bits
+ *.    uint16_t  - an unsigned integer type storing EXACTLY 16 bits
+ *.    int32_t   - a signed integer type storing EXACTLY 32 bits
+ *.    uint32_t  - an unsigned integer type storing EXACTLY 32 bits
+ *   
+ *   Many modern compilers provide definitions for these types via the
+ *   standard header stdint.h.  Where stdint.h is provided, the platform code
+ *   can merely #include <stdint.h>.
+ *   
+ *   For compilers where stdint.h isn't available, you must provide suitable
+ *   typedefs.  Note that the types must be defined with the exact bit sizes
+ *   specified; it's not sufficient to use a bigger type, because we depend
+ *   in some cases on overflow and sign extension behavior at the specific
+ *   bit size.
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Thread-local storage (TLS).
+ *   
+ *   When TADS is compiled with threading support, it requires some variables
+ *   to be "thread-local".  This means that the variables have global scope
+ *   (so they're not stored in "auto" variables on the stack), but each
+ *   thread has a private copy of each such variable.
+ *   
+ *   Nearly all systems that support threads also support thread-local
+ *   storage.  Like threading support itself, though, TLS support is at
+ *   present implemented only in non-portable OS APIs rather than standard C
+ *   language features.  TLS is a requirement if TADS is compiled with
+ *   threading, but it's not needed for non-threaded builds.  TADS only
+ *   requires threading at present (version 3.1) for its network features;
+ *   since these features are optional, systems that don't have threading and
+ *   TLS support will simply need to disable the network features, which will
+ *   allow all of the threading and TLS definitions in osifc to be omitted.
+ *   
+ *   There appear to be two common styles of TLS programming models.  The
+ *   first provides non-standard compiler syntax for declarative creation of
+ *   thread-local variables.  The Microsoft (on Windows) and Gnu compilers
+ *   (on Linux and Unix) do this: they provide custom storage class modifiers
+ *   for declaring thread locals (__declspec(thread) for MSVC, __thread for
+ *   gcc).  Compilers that support declarative thread locals handle the
+ *   implementation details through code generation, so the program merely
+ *   needs to add the special TLS storage class qualifier to an otherwise
+ *   ordinary global variable declaration, and then can access the thread
+ *   local as though it were an ordinary global.
+ *   
+ *   The second programming model is via explicit OS API calls to create,
+ *   initialize, and access thread locals.  pthreads provides such an API, as
+ *   does Win32.  In fact, when you use the declarative syntax with MSVC or
+ *   gcc, the compiler generates the appropriate API calls, but the details
+ *   are transparent to the program; in contrast, when using pthreads
+ *   directly, the program must actively call the relevant APIs.
+ *   
+ *   It's probably the case that every system that has compiler-level support
+ *   for declarative thread local creation also has procedural APIs, so the
+ *   simplest way to abstract the platform differences would be to do
+ *   everything in terms of APIs.  However, it seems likely that compilers
+ *   with declarative syntax might be able to generate more efficient code,
+ *   since optimizers always benefit from declarative information.  So we'd
+ *   like to use declarative syntax whenever it's available, but fall back on
+ *   explicit API calls when it's not.  So our programming model is a union
+ *   of the two styles:
+ *   
+ *   1. For each thread local, declare the thread local:
+ *.      OS_DECL_TLS(char *, my_local);
+ *   
+ *   2. At main program startup (for the main thread only), initialize each
+ *   thread local:
+ *.      os_tls_create(my_local);
+ *   
+ *   3. Never get or set the value of a thread local directly; instead, use
+ *   the get/set functions:
+ *.      char *x = os_tls_get(char *, my_local);
+ *.      os_tls_set(my_local, "hello");
+ *   
+ *   One key feature of this implementation is that each thread local is
+ *   stored as a (void *) value.  We do it this way to allow a simple direct
+ *   mapping to the pthreads APIs, since that's going to be the most common
+ *   non-declarative implementation.  This means that a thread local variable
+ *   can contain any pointer type, but *only* a pointer type.  The standard
+ *   pattern for dealing with anything more ocmplex is the same as in
+ *   pthreads: gather up the data into a structure, malloc() an instance of
+ *   that structure at entry to each thread (including the main thread), and
+ *   os_tls_set() the variable to contain a pointer to that structure.  From
+ *   then on, use os_tls_set(my_struct *, my_local)->member to access the
+ *   member variables in the structure.  And finally, each thread must delete
+ *   the structure at thread exit.
+ */
+
+/*   
+ *   
+ *   Declare a thread local.
+ *   
+ *   - For compilers that support declarative TLS variables, the local OS
+ *   headers should use the compiler support by #defining OS_DECL_TLS to the
+ *   appropriate local declarative keyword.
+ *   
+ *   - For systems without declarative TLS support but with TLS APIs, the
+ *   global declared by this macro actually stores the slot ID (what pthreads
+ *   calls the "key") for the variable.  This macro should therefore expand
+ *   to a declaration of the appropriate API type for a slot ID; for example,
+ *   on pthreads, #define OS_DECL_TLS(t, v) pthread_key_t v.
+ *   
+ *   - For builds with no thread support, simply #define this to declare the
+ *   variable as an ordinary global: #define OS_DECL_TLS(t, v) t v.
+ */
+/* #define OS_DECL_TLS(typ, varname)  __thread typ varname */
+
+/*
+ *   For API-based systems without declarative support in the compiler, the
+ *   main program startup code must explicitly create a slot for each thread-
+ *   local variable by calling os_tls_create().  The API returns a slot ID,
+ *   which is shared among threads and therefore can be stored in an ordinary
+ *   global variable.  OS_DECL_TLS will have declared the global variable
+ *   name in this case as an ordinary global of the slot ID type.  The
+ *   os_tls_create() macro should therefore expand to a call to the slot
+ *   creation API, storing the new slot ID in the global.
+ *   
+ *   Correspondingly, before the main thread exits, it should delete each
+ *   slot it created, b calling os_tls_delete().
+ *   
+ *   For declarative systems, there's no action required here, so these
+ *   macros can be defined to empty.
+ */
+/* #define os_tls_create(varname)  pthread_key_create(&varname, NULL) */
+/* #define os_tls_delete(varname)  pthread_key_delete(varname) */
+
+
+/*
+ *   On API-based systems, each access to get or set the thread local
+ *   requires an API call, using the slot ID stored in the actual global to
+ *   get the per-thread instance of the variable's storage.
+ *.    #define os_tls_get(typ, varname) ((typ)pthread_getspecific(varname))
+ *.    #define os_tls_set(varname, val) pthread_setspecific(varname, val)
+ *   
+ *   On declarative systems, the global variable itself is the thread local,
+ *   so get/set can be implemented as direct access to the variable.
+ *.    #define os_tls_get(typ, varname) varname
+ *.    #define os_tls_set(varname, val) varname = (val)
+ */
+
+/*
+ *   Common TLS definitions - declarative thread locals
+ *   
+ *   For systems with declarative TLS support in the compiler, the OS header
+ *   can #define OS_DECLARATIVE_TLS to pick up suitable definitions for the
+ *   os_tls_xxx() macros.  The OS header must separately define OS_DECL_TLS
+ *   as appropriate for the local system.
+ */
+#ifdef OS_DECLARATIVE_TLS
+#define os_tls_create(varname)
+#define os_tls_delete(varname)
+#define os_tls_get(typ, varname) varname
+#define os_tls_set(varname, val) varname = (val)
+#endif
+
+/*
+ *   Common TLS definitions - pthreads
+ *   
+ *   For pthreads systems without declarative TLS support in the compiler,
+ *   the OS header can simply #define OS_PTHREAD_TLS to pick up the standard
+ *   definitions below. 
+ */
+#ifdef OS_PTHREAD_TLS
+#include <pthread.h>
+#define OS_DECL_TLS(typ, varname) pthread_key_t varname
+#define os_tls_create(varname) pthread_key_create(&varname, NULL)
+#define os_tls_delete(varname) pthread_key_delete(varname)
+#define os_tls_get(typ, varname) ((typ)pthread_getspecific(varname))
+#define os_tls_set(varname, val) pthread_setspecific(varname, val)
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   <time.h> definitions.
+ *   
+ *   os_time() should act like Unix time(), returning the number of seconds
+ *   elapsed since January 1, 1970 at midnight UTC.
+ *   
+ *   The original Unix <time.h> package defined time_t as a 32-bit signed
+ *   int, and many subsequent C compilers on other platforms followed suit.
+ *   A signed 32-bit time_t has the well-known year-2038 problem; some later
+ *   C compilers tried to improve matters by using an unsigned 32-bit time_t
+ *   instead, but for many purposes this is even worse since it can't
+ *   represent any date before 1/1/1970.  *Most* modern compilers solve the
+ *   problem once and for all (for 300 billion years in either direction of
+ *   1/1/1970, anyway - enough to represent literally all of eternity in most
+ *   current cosmological models) by defining time_t as a signed 64-bit int.
+ *   But some compilers stubbornly stick to the old 32-bit time_t even in
+ *   newer versions, for the sake of compatibility with older code that might
+ *   be lax about mixing time_t's with ordinary int's.  E.g., MSVC2003 does
+ *   this.  Fortunately, some of these compilers (such as MSVC2003 again)
+ *   also define a parallel, transitional set of 64-bit time functions that
+ *   you can use by replacing all references to the standard time_t and
+ *   related names with the corresponding 64-bit names.
+ *   
+ *   We'd really like to use a 64-bit time_t wherever we can - the TADS
+ *   release cycle can be a bit slow, and we don't want 2038 to sneak up on
+ *   us and catch us unawares.  So for those compilers that offer a choice of
+ *   32 or 64 bits, we'd like to select the 64 bit version.  To facilitate
+ *   this, we define covers here for the time.h types and functions that we
+ *   use.  On platforms where the regular time_t is already 64 bits, or where
+ *   there's no 64-bit option at all, you can simply do nothing - the
+ *   defaults defined here use the standard time_t typedef and functions, so
+ *   that's what you'll get if you don't define these in the OS-specific
+ *   headers for your platform.  For compilers that provide both a 32-bit
+ *   time_t and a 64-bit other_time_t, the OS headers should #define these
+ *   macros in terms of those compiler-specific 64-bit names.
+ */
+#ifndef os_time_t
+# define os_time_t        TimeDate
+# define os_gmtime(t)     gmtime(t)
+# define os_localtime(t)  localtime(t)
+# define os_time(t)       time(t)
+#endif
+
+/*
+ *   Initialize the time zone.  This routine is meant to take care of any
+ *   work that needs to be done prior to calling localtime() and other
+ *   time-zone-dependent routines in the run-time library.  For DOS and
+ *   Windows, we need to call the run-time library routine tzset() to set up
+ *   the time zone from the environment; most systems shouldn't need to do
+ *   anything in this routine.  It's sufficient to call this once during the
+ *   process lifetime, since it's meant to perform static initialization that
+ *   lasts as long as the process is running.
+ */
+#ifndef os_tzset
+void os_tzset(void);
+#endif
+
+/*
+ *   Higher-precision time.  This retrieves the same time information as
+ *   os_time() (i.e., the elapsed time since the standard Unix Epoch, January
+ *   1, 1970 at midnight UTC), but retrieves it with the highest precision
+ *   available on the local system, up to nanosecond precision.  If less
+ *   precision is available, that's fine; just return the time to the best
+ *   precision available, but expressed in terms of the number of
+ *   nanoseconds.  For example, if you can retrieve milliseconds, you can
+ *   convert that to nanoseconds by multiplying by 1,000,000.
+ *   
+ *   On return, fills in '*seconds' with the number of whole seconds since
+ *   the Epoch, and fills in '*nanoseconds' with the fractional portion,
+ *   expressed in nanosceconds.  Note that '*nanoseconds' is merely the
+ *   fractional portion of the time, so 0 <= *nanoseconds < 1000000000.
+ */
+void os_time_ns(os_time_t *seconds, long *nanoseconds);
+
+/*
+ *   Get the local time zone name, as a location name in the IANA zoneinfo
+ *   database.  For example, locations using US Pacific Time should return
+ *   "America/Los_Angeles".
+ *   
+ *   Returns true if successful, false if not.  If the local operating system
+ *   doesn't have a way to obtain this information, or if it's not available
+ *   in the local machine's configuration, this returns false.
+ *   
+ *   The zoneinfo database is also known as the Olson or TZ (timezone)
+ *   database; it's widely used on Unix systems as the definitive source of
+ *   local time zone settings.  See http://www.iana.org/time-zones for more
+ *   information.
+ *   
+ *   On many Unix systems, the TZ environment variable contains the zoneinfo
+ *   zone name when its first character is ':'.  Windows uses a proprietary
+ *   list of time zone names that can be mapped to zoneinfo names via a
+ *   hand-coded list (such a list is maintained in the Unicode CLDR; our
+ *   Windows implementation uses the CLDR list to generate the mapping).
+ *   MacOS X uses zoneinfo keys directly; /etc/localtime is a link to the
+ *   zoneinfo file for the local zone as set via the system preferences.
+ *   
+ *   os_tzset() must be invoked at some point before this routine is called.
+ */
+int os_get_zoneinfo_key(char *buf, size_t buflen);
+
+/*
+ *   Get a description of the local time zone.  Fills in '*info' with the
+ *   available information.  Returns true on success, false on failure.
+ *   
+ *   See osstzprs.h/.c for a portable implementation of a parser for
+ *   POSIX-style TZ strings.  That can serve as a full implementation of this
+ *   function for systems that use the POSIX TZ environment variable syntax
+ *   to specify the timezone.  (That routine simply parses a string from any
+ *   source, so it can be used to parse the TZ syntax even on systems where
+ *   the string comes from somewhere other than the TZ environment variable.)
+ *   
+ *   os_tzset() must be invoked at some point before this routine is called.
+ *   
+ *   The following two structures are used for the return information:
+ *   
+ *   os_tzrule_t - Timezone Rule structure.  This describes a rule for an
+ *   annual transition between daylight savings time and standard time in a
+ *   time zone.  Most timezones that have recurring standard/daylight changes
+ *   require two of these rules, one for switching to daylight time in the
+ *   spring and one for switching to standard time in the fall.
+ *   
+ *   os_tzinfo_t - Timezone Information structure.  This describes a
+ *   timezone's clock settings, name(s), and rules for recurring annual
+ *   changes between standard time and daylight time, if applicable.
+ */
+struct os_tzrule_t
+{
+    /* 
+     *   Day of year, 1-365, NEVER counting Feb 29; set to 0 if not used.
+     *   Corresponds to the "J" format in Unix TZ strings.  (Called "Julian
+     *   day" in the POSIX docs, thus the "J", even though it's a bit of a
+     *   misnomer.)(Because of the invariance of the mapping from J-number to
+     *   date, this is just an obtuse way of specifying a month/day date.
+     *   But even so, we'll let the OS layer relay this back to us in
+     *   J-number format and count on the portable caller to work out the
+     *   date, rather than foisting that work on each platform
+     *   implementation.)
+     */
+    int jday;
+
+    /*
+     *   Day of year, 1-366, counting Feb 29 on leap years; set to 0 if not
+     *   used; ignored if 'jday' is nonzero.  This corresponds to the Julian
+     *   day sans "J" in TZ strings (almost - that TZ format uses 0-365 as
+     *   its range, so bump it up by one when parsing a TZ string).  This
+     *   format is even more obtuse than the J-day format, in that it doesn't
+     *   even have an invariant month/day mapping (not after day 59, anyway -
+     *   day 60 is either February 29 or March 1, depending on the leapness
+     *   of the year, and every day after that is similarly conditional).  As
+     *   far as I can tell, no one uses this option, so I'm not sure why it
+     *   exists.  The zoneinfo source format doesn't have a way to represent
+     *   it, which says to me that no one has ever used it in a statutory DST
+     *   start/end date definition in the whole history of time zones around
+     *   the world, since the whole history of time zones around the world is
+     *   exactly what the zoneinfo database captures in exhaustive and
+     *   painstaking detail.  If anyone had ever used it in defining a time
+     *   zone, zoneinfo would have an option for it.  My guess is that it's a
+     *   fossilized bug from some early C RTL that's been retained out of an
+     *   abundance of caution vis-a-vis compatibility, and was entirely
+     *   replaced in practice by the J-number format as soon as someone
+     *   noticed the fiddly leap year behavior.  But for the sake of
+     *   completeness...
+     */
+    int yday;
+    
+    /* 
+     *   The month (1-12), week of the month, and day of the week (1-7 for
+     *   Sunday to Saturday).  Week 1 is the first week in which 'day'
+     *   occurs, week 2 is the second, etc.; week 5 is the last occurrence of
+     *   'day' in the month.  These fields are used for "second Sunday in
+     *   March" types of rules.  Set these to zero if they're not used;
+     *   they're ignored in any case if 'jday' or 'yday' are non-zero.
+     */
+    int month;
+    int week;
+    int day;
+
+    /* time of day, in seconds after midnight (e.g., 2AM is 120 == 2*60*60) */
+    int time;
+};
+struct os_tzinfo_t
+{
+    /*
+     *   The local offset from GMT, in seconds, for standard time and
+     *   daylight time in this zone.  These values are positive for zones
+     *   east of GMT and negative for zones west: New York standard time
+     *   (EST) is 5 hours west of GMT, so its offset is -5*60*60.
+     *   
+     *   Set both of these fields (if possible) regardless of whether
+     *   standard or daylight time is currently in effect in the zone.  The
+     *   caller will select which offset to use based on the start/end rules,
+     *   or based on the 'is_dst' flag if no rules are available.
+     *   
+     *   If it's only possible to determine the current wall clock offset, be
+     *   it standard or daylight time, and it's not possible to determine the
+     *   time difference between the two, simply set both of these to the
+     *   current offset.  This information isn't available from the standard
+     *   C library, and many OS APIs also lack it.  
+     */
+    int32_t std_ofs;
+    int32_t dst_ofs;
+
+    /*
+     *   The abbreviations for the local zone's standard time and daylight
+     *   time, respectively, when displaying date/time values.  E.g., "EST"
+     *   and "EDT" for US Eastern Time.  If the zone doesn't observe daylight
+     *   time (it's on standard time year round), set dst_abbr to an empty
+     *   string.
+     *   
+     *   As with std_ofs and dst_ofs, you can set both of these to the same
+     *   string if it's only possible to determine the one that's currently
+     *   in effect.
+     */
+    char std_abbr[16];
+    char dst_abbr[16];
+
+    /*
+     *   The ongoing rules for switching between daylight and standard time
+     *   in this zone, if available.  'dst_start' is the date when daylight
+     *   savings starts, 'dst_end' is the date when standard time resumes.
+     *   Set all fields to 0 if the start/stop dates aren't available, or the
+     *   zone is on standard time year round.
+     */
+    struct os_tzrule_t dst_start;
+    struct os_tzrule_t dst_end;
+
+    /* 
+     *   True -> the zone is CURRENTLY on daylight savings time; false means
+     *   it's currently on standard time.
+     *   
+     *   This is only used if the start/end rules aren't specified.  In the
+     *   absence of start/end rules, there's no way to know when the current
+     *   standard/daylight phase ends, so we'll have to assume that the
+     *   current mode is in effect permanently.  In this case, the caller
+     *   will use only be able to use the offset and abbreviation for the
+     *   current mode and will have to ignore the other one.
+     */
+    int is_dst;
+};
+int os_get_timezone_info(struct os_tzinfo_t *info);
+
+
+/*
+ *   Get the current system high-precision timer.  This function returns a
+ *   value giving the wall-clock ("real") time in milliseconds, relative to
+ *   any arbitrary zero point.  It doesn't matter what this value is relative
+ *   to -- the only important thing is that the values returned by two
+ *   different calls should differ by the number of actual milliseconds that
+ *   have elapsed between the two calls.  This might be the number of
+ *   milliseconds since the computer was booted, since the current user
+ *   logged in, since midnight of the previous night, since the program
+ *   started running, since 1-1-1970, etc - it doesn't matter what the epoch
+ *   is, so the implementation can use whatever's convenient on the local
+ *   system.
+ *   
+ *   True millisecond precision isn't required.  Each implementation should
+ *   simply use the best precision available on the system.  If your system
+ *   doesn't have any kind of high-precision clock, you can simply use the
+ *   time() function and multiply the result by 1000 (but see the note below
+ *   about exceeding 32-bit precision).
+ *   
+ *   However, it *is* required that the return value be in *units* of
+ *   milliseconds, even if your system clock doesn't have that much
+ *   precision; so on a system that uses its own internal clock units, this
+ *   routine must multiply the clock units by the appropriate factor to yield
+ *   milliseconds for the return value.
+ *   
+ *   It is also required that the values returned by this function be
+ *   monotonically increasing.  In other words, each subsequent call must
+ *   return a value that is equal to or greater than the value returned from
+ *   the last call.  On some systems, you must be careful of two special
+ *   situations.
+ *   
+ *   First, the system clock may "roll over" to zero at some point; for
+ *   example, on some systems, the internal clock is reset to zero at
+ *   midnight every night.  If this happens, you should make sure that you
+ *   apply a bias after a roll-over to make sure that the value returned from
+ *   this return continues to increase despite the reset of the system clock.
+ *   
+ *   Second, a 32-bit signed number can only hold about twenty-three days
+ *   worth of milliseconds.  While it seems unlikely that a TADS game would
+ *   run for 23 days without a break, it's certainly reasonable to expect
+ *   that the computer itself may run this long without being rebooted.  So,
+ *   if your system uses some large type (a 64-bit number, for example) for
+ *   its high-precision timer, you may want to store a zero point the very
+ *   first time this function is called, and then always subtract this zero
+ *   point from the large value returned by the system clock.  If you're
+ *   using time(0)*1000, you should use this technique, since the result of
+ *   time(0)*1000 will almost certainly not fit in 32 bits in most cases.  
+ */
+long os_get_sys_clock_ms(void);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Hardware Configuration.  Define the following functions appropriately
+ *   for your hardware.  For efficiency, these functions should be defined
+ *   as macros if possible.
+ *   
+ *   Note that these hardware definitions are independent of the OS, at
+ *   least to the extent that your OS can run on multiple types of
+ *   hardware.  So, rather than combining these definitions into your
+ *   osxxx.h header file, we recommend that you put these definitions in a
+ *   separate h_yyy.h header file, which can be configured into os.h with
+ *   an appropriate "_M_yyy" preprocessor symbol.  Refer to os.h for
+ *   details of configuring the hardware include file.  
+ */
+
+/* 
+ *   Round a size up to worst-case alignment boundary.  For example, on a
+ *   platform where the largest type must be aligned on a 4-byte boundary,
+ *   this should round the value up to the next higher mutliple of 4 and
+ *   return the result.  
+ */
+/* size_t osrndsz(size_t siz); */
+
+/* 
+ *   Round a pointer up to worst-case alignment boundary. 
+ */
+/* void *osrndpt(void *ptr); */
+
+/* 
+ *   Read an unaligned portable unsigned 2-byte value, returning an int
+ *   value.  The portable representation has the least significant byte
+ *   first, so the value 0x1234 is represented as the byte 0x34, followed
+ *   by the byte 0x12.
+ *   
+ *   The source value must be treated as unsigned, but the result is
+ *   signed.  This is significant on 32- and 64-bit platforms, because it
+ *   means that the source value should never be sign-extended to 32-bits.
+ *   For example, if the source value is 0xffff, the result is 65535, not
+ *   -1.  
+ */
+/* int osrp2(unsigned char *p); */
+
+/* 
+ *   Read an unaligned portable signed 2-byte value, returning int.  This
+ *   differs from osrp2() in that this function treats the source value as
+ *   signed, and returns a signed result; hence, on 32- and 64-bit
+ *   platforms, the result must be sign-extended to the int size.  For
+ *   example, if the source value is 0xffff, the result is -1.  
+ */
+/* int osrp2s(unsigned char *p); */
+
+/* 
+ *   Write unsigned int to unaligned portable 2-byte value.  The portable
+ *   representation stores the low-order byte first in memory, so
+ *   oswp2(0x1234) should result in storing a byte value 0x34 in the first
+ *   byte, and 0x12 in the second byte.  
+ */
+/* void oswp2(unsigned char *p, unsigned int i); */
+
+/*
+ *   Write signed int to unaligned portable 2-byte value.  Negative values
+ *   must be stored in two's complement notation.  E.g., -1 is stored as
+ *   FF.FF, -32768 is stored as 00.80 (little-endian).  
+ *   
+ *   Virtually all modern hardware uses two's complement notation as the
+ *   native representation, which makes this routine a trivial synonym of
+ *   osrp2() (i.e., #define oswp2s(p,i) oswp2(p,i)).  We distinguish the
+ *   signed version on the extremely off chance that TADS is ever ported to
+ *   wacky hardware with a different representation for negative integers
+ *   (one's complement, sign bit, etc).  
+ */
+/* void oswp2s(unsigned char *p, int i); */
+
+/* 
+ *   Read an unaligned unsigned portable 4-byte value, returning long.  The
+ *   underlying value should be considered signed, and the result is signed.
+ *   The portable representation stores the bytes starting with the least
+ *   significant: the value 0x12345678 is stored with 0x78 in the first byte,
+ *   0x56 in the second byte, 0x34 in the third byte, and 0x12 in the fourth
+ *   byte.
+ */
+/* unsigned long osrp4(unsigned char *p); */
+
+/*
+ *   Read an unaligned signed portable 4-byte value, returning long. 
+ */
+/* long osrp4s(unsigned char *p); */
+
+/* 
+ *   Write an unsigned long to an unaligned portable 4-byte value.  The
+ *   portable representation stores the low-order byte first in memory, so
+ *   0x12345678 is written to memory as 0x78, 0x56, 0x34, 0x12.  
+ */
+/* void oswp4(unsigned char *p, unsigned long l); */
+
+/*
+ *   Write a signed long, using little-endian byte order and two's complement
+ *   notation for negative numbers.  This is a trivial synonym for oswp4()
+ *   for all platforms with native two's complement arithmetic (which is
+ *   virtually all modern platforms).  See oswp2s() for more discussion.
+ */
+/* void oswp4s(unsigned char *p, long l); */
+
+/*
+ *   For convenience and readability, the 1-byte integer (signed and
+ *   unsigned) equivalents of the above.
+ */
+#define osrp1(p) (*(unsigned char *)(p))
+#define osrp1s(p) (*(signed char *)(p))
+#define oswp1(p, b) (*(unsigned char *)(p) = (b))
+#define oswp1s(p, b) (*(signed char *)(p) = (b))
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   varargs va_copy() extension.
+ *   
+ *   On some compilers, va_list is a reference type.  This means that if a
+ *   va_list value is passed to a function that uses va_arg() to step through
+ *   the referenced arguments, the caller's copy of the va_list might be
+ *   updated on return.  This is problematic in cases where the caller needs
+ *   to use the va_list again in another function call, since the va_list is
+ *   no longer pointing to the first argument for the second call.  C99 has a
+ *   solution in the form of the va_copy() macro.  Unfortunately, this isn't
+ *   typically available in pre-C99 compilers, and isn't standard in *any*
+ *   C++ version.  We thus virtualize it here in a macro.
+ *   
+ *   os_va_copy() has identical semantics to C99 va_copy().  A matching call
+ *   to os_va_copy_end() must be made for each call to os_va_copy() before
+ *   the calling function returns; this has identical semantics to C99
+ *   va_end().
+ *   
+ *   Because our semantics are identical to the C99 version, we provide a
+ *   default definition here for compilers that define va_copy().  Platform
+ *   headers must provide suitable definitions only if their compilers don't
+ *   have va_copy().  We also provide a definition for GCC compilers that
+ *   define the private __va_copy macro, which also has the same semantics.
+ */
+#ifdef va_copy
+# define os_va_copy(dst, src) va_copy(dst, src)
+# define os_va_copy_end(dst)  va_end(dst)
+#else
+# if defined(__GNUC__) && defined(__va_copy)
+#  define os_va_copy(dst, src) __va_copy(dst, src)
+#  define os_va_copy_end(dst)  va_end(dst)
+# endif
+#endif
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Platform Identifiers.  You must define the following macros in your
+ *   osxxx.h header file:
+ *   
+ *   OS_SYSTEM_NAME - a string giving the system identifier.  This string
+ *   must contain only characters that are valid in a TADS identifier:
+ *   letters, numbers, and underscores; and must start with a letter or
+ *   underscore.  For example, on MS-DOS, this string is "MSDOS".
+ *   
+ *   OS_SYSTEM_LDESC - a string giving the system descriptive name.  This
+ *   is used in messages displayed to the user.  For example, on MS-DOS,
+ *   this string is "MS-DOS".  
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Message Linking Configuration.  You should #define ERR_LINK_MESSAGES
+ *   in your osxxx.h header file if you want error messages linked into
+ *   the application.  Leave this symbol undefined if you want an external
+ *   message file. 
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Program Exit Codes.  These values are used for the argument to exit()
+ *   to conform to local conventions.  Define the following values in your
+ *   OS-specific header:
+ *   
+ *   OSEXSUCC - successful completion.  Usually defined to 0.
+ *.  OSEXFAIL - failure.  Usually defined to 1.  
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Basic memory management interface.  These functions are merely
+ *   documented here, but no prototypes are defined, because most
+ *   platforms #define macros for these functions and types, mapping them
+ *   to malloc or other system interfaces.  
+ */
+
+/*
+ *   Theoretical maximum osmalloc() size.  This may be less than the
+ *   capacity of the argument to osmalloc() on some systems.  For example,
+ *   on segmented architectures (such as 16-bit x86), memory is divided into
+ *   segments, so a single memory allocation can allocate only a subset of
+ *   the total addressable memory in the system.  This value thus specifies
+ *   the maximum amount of memory that can be allocated in one chunk.
+ *   
+ *   Note that this is an architectural maximum for the hardware and
+ *   operating system.  It doesn't have anything to do with the total amount
+ *   of memory actually available at run-time.
+ *   
+ *   #define OSMALMAX to a constant long value with theoretical maximum
+ *   osmalloc() argument value.  For a platform with a flat (unsegmented)
+ *   32-bit memory space, this is usually 0xffffffff; for 16-bit platforms,
+ *   this is usually 0xffff.  
+ */
+/* #define OSMALMAX 0xffffffff */
+
+/*   
+ *   Allocate a block of memory of the given size in bytes.  The actual
+ *   allocation may be larger, but may be no smaller.  The block returned
+ *   should be worst-case aligned (i.e., suitably aligned for any type).
+ *   Return null if the given amount of memory is not available.  
+ */
+/* void *osmalloc(size_t siz); */
+
+/*
+ *   Free memory previously allocated with osmalloc().  
+ */
+/* void osfree(void *block); */
+
+/* 
+ *   Reallocate memory previously allocated with osmalloc() or
+ *   osrealloc(), changing the block's size to the given number of bytes.
+ *   If necessary, a new block at a different address can be allocated, in
+ *   which case the data from the original block is copied (the lesser of
+ *   the old block size and the new size is copied) to the new block, and
+ *   the original block is freed.  If the new size is less than the old
+ *   size, this need not do anything at all, since the returned block can
+ *   be larger than the new requested size.  If the block cannot be
+ *   enlarged to the requested size, return null.  
+ */
+/* void *osrealloc(void *block, size_t siz); */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Basic file I/O interface.  These functions are merely documented here,
+ *   but no prototypes are defined, because most platforms #define macros for
+ *   these functions and types, mapping them to stdio or other system I/O
+ *   interfaces.  
+ *   
+ *   When writing a file, writes might or might not be buffered in
+ *   application memory; this is up to the OS implementation, which can
+ *   perform buffering according to local conventions and what's most
+ *   efficient.  However, it shouldn't make any difference to the caller
+ *   whether writes are buffered or not - the OS implementation must take
+ *   care that any buffering is invisible to the app.  (Porters: note that
+ *   the basic C stdio package has the proper behavior here, so you'll get
+ *   the correct semantics if you use a simple stdio implementation.)
+ *   
+ *   Write buffering might be visible to *other* apps, though.  In
+ *   particular, another process might not see data written to a file (with
+ *   osfwb(), os_fprint(), etc) immediately, since the write functions might
+ *   hold the written bytes in an internal memory buffer rather than sending
+ *   them to the OS.  Any internal buffers are guaranteed to be flushed to
+ *   the OS upon calling osfcls() or osfflush().  Note that it's never
+ *   *necessary* to call osfflush(), because buffered data will always be
+ *   flushed on closing the file with osfcls().  However, if you want other
+ *   apps to be able to see updates immediately, you can use osfflush() to
+ *   ensure that buffers are flushed to a file before you close it.
+ *   
+ *   You can also use osfflush() to check for buffered write errors.  When
+ *   you use osfwb() or other write functions to write data, they will return
+ *   a success indication even if the data was only copied into a buffer.
+ *   This means that a write that appeared to succeed might actually fail
+ *   later, when the buffer is flushed.  The only way to know for sure is to
+ *   explicitly flush buffers using osfflush(), and check the result code.
+ *   If the original write function and a subsequent osfflush() *both* return
+ *   success indications, then the write has definitely succeeded.  
+ */
+
+
+/*
+ *   Define the following values in your OS header to indicate local
+ *   file/path syntax conventions:
+ *   
+ *   OSFNMAX - integer indicating maximum length of a filename
+ *   
+ *   OSPATHCHAR - character giving the normal path separator character
+ *.  OSPATHALT - string giving other path separator characters
+ *.  OSPATHURL - string giving path separator characters for URL conversions
+ *.  OSPATHSEP - directory separator for PATH-style environment variables
+ *.  OSPATHPWD - string giving the special path representing the current
+ *.              working directory; for Unix or Windows, this is "."
+ *   
+ *   OSPATHURL is a little different: this specifies the characters that
+ *   should be converted to URL-style separators when converting a path from
+ *   local notation to URL notation.  This is usually the same as the union
+ *   of OSPATHCHAR and OSPATHALT, but need not be; for example, on DOS, the
+ *   colon (':') is a path separator for most purposes, but is NOT a path
+ *   character for URL conversions.
+ */
+
+/*
+ *   Define the type osfildef as the appropriate file handle structure for
+ *   your osfxxx functions.  This type is always used as a pointer, but
+ *   the value is always obtained from an osfopxxx call, and is never
+ *   synthesized by portable code, so you can use essentially any type
+ *   here that you want.
+ *   
+ *   For platforms that use C stdio functions to implement the osfxxx
+ *   functions, osfildef can simply be defined as FILE.
+ */
+/* typedef FILE osfildef; */
+
+
+/*
+ *   File types.
+ *   
+ *   These are symbols of the form OSFTxxxx defining various content types,
+ *   somewhat aking to MIME types.  These were mainly designed for the old
+ *   Mac OS (versions up to OS 9), where the file system stored a type tag
+ *   with each file's metadata.  The type tags were used for things like
+ *   filtering file selector dialogs and setting file-to-app associations in
+ *   the desktop shell.
+ *   
+ *   Our OSFTxxx symbols are abstract file types that we define, for types
+ *   used within the TADS family of applications.  They give us a common,
+ *   cross-platform reference point for each type we use.  Each port where
+ *   file types are meaningful then maps our abstract type IDs to the
+ *   corresponding port-specific type IDs.  In practice, this has never been
+ *   used anywhere other than the old Mac OS ports; in fact, it's not even
+ *   used in the modern Mac OS (OS X and later), since Apple decided to stop
+ *   fighting the tide and start using filename suffixes for this sort of
+ *   tagging, like everyone else always has.
+ *   
+ *   For the list of file types, see osifctyp.h 
+ */
+
+
+/*
+ *   Local newline convention.
+ *   
+ *   Because of the pernicious NIH ("Not Invented Here") cultures of the
+ *   major technology vendors, basically every platform out there has its own
+ *   unique way of expressing newlines in text files.  Unix uses LF (ASCII
+ *   10); Mac uses CR (ASCII 13); DOS and Windows use CR-LF pairs.  In the
+ *   past there were heaven-only-knows how many other conventions in use, but
+ *   fortunately these three have the market pretty well locked up at this
+ *   point.  But we do still have to worry about these three.
+ *   
+ *   Our strategy on input is to be open to just about anything whenever
+ *   possible.  So, when we're reading something that we believe to be a text
+ *   file, we'll treat all of these as line endings: CR, LF, CR-LF, and
+ *   LF-CR.  It's pretty safe to do this; if we have a CR and LF occurring
+ *   adjacently, it's almost certain that they're intended to be taken
+ *   together as a single newline sequence.  Likewise, if there's a lone CR
+ *   or LF, it's rare for it to mean anything other than a newline.
+ *   
+ *   On output, though, we can't be as loose.  The problem is that other
+ *   applications on our big three platforms *don't* tend to aim for the same
+ *   flexibility we do on input: other apps usually expect exactly the local
+ *   conventions on input, and don't always work well if they don't get it.
+ *   So it's important that when we're writing a text file, we write newlines
+ *   in the local convention.  This means that we sometimes need to know what
+ *   the local convention actually is.  That's where this definition comes
+ *   in.
+ *   
+ *   Each port must define OS_NEWLINE_SEQ as an ASCII string giving the local
+ *   newline sequence to write on output.  For example, DOS defines it as
+ *   "\r\n" (CR-LF).  Always define it as a STRING (not a character
+ *   constant), even if it's only one character long.
+ *   
+ *   (Note that some compilers use wacky mappings for \r and \n.  Some older
+ *   Mac compilers, for example, defined \n as CR and \r as LF, because of
+ *   the Mac convention where newline is represented as CR in a text file.
+ *   If there's any such variability on your platform, you can always use the
+ *   octal codes to be unambiguous: \012 for LF and \015 for CR.)  
+ */
+/* #define OS_NEWLINE_SEQ  "\r\n" */
+
+
+
+/* 
+ *   Open text file for reading.  This opens the file with read-only access;
+ *   we're not allowed to write to the file using this handle.  Returns NULL
+ *   on error.
+ *   
+ *   A text file differs from a binary file in that some systems perform
+ *   translations to map between C conventions and local file system
+ *   conventions; for example, on DOS, the stdio library maps the DOS CR-LF
+ *   newline convention to the C-style '\n' newline format.  On many systems
+ *   (Unix, for example), there is no distinction between text and binary
+ *   files.
+ *   
+ *   On systems that support file sharing and locking, this should open the
+ *   file in "shared read" mode - this means that other processes are allowed
+ *   to simultaneously read from the file, but no other processs should be
+ *   allowed to write to the file as long as we have it open.  If another
+ *   process already has the file open with write access, this routine should
+ *   return failure, since we can't take away the write privileges the other
+ *   process already has and thus we can't guarantee that other processes
+ *   won't write to the file while we have it open.  
+ */
+/* osfildef *osfoprt(const char *fname, os_filetype_t typ); */
+
+/*
+ *   Open a text file for "volatile" reading: we open the file with read-only
+ *   access, and we explicitly accept instability in the file's contents due
+ *   to other processes simultaneously writing to the file.  On systems that
+ *   support file sharing and locking, the file should be opened in "deny
+ *   none" mode, meaning that other processes can simultaneously open the
+ *   file for reading and/or writing even while have the file open.  
+ */
+/* osfildef *osfoprtv(const char *fname, os_filetype_t typ); */
+
+/* 
+ *   Open text file for writing; returns NULL on error.  If the file already
+ *   exists, this truncates the file to zero length, deleting any existing
+ *   contents.
+ */
+/* osfildef *osfopwt(const char *fname, os_filetype_t typ); */
+
+/*
+ *   Open text file for reading and writing, keeping the file's existing
+ *   contents if the file already exists or creating a new file if no such
+ *   file exists.  Returns NULL on error. 
+ */
+/* osfildef *osfoprwt(const char *fname, os_filetype_t typ); */
+
+/* 
+ *   Open text file for reading/writing.  If the file already exists,
+ *   truncate the existing contents to zero length.  Create a new file if it
+ *   doesn't already exist.  Return null on error.  
+ */
+/* osfildef *osfoprwtt(const char *fname, os_filetype_t typ); */
+
+/* 
+ *   Open binary file for writing; returns NULL on error.  If the file
+ *   exists, this truncates the existing contents to zero length.
+ */
+/* osfildef *osfopwb(const char *fname, os_filetype_t typ); */
+
+/* 
+ *   Open source file for reading - use the appropriate text or binary
+ *   mode.  
+ */
+/* osfildef *osfoprs(const char *fname, os_filetype_t typ); */
+
+/* 
+ *   Open binary file for reading; returns NULL on error.  
+ */
+/* osfildef *osfoprb(const char *fname, os_filetype_t typ); */
+
+/*
+ *   Open binary file for 'volatile' reading; returns NULL on error.
+ *   ("Volatile" means that we'll accept writes from other processes while
+ *   reading, so the file should be opened in "deny none" mode or the
+ *   equivalent, to the extent that the local system supports file sharing
+ *   modes.)  
+ */
+/* osfildef *osfoprbv(const char *fname, os_filetype_t typ); */
+
+/* 
+ *   Open binary file for random-access reading/writing.  If the file already
+ *   exists, keep the existing contents; if the file doesn't already exist,
+ *   create a new empty file.
+ *   
+ *   The caller is allowed to perform any mixture of read and write
+ *   operations on the returned file handle, and can seek around in the file
+ *   to read and write at random locations.
+ *   
+ *   If the local file system supports file sharing or locking controls, this
+ *   should generally open the file in something equivalent to "exclusive
+ *   write, shared read" mode ("deny write" in DENY terms), so that other
+ *   processes can't modify the file at the same time we're modifying it (but
+ *   it doesn't bother us to have other processes reading from the file while
+ *   we're working on it, as long as they don't mind that we could change
+ *   things on the fly).  It's not absolutely necessary to assert these
+ *   locking semantics, but if there's an option to do so this is preferred.
+ *   Stricter semantics (such as "exclusive" or "deny all" mode) are better
+ *   than less strict semantics.  Less strict semantics are dicey, because in
+ *   that case the caller has no way of knowing that another process could be
+ *   modifying the file at the same time, and no way (through osifc) of
+ *   coordinating that activity.  If less strict semantics are implemented,
+ *   the caller will basically be relying on luck to avoid corruptions due to
+ *   writing by other processes.
+ *   
+ *   Return null on error.  
+ */
+/* osfildef *osfoprwb(const char *fname, os_filetype_t typ); */
+
+/* 
+ *   Open binary file for random-access reading/writing.  If the file already
+ *   exists, truncate the existing contents (i.e., delete the contents of the
+ *   file, resetting it to a zero-length file).  Create a new file if it
+ *   doesn't already exist.  The caller is allowed to perform any mixture of
+ *   read and write operations on the returned handle, and can seek around in
+ *   the file to read and write at random locations.
+ *   
+ *   The same comments regarding sharing/locking modes for osfoprwb() apply
+ *   here as well.
+ *   
+ *   Return null on error.  
+ */
+/* osfildef *osfoprwtb(const char *fname, os_filetype_t typ); */
+
+/*
+ *   Duplicate a file handle.  Returns a new osfildef* handle that accesses
+ *   the same open file as an existing osfildef* handle.  The new handle is
+ *   independent of the original handle, with its own seek position,
+ *   buffering, etc.  The new handle and the original handle must each be
+ *   closed separately when the caller is done with them (closing one doesn't
+ *   close the other).  The effect should be roughly the same as the Unix
+ *   dup() function.
+ *   
+ *   On success, returns a new, non-null osfildef* handle duplicating the
+ *   original handle.  Returns null on failure.
+ *   
+ *   'mode' is a simplified stdio fopen() mode string.  The first
+ *   character(s) indicate the access type: "r" for read access, "w" for
+ *   write access, or "r+" for read/write access.  Note that "w+" mode is
+ *   specifically not defined, since the fopen() handling of "w+" is to
+ *   truncate any existing file contents, which is not desirable when
+ *   duplicating a handle.  The access type can optionally be followed by "t"
+ *   for text mode, "s" for source file mode, or "b" for binary mode, with
+ *   the same meanings as for the various osfop*() functions.  The default is
+ *   't' for text mode if none of these are specified.
+ *   
+ *   If the osfop*() functions are implemented in terms of stdio FILE*
+ *   objects, this can be implemented as fdopen(dup(fileno(orig)), mode), or
+ *   using equivalents if the local stdio library uses different names for
+ *   these functions.  Note that "s" (source file format) isn't a stdio mode,
+ *   so implementations must translate it to the appropriate "t" or "b" mode.
+ *   (For that matter, "t" and "b" modes aren't universally supported either,
+ *   so some implementations may have to translate these, or more likely
+ *   simply remove them, as most platforms don't distinguish text and binary
+ *   modes anyway.)
+ */
+osfildef *osfdup(osfildef *orig, const char *mode);
+
+/* 
+ *   Set a file's type information.  This is primarily for implementations on
+ *   Mac OS 9 and earlier, where the file system keeps file-type metadata
+ *   separate from the filename.  On such systems, this can be used to set
+ *   the type metadata after a file is created.  The system should map the
+ *   os_filetype_t values to the actual metadata values on the local system.
+ *   On most systems, there's no such thing as file-type metadata, in which
+ *   case this function should simply be stubbed out with an empty function.
+ */
+void os_settype(const char *f, os_filetype_t typ);
+
+/* open the error message file for reading */
+osfildef *oserrop(const char *arg0);
+
+/* 
+ *   Get a line of text from a text file.  Uses fgets semantics.  
+ */
+/* char *osfgets(char *buf, size_t len, osfildef *fp); */
+
+/* 
+ *   Write a line of text to a text file.  Uses fputs semantics.  
+ */
+/* void osfputs(const char *buf, osfildef *fp); */
+
+/*
+ *   Write to a text file.  os_fprintz() takes a null-terminated string,
+ *   while os_fprint() takes an explicit separate length argument that might
+ *   not end with a null terminator.  
+ */
+void os_fprintz(osfildef *fp, const char *str);
+void os_fprint(osfildef *fp, const char *str, size_t len);
+
+/* 
+ *   Write bytes to file.  Return 0 on success, non-zero on error.
+ */
+/* int osfwb(osfildef *fp, const void *buf, int bufl); */
+
+/*
+ *   Flush buffered writes to a file.  This ensures that any bytes written to
+ *   the file (with osfwb(), os_fprint(), etc) are actually sent out to the
+ *   operating system, rather than being buffered in application memory for
+ *   later writing.
+ *   
+ *   Note that this routine only guarantees that we write through to the
+ *   operating system.  This does *not* guarantee that the data will actually
+ *   be committed to the underlying physical storage device.  Such a
+ *   guarantee is hard to come by in general, since most modern systems use
+ *   multiple levels of software and hardware buffering - the OS might buffer
+ *   some data in system memory, and the physical disk drive might itself
+ *   buffer data in its own internal cache.  This routine thus isn't good
+ *   enough, for example, to protect transactional data that needs to survive
+ *   a power failure or a serious system crash.  What this routine *does*
+ *   ensure is that buffered data are written through to the OS; in
+ *   particular, this ensures that another process that's reading from the
+ *   same file will see all updates we've made up to this point.
+ *   
+ *   Returns 0 on success, non-zero on error.  Errors can occur for any
+ *   reason that they'd occur on an ordinary write - a full disk, a hardware
+ *   failure, etc.  
+ */
+/* int osfflush(osfildef *fp); */
+
+/* 
+ *   Read a character from a file.  Provides the same semantics as fgetc().
+ */
+/* int osfgetc(osfildef *fp); */
+
+/* 
+ *   Read bytes from file.  Return 0 on success, non-zero on error.  
+ */
+/* int osfrb(osfildef *fp, void *buf, int bufl); */
+
+/* 
+ *   Read bytes from file and return the number of bytes read.  0
+ *   indicates that no bytes could be read. 
+ */
+/* size_t osfrbc(osfildef *fp, void *buf, size_t bufl); */
+
+/* 
+ *   Get the current seek location in the file.  The first byte of the
+ *   file has seek position 0.  
+ */
+/* long osfpos(osfildef *fp); */
+
+/* 
+ *   Seek to a location in the file.  The first byte of the file has seek
+ *   position 0.  Returns zero on success, non-zero on error.
+ *   
+ *   The following constants must be defined in your OS-specific header;
+ *   these values are used for the "mode" parameter to indicate where to
+ *   seek in the file:
+ *   
+ *   OSFSK_SET - set position relative to the start of the file
+ *.  OSFSK_CUR - set position relative to the current file position
+ *.  OSFSK_END - set position relative to the end of the file 
+ */
+/* int osfseek(osfildef *fp, long pos, int mode); */
+
+/* 
+ *   Close a file.
+ *   
+ *   If the OS implementation uses buffered writes, this routine guarantees
+ *   that any buffered data are flushed to the underlying file.  So, it's not
+ *   necessary to call osfflush() before calling this routine.  However,
+ *   since this function doesn't return any error indication, a caller could
+ *   use osfflush() first to check for errors on any final buffered writes.  
+ */
+/* void osfcls(osfildef *fp); */
+
+/* 
+ *   Delete a file.  Returns zero on success, non-zero on error. 
+ */
+/* int osfdel(const char *fname); */
+
+/*
+ *   Rename/move a file.  This should apply the usual C rename() behavior.
+ *   Renames the old file to the new name, which may be in a new directory
+ *   location if supported on the local system; moves across devices,
+ *   volumes, file systems, etc may or may not be supported according to the
+ *   local system's rules.  If the new file already exists, results are
+ *   undefined.  Returns true on success, false on failure.
+ */
+/* int os_rename_file(const char *oldname, const char *newname); */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   File "stat()" information - mode, size, time stamps 
+ */
+
+/* 
+ *   Test access to a file - i.e., determine if the file exists.  Returns
+ *   zero if the file exists, non-zero if not.  (The semantics may seem
+ *   backwards, but this is consistent with the conventions used by most of
+ *   the other osfxxx calls: zero indicates success, non-zero indicates an
+ *   error.  If the file exists, "accessing" it was successful, so osfacc
+ *   returns zero; if the file doesn't exist, accessing it gets an error,
+ *   hence a non-zero return code.)  
+ */
+/* int osfacc(const char *fname) */
+
+/*
+ *   Get a file's mode and attribute flags.  This retrieves information on
+ *   the given file equivalent to the st_mode member of the 'struct stat'
+ *   data returned by the Unix stat() family of functions, as well as some
+ *   extra system-specific attributes.  On success, fills in *mode (if mode
+ *   is non-null) with the mode information as a bitwise combination of
+ *   OSFMODE_xxx values, fills in *attr (if attr is non-null) with a
+ *   combination of OSFATTR_xxx attribute flags, and returns true; on
+ *   failure, simply returns false.  Failure can occur if the file doesn't
+ *   exist, can't be accessed due to permissions, etc.
+ *   
+ *   Note that 'mode' and/or 'attr' can be null if the caller doesn't need
+ *   that information.  Implementations must check these parameters for null
+ *   pointers and skip returning the corresponding information if null.
+ *   
+ *   If the file in 'fname' is a symbolic link, the behavior depends upon
+ *   'follow_links'.  If 'follow_links' is true, the function should resolve
+ *   the link reference (and if that points to another link, the function
+ *   resolves that link as well, and so on) and return information on the
+ *   object the link points to.  Otherwise, the function returns information
+ *   on the link itself.  This only applies for symbolic links (not for hard
+ *   links), and only if the underlying OS and file system support this
+ *   distinction; if the OS transparently resolves links and doesn't allow
+ *   retrieving information about the link itself, 'follow_links' can be
+ *   ignored.  Likewise, hard links (on systems that support them) are
+ *   generally indistinguishable from regular files, so this function isn't
+ *   expected to do anything special with them.
+ *   
+ *   The '*mode' value returned is a bitwise combination of OSFMODE_xxx flag.
+ *   Many of the flags are mutually exclusive; for example, "file" and
+ *   "directory" should never be combined.  It's also possible for '*mode' to
+ *   be zero for a valid file; this means that the file is of some special
+ *   type on the local system that doesn't fit any of the OSFMODE_xxx types.
+ *   (If any ports do encounter such cases, we can add OSFMODE_xxx types to
+ *   accommodate new types.  The list below isn't meant to be final; it's
+ *   just what we've encountered so far on the platforms where TADS has
+ *   already been ported.)
+ *   
+ *   The OSFMODE_xxx values are left for the OS to define so that they can be
+ *   mapped directly to the OS API's equivalent constants, if desired.  This
+ *   makes the routine easy to write, since you can simply set *mode directly
+ *   to the mode information the OS returns from its stat() or equivalent.
+ *   However, note that these MUST be defined as bit flags - that is, each
+ *   value must be exactly a power of 2.  Windows and Unix-like systems
+ *   follow this practice, as do most "stat()" functions in C run-time
+ *   libraries, so this usually works automatically if you map these
+ *   constants to OS or C library values.  However, if a port defines its own
+ *   values for these, take care that they're all powers of 2.
+ *   
+ *   Obviously, a given OS might not have all of the file types listed here.
+ *   If any OSFMODE_xxx values aren't applicable on the local OS, you can
+ *   simply define them as zero since they'll never be returned.
+ *   
+ *   Notes on attribute flags:
+ *   
+ *   OSFATTR_HIDDEN means that the file is conventionally hidden by default
+ *   in user interface views or listings, but is still fully accessible to
+ *   the user.  Hidden files are also usually excluded by default from
+ *   wildcard patterns in commands ("rm *.*").  On Unix, a hidden file is one
+ *   whose name starts with "."; on Windows, it's a file with the HIDDEN bit
+ *   in its file attributes.  On systems where this concept exists, the user
+ *   can still manipulate these files as normal by naming them explicitly,
+ *   and can typically make them appear in UI views or directory listings via
+ *   a preference setting or command flag (e.g., "ls -a" on Unix).  The
+ *   "hidden" flag is explicitly NOT a security or permissions mechanism, and
+ *   it doesn't protect the file against intentional access by a user; it's
+ *   merely a convenience designed to reduce clutter by excluding files
+ *   maintained by the OS or by an application (such as preference files,
+ *   indices, caches, etc) from casual folder browsing, where a user is
+ *   typically only concerned with her own document files.  On systems where
+ *   there's no such naming convention or attribute metadata, this flag will
+ *   never appear.
+ *   
+ *   OSFATTR_SYSTEM is similar to 'hidden', but means that the file is
+ *   specially marked as an operating system file.  This is mostly a
+ *   DOS/Windows concept, where it corresponds to the SYSTEM bit in the file
+ *   attributes; this flag will probably never appear on other systems.  The
+ *   distinction between 'system' and 'hidden' is somewhat murky even on
+ *   Windows; most 'system' file are also marked as 'hidden', and in
+ *   practical terms in the user interface, 'system' files are treated the
+ *   same as 'hidden'.
+ *   
+ *   OSFATTR_READ means that the file is readable by this process.
+ *   
+ *   OSFATTR_WRITE means that the file is writable by this process.
+ */
+/* int osfmode(const char *fname, int follow_links, */
+/*             unsigned long *mode, unsigned long *attr); */
+
+/* file mode/type constants */
+/* #define OSFMODE_FILE    - regular file */
+/* #define OSFMODE_DIR     - directory */
+/* #define OSFMODE_BLK     - block-mode device */
+/* #define OSFMODE_CHAR    - character-mode device */
+/* #define OSFMODE_PIPE    - pipe/FIFO/other character-oriented IPC */
+/* #define OSFMODE_SOCKET  - network socket */
+/* #define OSFMODE_LINK    - symbolic link */
+
+/* file attribute constants */
+/* #define OSFATTR_HIDDEN  - hidden file */
+/* #define OSFATTR_SYSTEM  - system file */
+/* #define OSFATTR_READ    - the file is readable by this process */
+/* #define OSFATTR_WRITE   - the file is writable by this process */
+
+struct os_file_stat_t {
+    /* 
+     *   Size of the file, in bytes.  For platforms lacking 64-bit types, we
+     *   split this into high and low 32-bit portions.  Platforms where the
+     *   native stat() or equivalent only returns a 32-bit file size can
+     *   simply set sizehi to zero, since sizelo can hold the entire size
+     *   value.
+     */
+    uint32_t sizelo;
+    uint32_t sizehi;
+
+    /* 
+     *   Creation time, modification time, and last access time.  If the file
+     *   system doesn't keep information on one or more of these, use
+     *   (os_time_t)0 to indicate that the timestamp isn't available.  It's
+     *   fine to return any subset of these.  Per the standard C stat(),
+     *   these should be expressed as seconds after the Unix Epoch.
+     */
+    os_time_t cre_time;
+    os_time_t mod_time;
+    os_time_t acc_time;
+
+    /* file mode, using the same flags as returned from osfmode() */
+    unsigned long mode;
+
+    /* file attributes, using the same flags as returned from osfmode() */
+    unsigned long attrs;
+};
+
+
+/*
+ *   Get stat() information.  This fills in the portable os_file_stat
+ *   structure with the requested file information.  Returns true on success,
+ *   false on failure (file not found, permissions error, etc).
+ *   
+ *   'follow_links' has the same meaning as for osfmode().
+ */
+int os_file_stat(const char *fname, int follow_links, os_file_stat_t *s);
+
+/*
+ *   Manually resolve a symbolic link.  If the local OS and file system
+ *   support symbolic links, and the given filename is a symbolic link (in
+ *   which case osfmode(fname, FALSE, &m, &a) will set OSFMODE_LINK in the
+ *   mode bits), this fills in 'target' with the name of the link target
+ *   (i.e., the object that the link in 'fname' points to).  This should
+ *   return a fully qualified file system path.  Returns true on success,
+ *   false on failure.
+ *   
+ *   This should only resolve a single level of indirection.  If the link
+ *   target of 'fname' is itself a link to a second target, this should only
+ *   resolve the single reference from 'fname' to its direct direct.  Callers
+ *   that wish to resolve the final target of a chain of link references must
+ *   iterate until the returned path doesn't refer to a link.
+ */
+int os_resolve_symlink(const char *fname, char *target, size_t target_size);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Get a list of root directories.  If 'buf' is non-null, fills in 'buf'
+ *   with a list of strings giving the root directories for the local,
+ *   file-oriented devices on the system.  The strings are each null
+ *   terminated and are arranged consecutively in the buffer, with an extra
+ *   null terminator after the last string to mark the end of the list.
+ *   
+ *   The return value is the length of the buffer required to hold the
+ *   results.  If the caller's buffer is null or is too short, the routine
+ *   should return the full length required, and leaves the contents of the
+ *   buffer undefined; the caller shouldn't expect any contents to be filled
+ *   in if the return value is greater than buflen.  Both 'buflen' and the
+ *   return value include the null terminators, including the extra null
+ *   terminator at the end of the list.  If an error occurs, or the system
+ *   has no concept of a root directory, returns zero.
+ *   
+ *   Each result string should be expressed using the syntax for the root
+ *   directory on a device.  For example, on Windows, "C:\" represents the
+ *   root directory on the C: drive.
+ *   
+ *   "Local" means a device is mounted locally, as opposed to being merely
+ *   visible on the network via some remote node syntax; e.g., on Windows
+ *   this wouldn't include any UNC-style \\SERVER\SHARE names, and on VMS it
+ *   excludes any SERVER:: nodes.  It's up to each system how to treat
+ *   virtual local devices, i.e., those that look synctactically like local
+ *   devices but are actually mounted network devices, such as Windows mapped
+ *   network drives; we recommend including them if it would take extra work
+ *   to filter them out, and excluding them if it would take extra work to
+ *   include them.  "File-oriented" means that the returned devices are
+ *   accessed via file systems, not as character devices or raw block
+ *   devices; so this would exclude /dev/xxx devices on Unix and things like
+ *   CON: and LPT1: on Windows.
+ *   
+ *   Examples ("." represents a null byte):
+ *   
+ *   Windows: C:\.D:\.E:\..
+ *   
+ *   Unix example: /..
+ */
+size_t os_get_root_dirs(char *buf, size_t buflen);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Open a directory.  This begins an enumeration of a directory's contents.
+ *   'dirname' is a relative or absolute path to a directory.  On success,
+ *   returns true, and 'handle' is set to a port-defined handle value that's
+ *   used in subsequent calls to os_read_dir() and os_close_dir().  Returns
+ *   false on failure.
+ *   
+ *   If the routine succeeds, the caller must eventually call os_close_dir()
+ *   to release the resources associated with the handle.
+ */
+/* typedef <local system type> osdirhdl_t; */
+int os_open_dir(const char *dirname, /*OUT*/osdirhdl_t *handle);
+
+/*
+ *   Read the next file in a directory.  'handle' is a handle value obtained
+ *   from a call to os_open_dir().  On success, returns true and fills in
+ *   'fname' with the next filename; the handle is also internally updated so
+ *   that the next call to this function will retrieve the next file, and so
+ *   on until all files have been retrieved.  If an error occurs, or there
+ *   are no more files in the directory, returns false.
+ *   
+ *   The filename returned is the root filename only, without the path.  The
+ *   caller can build the full path by calling os_build_full_path() or
+ *   os_combine_paths() with the original directory name and the returned
+ *   filename as parameters.
+ *   
+ *   This routine lists all objects in the directory that are visible to the
+ *   corresponding native API, and is non-recursive.  The listing should thus
+ *   include subdirectory objects, but not the contents of subdirectories.
+ *   Implementations are encouraged to simply return all objects returned
+ *   from the corresponding native directory scan API; there's no need to do
+ *   any filtering, except perhaps in cases where it's difficult or
+ *   impossible to represent an object in terms of the osifc APIs (e.g., it
+ *   might be reasonable to exclude files without names).  System relative
+ *   links, such as the Unix/DOS "." and "..", specifically should be
+ *   included in the listing.  For unusual objects that don't fit into the
+ *   os_file_stat() taxonomy or that otherwise might create confusion for a
+ *   caller, err on the side of full disclosure (i.e., just return everything
+ *   unfiltered); if necessary, we can extend the os_file_stat() taxonomy or
+ *   add new osifc APIs to create a portable abstraction to handle whatever
+ *   is unusual or potentially confusing about the native object.  For
+ *   example, Unix implementations should feel free to return symbolic link
+ *   objects, including dangling links, since we have the portable
+ *   os_resolve_symlink() that lets the caller examine the meaning of the
+ *   link object.
+ */
+int os_read_dir(osdirhdl_t handle, char *fname, size_t fname_size);
+
+/*
+ *   Close a directory handle.  This releases the resources associated with a
+ *   directory search started with os_open_dir().  Every successful call to
+ *   os_open_dir() must have a matching call to os_close_dir().  As usual for
+ *   open/close protocols, the handle is invalid after calling this function,
+ *   so no more calls to os_read_dir() may be made with the handle.
+ */
+void os_close_dir(osdirhdl_t handle);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   NB - this routine is DEPRECATED as of TADS 2.5.16/3.1.1.  Callers should
+ *   use os_open_dir(), os_read_dir(), os_close_dir() instead.
+ *   
+ *   Find the first file matching a given pattern.  The returned context
+ *   pointer is a pointer to whatever system-dependent context structure is
+ *   needed to continue the search with the next file, and is opaque to the
+ *   caller.  The caller must pass the context pointer to the next-file
+ *   routine.  The caller can optionally cancel a search by calling the
+ *   close-search routine with the context pointer.  If the return value is
+ *   null, it indicates that no matching files were found.  If a file was
+ *   found, outbuf will be filled in with its name, and isdir will be set to
+ *   true if the match is a directory, false if it's a file.  If pattern is
+ *   null, all files in the given directory should be returned; otherwise,
+ *   pattern is a string containing '*' and '?' as wildcard characters, but
+ *   not containing any directory separators, and all files in the given
+ *   directory matching the pattern should be returned.
+ *   
+ *   Important: because this routine may allocate memory for the returned
+ *   context structure, the caller must either call os_find_next_file until
+ *   that routine returns null, or call os_find_close() to cancel the search,
+ *   to ensure that the os code has a chance to release the allocated memory.
+ *   
+ *   'outbuf' should be set on output to the name of the matching file,
+ *   without any path information.
+ *   
+ *   'outpathbuf' should be set on output to full path of the matching file.
+ *   If possible, 'outpathbuf' should use the same relative or absolute
+ *   notation that the search criteria used on input.  For example, if dir =
+ *   "resfiles", and the file found is "MyPic.jpg", outpathbuf should be set
+ *   to "resfiles/MyPic.jpg" (or appropriate syntax for the local platform).
+ *   Similarly, if dir = "/home/tads/resfiles", outpath buf should be
+ *   "/home/tads/resfiles/MyPic.jpg".  The result should always conform to
+ *   correct local conventions, which may require some amount of manipulation
+ *   of the filename; for example, on the Mac, if dir = "resfiles", the
+ *   result should be ":resfiles:MyPic.jpg" (note the added leading colon) to
+ *   conform to Macintosh relative path notation.
+ *   
+ *   Note that 'outpathbuf' may be null, in which case the caller is not
+ *   interested in the full path information.  
+ */
+/*   
+ *   Note the following possible ways this function may be called:
+ *   
+ *   dir = "", pattern = filename - in this case, pattern is the name of a
+ *   file or directory in the current directory.  filename *might* be a
+ *   relative path specified by the user (on a command line, for example);
+ *   for instance, on Unix, it could be something like "resfiles/jpegs".
+ *   
+ *   dir = path, pattern = filname - same as above, but this time the
+ *   filename or directory pattern is relative to the given path, rather
+ *   than to the current directory.  For example, we could have dir =
+ *   "/games/mygame" and pattern = "resfiles/jpegs".
+ *   
+ *   dir = path, pattern = 0 (NULL) - this should search for all files in
+ *   the given path.  The path might be absolute or it might be relative.
+ *   
+ *   dir = path, pattern = "*" - this should have the same result as when
+ *   pattern = 0.
+ *   
+ *   dir = path, pattern = "*.ext" - this should search for all files in
+ *   the given path whose names end with ".ext".
+ *   
+ *   dir = path, pattern = "abc*" - this should search for all files in
+ *   the given path whose names start with "abc".
+ *   
+ *   All of these combinations are possible because callers, for
+ *   portability, must generally not manipulate filenames directly;
+ *   instead, callers obtain paths and search strings from external
+ *   sources, such as from the user, and present them to this routine with
+ *   minimal manipulation.  
+ */
+void *os_find_first_file(const char *dir,
+                         char *outbuf, size_t outbufsiz, int *isdir,
+                         char *outpathbuf, size_t outpathbufsiz);
+
+/*
+ *   Implementation notes for porting os_find_first_file:
+ *   
+ *   The algorithm for this routine should go something like this:
+ *   
+ *   - If 'path' is null, create a variable real_path and initialize it
+ *   with the current directory.  Otherwise, copy path to real_path.
+ *   
+ *   - If 'pattern' contains any directory separators ("/" on Unix, for
+ *   example), change real_path so that it reflects the additional leading
+ *   subdirectories in the path in 'pattern', and remove the leading path
+ *   information from 'pattern'.  For example, on Unix, if real_path
+ *   starts out as "./subdir", and pattern is "resfiles/jpegs", change
+ *   real_path to "./subdir/resfiles", and change pattern to "jpegs".
+ *   Take care to add and remove path separators as needed to keep the
+ *   path strings well-formed.
+ *   
+ *   - Begin a search using appropriate OS API's for all files in
+ *   real_path.
+ *   
+ *   - Check each file found.  Skip any files that don't match 'pattern',
+ *   treating "*" as a wildcard that matches any string of zero or more
+ *   characters, and "?" as a wildcard that matches any single character
+ *   (or matches nothing at the end of a string).  For example:
+ *   
+ *.  "*" matches anything
+ *.  "abc?" matches "abc", "abcd", "abce", "abcf", but not "abcde"
+ *.  "abc???" matches "abc", "abcd", "abcde", "abcdef", but not "abcdefg"
+ *.  "?xyz" matches "wxyz", "axyz", but not "xyz" or "abcxyz"
+ *   
+ *   - Return the first file that matches, if any, by filling in 'outbuf'
+ *   and 'isdir' with appropriate information.  Before returning, allocate
+ *   a context structure (which is entirely for your own use, and opaque
+ *   to the caller) and fill it in with the information necessary for
+ *   os_find_next_file to get the next matching file.  If no file matches,
+ *   return null.  
+ */
+
+
+/*
+ *   Find the next matching file, continuing a search started with
+ *   os_find_first_file().  Returns null if no more files were found, in
+ *   which case the search will have been automatically closed (i.e.,
+ *   there's no need to call os_find_close() after this routine returns
+ *   null).  Returns a non-null context pointer, which is to be passed to
+ *   this function again to get the next file, if a file was found.
+ *   
+ *   'outbuf' and 'outpathbuf' are filled in with the filename (without
+ *   path) and full path (relative or absolute, as appropriate),
+ *   respectively, in the same manner as they do for os_find_first_file().
+ *   
+ *   Implementation note: if os_find_first_file() allocated memory for the
+ *   search context, this routine must free the memory if it returs null,
+ *   because this indicates that the search is finished and the caller
+ *   need not call os_find_close().  
+ */
+void *os_find_next_file(void *ctx, char *outbuf, size_t outbufsiz,
+                        int *isdir, char *outpathbuf, size_t outpathbufsiz);
+
+/*
+ *   Cancel a search.  The context pointer returned by the last call to
+ *   os_find_first_file() or os_find_next_file() is the parameter.  There
+ *   is no need to call this function if find-first or find-next returned
+ *   null, since they will have automatically closed the search.
+ *   
+ *   Implementation note: if os_find_first_file() allocated memory for the
+ *   search context, this routine should release the memory.  
+ */
+void os_find_close(void *ctx);
+
+/*
+ *   Special filename classification 
+ */
+enum os_specfile_t
+{
+    /* not a special file */
+    OS_SPECFILE_NONE,
+
+    /* 
+     *   current directory link - this is a file like the "." file on Unix
+     *   or DOS, which is a special link that simply refers to itself 
+     */
+    OS_SPECFILE_SELF,
+
+    /* 
+     *   parent directory link - this is a file like the ".." file on Unix
+     *   or DOS, which is a special link that refers to the parent
+     *   directory 
+     */
+    OS_SPECFILE_PARENT
 };
 
+/*
+ *   Determine if the given filename refers to a special file.  Returns the
+ *   appropriate enum value if so, or OS_SPECFILE_NONE if not.  The given
+ *   filename must be a root name - it must not contain a path prefix.  The
+ *   purpose here is to classify the results from os_find_first_file() and
+ *   os_find_next_file() to identify the special relative links, so callers
+ *   can avoid infinite recursion when traversing a directory tree.
+ */
+enum os_specfile_t os_is_special_file(const char *fname);
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   Convert string to all-lowercase. 
+ */
+char *os_strlwr(char *s);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Character classifications for quote characters.  os_squote() returns
+ *   true if its argument is any type of single-quote character;
+ *   os_dquote() returns true if its argument is any type of double-quote
+ *   character; and os_qmatch(a, b) returns true if a and b are matching
+ *   open- and close-quote characters.
+ *   
+ *   These functions allow systems with extended character codes with
+ *   weird quote characters (such as the Mac) to match the weird
+ *   characters, so that users can use the extended quotes in input.
+ *   
+ *   These are usually implemented as macros.  The most common
+ *   implementation simply returns true for the standard ASCII quote
+ *   characters:
+ *   
+ *   #define os_squote(c) ((c) == '\'')
+ *.  #define os_dquote(c) ((c) == '"')
+ *.  #define os_qmatch(a, b) ((a) == (b))
+ *   
+ *   These functions take int arguments to allow for the possibility of
+ *   Unicode input.  
+ */
+/* int os_squote(int c); */
+/* int os_dquote(int c); */
+/* int os_qmatch(int a, int b); */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Special file and directory locations
+ */
+
+/*
+ *   Get the full filename (including directory path) to the executable
+ *   file, given the argv[0] parameter passed into the main program.  This
+ *   fills in the buffer with a null-terminated string that can be used in
+ *   osfoprb(), for example, to open the executable file.
+ *   
+ *   Returns non-zero on success.  If it's not possible to determine the
+ *   name of the executable file, returns zero.
+ *   
+ *   Some operating systems might not provide access to the executable file
+ *   information, so non-trivial implementation of this routine is optional;
+ *   if the necessary information is not available, simply implement this to
+ *   return zero.  If the information is not available, callers should offer
+ *   gracefully degraded functionality if possible.  
+ */
+int os_get_exe_filename(char *buf, size_t buflen, const char *argv0);
+
+/*
+ *   Get a special directory path.  Returns the selected path, in a format
+ *   suitable for use with os_build_full_path().  The main program's argv[0]
+ *   parameter is provided so that the system code can choose to make the
+ *   special paths relative to the program install directory, but this is
+ *   entirely up to the system implementation, so the argv[0] parameter can
+ *   be ignored if it is not needed.
+ *   
+ *   The 'id' parameter selects which special path is requested; this is one
+ *   of the constants defined below.  If the id is not understood, there is
+ *   no way of signalling an error to the caller; this routine can fail with
+ *   an assert() in such cases, because it indicates that the OS layer code
+ *   is out of date with respect to the calling code.
+ *   
+ *   This routine can be implemented using one of the strategies below, or a
+ *   combination of these.  These are merely suggestions, though, and systems
+ *   are free to ignore these and implement this routine using whatever
+ *   scheme is the best fit to local conventions.
+ *   
+ *   - Relative to argv[0].  Some systems use this approach because it keeps
+ *   all of the TADS files together in a single install directory tree, and
+ *   doesn't require any extra configuration information to find the install
+ *   directory.  Since we base the path name on the executable that's
+ *   actually running, we don't need any environment variables or parameter
+ *   files or registry entries to know where to look for related files.
+ *   
+ *   - Environment variables or local equivalent.  On some systems, it is
+ *   conventional to set some form of global system parameter (environment
+ *   variables on Unix, for example) for this sort of install configuration
+ *   data.  In these cases, this routine can look up the appropriate
+ *   configuration variables in the system environment.
+ *   
+ *   - Hard-coded paths.  Some systems have universal conventions for the
+ *   installation configuration of compiler-like tools, so the paths to our
+ *   component files can be hard-coded based on these conventions.
+ *   
+ *   - Hard-coded default paths with environment variable overrides.  Let the
+ *   user set environment variables if they want, but use the standard system
+ *   paths as hard-coded defaults if the variables aren't set.  This is often
+ *   the best choice; users who expect the standard system conventions won't
+ *   have to fuss with any manual settings or even be aware of them, while
+ *   users who need custom settings aren't stuck with the defaults.
+ */
+void os_get_special_path(char *buf, size_t buflen,
+                         const char *argv0, int id);
+
+/* 
+ *   TADS 3 system resource path.  This path is used to load system
+ *   resources, such as character mapping files and error message files.  
+ */
+#define OS_GSP_T3_RES       1
+
+/* 
+ *   TADS 3 compiler - system headers.  This is the #include path for the
+ *   header files included with the compiler. 
+ */
+#define OS_GSP_T3_INC       2
+
+/*
+ *   TADS 3 compiler - system library source code.  This is the path to the
+ *   library source files that the compiler includes in every compilation by
+ *   default (such as _main.t). 
+ */
+#define OS_GSP_T3_LIB       3
+
+/*
+ *   TADS 3 compiler - user library path list.  This is a list of directory
+ *   paths, separated by the OSPATHSEP character, that should be searched for
+ *   user library files.  The TADS 3 compiler uses this as an additional set
+ *   of locations to search after the list of "-Fs" options and before the
+ *   OS_GSP_T3_LIB directory.
+ *   
+ *   This path list is intended for the user's use, so no default value is
+ *   needed.  The value should be user-configurable using local conventions;
+ *   on Unix, for example, this might be handled with an environment
+ *   variable.  
+ */
+#define OS_GSP_T3_USER_LIBS 4
+
+/*
+ *   TADS 3 interpreter - application data path.  This is the directory where
+ *   we should store things like option settings: data that we want to store
+ *   in a file, global to all games.  Depending on local system conventions,
+ *   this can be a global shared directory for all users, or can be a
+ *   user-specific directory. 
+ */
+#define OS_GSP_T3_APP_DATA 5
+
+/*
+ *   TADS 3 interpreter - system configuration files.  This is used for files
+ *   that affect all games, and generally all users on the system, so it
+ *   should be in a central location.  On Windows, for example, we simply
+ *   store these files in the install directory containing the intepreter
+ *   binary.  
+ */
+#define OS_GSP_T3_SYSCONFIG  6
+
+/*
+ *   System log files.  This is the directory for system-level status, debug,
+ *   and error logging files.  (Note that we're NOT talking about in-game
+ *   transcript logging per the SCRIPT command.  SCRIPT logs are usually sent
+ *   to files selected by the user via a save-file dialog, so these don't
+ *   need a special location.)
+ */
+#define OS_GSP_LOGFILE  7
+
+
+/* 
+ *   Seek to the resource file embedded in the current executable file,
+ *   given the main program's argv[0].
+ *   
+ *   On platforms where the executable file format allows additional
+ *   information to be attached to an executable, this function can be used
+ *   to find the extra information within the executable.
+ *   
+ *   The 'typ' argument gives a resource type to find.  This is an arbitrary
+ *   string that the caller uses to identify what type of object to find.
+ *   The "TGAM" type, for example, is used by convention to indicate a TADS
+ *   compiled GAM file.  
+ */
+osfildef *os_exeseek(const char *argv0, const char *typ);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Load a string resource.  Given a string ID number, load the string
+ *   into the given buffer.
+ *   
+ *   Returns zero on success, non-zero if an error occurs (for example,
+ *   the buffer is too small, or the requested resource isn't present).
+ *   
+ *   Whenever possible, implementations should use an operating system
+ *   mechanism for loading the string from a user-modifiable resource
+ *   store; this will make localization of these strings easier, since the
+ *   resource store can be modified without the need to recompile the
+ *   application.  For example, on the Macintosh, the normal system string
+ *   resource mechanism should be used to load the string from the
+ *   application's resource fork.
+ *   
+ *   When no operating system mechanism exists, the resources can be
+ *   stored as an array of strings in a static variable; this isn't ideal,
+ *   because it makes it much more difficult to localize the application.
+ *   
+ *   Resource ID's are application-defined.  For example, for TADS 2,
+ *   "res.h" defines the resource ID's.  
+ */
+int os_get_str_rsc(int id, char *buf, size_t buflen);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Look for a file in the "standard locations": current directory, program
+ *   directory, PATH-like environment variables, etc.  The actual standard
+ *   locations are specific to each platform; the implementation is free to
+ *   use whatever conventions are appropriate to the local system.  On
+ *   systems that have something like Unix environment variables, it might be
+ *   desirable to define a TADS-specific variable (TADSPATH, for example)
+ *   that provides a list of directories to search for TADS-related files.
+ *   
+ *   On return, fill in 'buf' with the full filename of the located copy of
+ *   the file (if a copy was indeed found), in a format suitable for use with
+ *   the osfopxxx() functions; in other words, after this function returns,
+ *   the caller should be able to pass the contents of 'buf' to an osfopxxx()
+ *   function to open the located file.
+ *   
+ *   Returns true (non-zero) if a copy of the file was located, false (zero)
+ *   if the file could not be found in any of the standard locations.  
+ */
+int os_locate(const char *fname, int flen, const char *arg0,
+              char *buf, size_t bufsiz);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Create and open a temporary file.  The file must be opened to allow
+ *   both reading and writing, and must be in "binary" mode rather than
+ *   "text" mode, if the system makes such a distinction.  Returns null on
+ *   failure.
+ *   
+ *   If 'fname' is non-null, then this routine should create and open a file
+ *   with the given name.  When 'fname' is non-null, this routine does NOT
+ *   need to store anything in 'buf'.  Note that the routine shouldn't try
+ *   to put the file in a special directory or anything like that; just open
+ *   the file with the name exactly as given.
+ *   
+ *   If 'fname' is null, this routine must choose a file name and fill in
+ *   'buf' with the chosen name; if possible, the file should be in the
+ *   conventional location for temporary files on this system, and should be
+ *   unique (i.e., it shouldn't be the same as any existing file).  The
+ *   filename stored in 'buf' is opaque to the caller, and cannot be used by
+ *   the caller except to pass to osfdel_temp().  On some systems, it may
+ *   not be possible to determine the actual filename of a temporary file;
+ *   in such cases, the implementation may simply store an empty string in
+ *   the buffer.  (The only way the filename would be unavailable is if the
+ *   implementation uses a system API that creates a temporary file, and
+ *   that API doesn't return the name of the created temporary file.  In
+ *   such cases, we don't need the name; the only reason we need the name is
+ *   so we can pass it to osfdel_temp() later, but since the system is going
+ *   to delete the file automatically, osfdel_temp() doesn't need to do
+ *   anything and thus doesn't need the name.)
+ *   
+ *   After the caller is done with the file, it should close the file (using
+ *   osfcls() as normal), then the caller MUST call osfdel_temp() to delete
+ *   the temporary file.
+ *   
+ *   This interface is intended to take advantage of systems that have
+ *   automatic support for temporary files, while allowing implementation on
+ *   systems that don't have any special temp file support.  On systems that
+ *   do have automatic delete-on-close support, this routine should use that
+ *   system-level support, because it helps ensure that temp files will be
+ *   deleted even if the caller fails to call osfdel_temp() due to a
+ *   programming error or due to a process or system crash.  On systems that
+ *   don't have any automatic delete-on-close support, this routine can
+ *   simply use the same underlying system API that osfoprwbt() normally
+ *   uses (although this routine must also generate a name for the temp file
+ *   when the caller doesn't supply one).
+ *   
+ *   This routine can be implemented using ANSI library functions as
+ *   follows: if 'fname' is non-null, return fopen(fname,"w+b"); otherwise,
+ *   set buf[0] to '\0' and return tmpfile().  
+ */
+osfildef *os_create_tempfile(const char *fname, char *buf);
+
+/*
+ *   Delete a temporary file - this is used to delete a file created with
+ *   os_create_tempfile().  For most platforms, this can simply be defined
+ *   the same way as osfdel().  For platforms where the operating system or
+ *   file manager will automatically delete a file opened as a temporary
+ *   file, this routine should do nothing at all, since the system will take
+ *   care of deleting the temp file.
+ *   
+ *   Callers are REQUIRED to call this routine after closing a file opened
+ *   with os_create_tempfile().  When os_create_tempfile() is called with a
+ *   non-null 'fname' argument, the same value should be passed as 'fname' to
+ *   this function.  When os_create_tempfile() is called with a null 'fname'
+ *   argument, then the buffer passed in the 'buf' argument to
+ *   os_create_tempfile() must be passed as the 'fname' argument here.  In
+ *   other words, if the caller explicitly names the temporary file to be
+ *   opened in os_create_tempfile(), then that same filename must be passed
+ *   here to delete the named file; if the caller lets os_create_tempfile()
+ *   generate a filename, then the generated filename must be passed to this
+ *   routine.
+ *   
+ *   If os_create_tempfile() is implemented using ANSI library functions as
+ *   described above, then this routine can also be implemented with ANSI
+ *   library calls as follows: if 'fname' is non-null and fname[0] != '\0',
+ *   then call remove(fname); otherwise do nothing.  
+ */
+int osfdel_temp(const char *fname);
+
+/*
+ *   Get the temporary file path.  This should fill in the buffer with a
+ *   path prefix (suitable for strcat'ing a filename onto) for a good
+ *   directory for a temporary file, such as the swap file.  
+ */
+void os_get_tmp_path(char *buf);
+
+/* 
+ *   Generate a name for a temporary file.  This constructs a random file
+ *   path in the system temp directory that isn't already used by an existing
+ *   file.
+ *   
+ *   On systems with long filenames, this can be implemented by selecting a
+ *   GUID-strength random name (such as 32 random hex digits) with a decent
+ *   random number generator.  That's long enough that the odds of a
+ *   collision are essentially zero.  On systems that only support short
+ *   filenames, the odds of a collision are non-zero, so the routine should
+ *   actually check that the chosen filename doesn't exist.
+ *   
+ *   Optionally, before returning, this routine *may* create (and close) an
+ *   empty placeholder file to "reserve" the chosen filename.  This isn't
+ *   required, and on systems with long filenames it's usually not necessary
+ *   because of the negligible chance of a collision.  On systems with short
+ *   filenames, a placeholder can be useful to prevent a subsequent call to
+ *   this routine, or a separate process, from using the same filename before
+ *   the caller has had a chance to use the returned name to create the
+ *   actual temp file.
+ *   
+ *   Returns true on success, false on failure.  This can fail if there's no
+ *   system temporary directory defined, or the temp directory is so full of
+ *   other files that we can't find an unused filename.  
+ */
+int os_gen_temp_filename(char *buf, size_t buflen);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Basic directory/folder management routines
+ */
+
+/*
+ *   Switch to a new working directory.
+ *   
+ *   This is meant to behave similarly to the Unix concept of a working
+ *   directory, in that it sets the base directory assumed for subsequent
+ *   file operations (e.g., the osfopxx() functions, osfdel(), etc - anything
+ *   that takes a filename or directory name as an argument).  The working
+ *   directory applies to filenames specified with relative paths in the
+ *   local system notation.  File operations on filenames specified with
+ *   absolute paths, of course, ignore the working directory.
+ */
+void os_set_pwd(const char *dir);
+
+/*
+ *   Switch the working directory to the directory containing the given
+ *   file.  Generally, this routine should only need to parse the filename
+ *   enough to determine the part that's the directory path, then use
+ *   os_set_pwd() to switch to that directory.  
+ */
+void os_set_pwd_file(const char *filename);
+
+/*
+ *   Create a directory.  This creates a new directory/folder with the given
+ *   name, which may be given as a relative or absolute path.  Returns true
+ *   on success, false on failure.
+ *   
+ *   If 'create_parents' is true, and the directory has mulitiple path
+ *   elements, this routine should create each enclosing parent that doesn't
+ *   already exist.  For example, if the path is specified as "a/b/c", and
+ *   there exists a folder "a" in the working directory, but "a" is empty,
+ *   this should first create "b" and then create "c".  If an error occurs
+ *   creating any parent, the routine should simply stop and return failure.
+ *   (Optionally, the routine may attempt to behave atomically by undoing any
+ *   parent folder creations it accomplished before failing on a nested
+ *   folder, but this isn't required.  To reduce the chances of a failure
+ *   midway through the operation, the routine might want to scan the
+ *   filename before starting to ensure that it contains only valid
+ *   characters, since an invalid character is the most likely reason for a
+ *   failure part of the way through.)
+ *   
+ *   We recommend making the routine flexible in terms of the notation it
+ *   accepts; e.g., on Unix, "/dir/sub/folder" and "/dir/sub/folder/" should
+ *   be considered equivalent.
+ */
+int os_mkdir(const char *dir, int create_parents);
+
+/*
+ *   Remove a directory.  Returns true on success, false on failure.
+ *   
+ *   If the directory isn't already empty, this routine fails.  That is, the
+ *   routine does NOT recursively delete the contents of a non-empty
+ *   directory.  It's up to the caller to delete any contents before removing
+ *   the directory, if that's the caller's intention.  (Note to implementors:
+ *   most native OS APIs to remove directories fail by default if the
+ *   directory isn't empty, so it's usually safe to implement this simply by
+ *   calling the native API.  However, if your system's version of this API
+ *   can remove a non-empty directory, you MUST add an extra test before
+ *   removing the directory to ensure it's empty, and return failure if it's
+ *   not.  For the purposes of this test, "empty" should of course ignore any
+ *   special objects that are automatically or implicitly present in all
+ *   directories, such as the Unix "." and ".." relative links.)
+ */
+int os_rmdir(const char *dir);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Filename manipulation routines
+ */
+
+/* apply a default extension to a filename, if it doesn't already have one */
+void os_defext(char *fname, const char *ext);
+
+/* unconditionally add an extention to a filename */
+void os_addext(char *fname, const char *ext);
+
+/* remove the extension from a filename */
+void os_remext(char *fname);
+
+/*
+ *   Compare two file names/paths for syntactic equivalence.  Returns true if
+ *   the names are equivalent names according to the local file system's
+ *   syntax conventions, false if not.  This does a syntax-only comparison of
+ *   the paths, without looking anything up in the file system.  This means
+ *   that a false return doesn't guarantee that the paths don't point to the
+ *   same file.
+ *   
+ *   This routine DOES make the following equivalences:
+ *   
+ *   - if the local file system is insensitive to case, the names are
+ *   compared ignoring case
+ *   
+ *   - meaningless path separator difference are ignored: on Unix, "a/b" ==
+ *   "a//b" == "a/b/"; on Windows, "a/b" == "a\\b"
+ *   
+ *   - relative links that are strictly structural or syntactic are applied;
+ *   for example, on Unix or Windows, "a/./b" == "a/b" = "a/b/c/..".  This
+ *   only applies for special relative links that can be resolved without
+ *   looking anything up in the file system.
+ *   
+ *   This DOES NOT do the following:
+ *   
+ *   - it doesn't apply working directories/volums to relative paths
+ *   
+ *   - it doesn't follow symbolic links in the file system
+ */
+int os_file_names_equal(const char *a, const char *b);
+
+/*
+ *   Get a pointer to the root name portion of a filename.  This is the part
+ *   of the filename after any path or directory prefix.  For example, on
+ *   Unix, given the string "/home/mjr/deep.gam", this function should return
+ *   a pointer to the 'd' in "deep.gam".  If the filename doesn't appear to
+ *   have a path prefix, it should simply return the argument unchanged.
+ *   
+ *   IMPORTANT: the returned pointer MUST point into the original 'buf'
+ *   string, and the contents of that buffer must NOT be modified.  The
+ *   return value must point into the same buffer because there are no
+ *   allowances for the alternatives.  In particular, (a) you can't return a
+ *   pointer to newly allocated memory, because callers won't free it, so
+ *   doing so would cause a memory leak; and (b) you can't return a pointer
+ *   to an internal static buffer, because callers might call this function
+ *   more than once and still rely on a value returned on an older call,
+ *   which would be invalid if a static buffer could be overwritten on each
+ *   call.  For these reasons, it's required that the return value point to a
+ *   position within the original string passed in 'buf'.  
+ */
+char *os_get_root_name(const char *buf);
+
+/*
+ *   Determine whether a filename specifies an absolute or relative path.
+ *   This is used to analyze filenames provided by the user (for example,
+ *   in a #include directive, or on a command line) to determine if the
+ *   filename can be considered relative or absolute.  This can be used,
+ *   for example, to determine whether to search a directory path for a
+ *   file; if a given filename is absolute, a path search makes no sense.
+ *   A filename that doesn't specify an absolute path can be combined with
+ *   a path using os_build_full_path().
+ *   
+ *   Returns true if the filename specifies an absolute path, false if
+ *   not.  
+ */
+int os_is_file_absolute(const char *fname);
+
+/*
+ *   Extract the path from a filename.  Fills in pathbuf with the path
+ *   portion of the filename.  If the filename has no path, the pathbuf
+ *   should be set appropriately for the current directory (on Unix or DOS,
+ *   for example, it can be set to an empty string).
+ *   
+ *   The result can end with a path separator character or not, depending on
+ *   local OS conventions.  Paths extracted with this function can only be
+ *   used with os_build_full_path(), so the conventions should match that
+ *   function's.
+ *   
+ *   Unix examples:
+ *   
+ *.   /home/mjr/deep.gam -> /home/mjr
+ *.   games/deep.gam -> games
+ *.   deep.gam -> [empty string]
+ *   
+ *   Mac examples:
+ *   
+ *    :home:mjr:deep.gam -> :home:mjr
+ *.   Hard Disk:games:deep.gam -> Hard Disk:games
+ *.   Hard Disk:deep.gam -> Hard Disk:
+ *.   deep.gam -> [empty string]
+ *   
+ *   VMS examples:
+ *
+ *.   SYS$DISK:[mjr.games]deep.gam -> SYS$DISK:[mjr.games]
+ *.   SYS$DISK:[mjr.games] -> SYS$DISK:[mjr]
+ *.   deep.gam -> [empty string]
+ *   
+ *   Note in the last example that we've retained the trailing colon in the
+ *   path, whereas we didn't in the others; although the others could also
+ *   retain the trailing colon, it's required only for the last case.  The
+ *   last case requires the colon because it would otherwise be impossible to
+ *   determine whether "Hard Disk" was a local subdirectory or a volume name.
+ *   
+ */
+void os_get_path_name(char *pathbuf, size_t pathbuflen, const char *fname);
+
+/*
+ *   Build a full path name, given a path and a filename.  The path may have
+ *   been specified by the user, or may have been extracted from another file
+ *   via os_get_path_name().  This routine must take care to add path
+ *   separators as needed, but also must take care not to add too many path
+ *   separators.
+ *   
+ *   This routine should reformat the path into canonical format to the
+ *   extent possible purely through syntactic analysis.  For example, special
+ *   relative links, such as Unix "." and "..", should be resolved; for
+ *   example, combining "a/./b/c" with ".." on Unix should yield "a/b".
+ *   However, symbolic links that require looking up names in the file system
+ *   should NOT be resolved.  We don't want to perform any actual file system
+ *   lookups because might want to construct hypothetical paths that don't
+ *   necessarily relate to files on the local system.
+ *   
+ *   Note that relative path names may require special care on some
+ *   platforms.  In particular, if the source path is relative, the result
+ *   should also be relative.  For example, on the Macintosh, a path of
+ *   "games" and a filename "deep.gam" should yield ":games:deep.gam" - note
+ *   the addition of the leading colon to make the result path relative.
+ *   
+ *   Note also that the 'filename' argument is not only allowed to be an
+ *   ordinary file, possibly qualified with a relative path, but is also
+ *   allowed to be a subdirectory.  The result in this case should be a path
+ *   that can be used as the 'path' argument to a subsequent call to
+ *   os_build_full_path; this allows a path to be built in multiple steps by
+ *   descending into subdirectories one at a time.
+ *   
+ *   Unix examples:
+ *   
+ *.   /home/mjr + deep.gam -> /home/mjr/deep.gam"
+ *.   /home/mjr + .. -> /home
+ *.   /home/mjr + ../deep.gam -> /home/deep.gam
+ *.   /home/mjr/ + deep.gam -> /home/mjr/deep.gam"
+ *.   games + deep.gam -> games/deep.gam"
+ *.   games/ + deep.gam -> games/deep.gam"
+ *.   /home/mjr + games/deep.gam -> /home/mjr/games/deep.gam"
+ *.   games + scifi/deep.gam -> games/scifi/deep.gam"
+ *.   /home/mjr + games -> /home/mjr/games"
+ *   
+ *   Mac examples:
+ *   
+ *.   Hard Disk: + deep.gam -> Hard Disk:deep.gam
+ *.   :games: + deep.gam -> :games:deep.gam
+ *.   :games:deep + ::test.gam -> :games:test.gam
+ *.   games + deep.gam -> :games:deep.gam
+ *.   Hard Disk: + :games:deep.gam -> Hard Disk:games:deep.gam
+ *.   games + :scifi:deep.gam -> :games:scifi:deep.gam
+ *.   Hard Disk: + games -> Hard Disk:games
+ *.   Hard Disk:games + scifi -> Hard Disk:games:scifi
+ *.   Hard Disk:games:scifi + deep.gam -> Hard Disk:games:scifi:deep.gam
+ *.   Hard Disk:games + :scifi:deep.gam -> Hard Disk:games:scifi:deep.gam
+ *   
+ *   VMS examples:
+ *   
+ *.   [home.mjr] + deep.gam -> [home.mjr]deep.gam
+ *.   [home.mjr] + [-]deep.gam -> [home]deep.gam
+ *.   mjr.dir + deep.gam -> [.mjr]deep.gam
+ *.   [home]mjr.dir + deep.gam -> [home.mjr]deep.gam
+ *.   [home] + [.mjr]deep.gam -> [home.mjr]deep.gam
+ */
+void os_build_full_path(char *fullpathbuf, size_t fullpathbuflen,
+                        const char *path, const char *filename);
+
+/*
+ *   Combine a path and a filename to form a full path to the file.  This is
+ *   *almost* the same as os_build_full_path(), but if the 'filename' element
+ *   is a special relative link, such as Unix '.' or '..', this preserves
+ *   that special link in the final name.
+ *   
+ *   Unix examples:
+ *   
+ *.    /home/mjr + deep.gam -> /home/mjr/deep.gam
+ *.    /home/mjr + . -> /home/mjr/.
+ *.    /home/mjr + .. -> /home/mjr/..
+ *   
+ *   Mac examples:
+ *   
+ *.    Hard Disk:games + deep.gam -> HardDisk:games:deep.gam
+ *.    Hard Disk:games + :: -> HardDisk:games::
+ *   
+ *   VMS exmaples:
+ *   
+ *.    [home.mjr] + deep.gam -> [home.mjr]deep.gam
+ *.    [home.mjr] + [-] -> [home.mjr.-]
+ */
+void os_combine_paths(char *fullpathbuf, size_t pathbuflen,
+                      const char *path, const char *filename);
+
+
+/*
+ *   Get the absolute, fully qualified filename for a file.  This fills in
+ *   'result_buf' with the absolute path to the given file, taking into
+ *   account the current working directory and any other implied environment
+ *   information that affects the way the file system would resolve the given
+ *   file name to a specific file on disk if we opened the file now using
+ *   this name.
+ *   
+ *   The returned path should be in absolute path form, meaning that it's
+ *   independent of the current working directory or any other environment
+ *   settings.  That is, this path should still refer to the same file even
+ *   if the working directory changes.
+ *   
+ *   Note that it's valid to get the absolute path for a file that doesn't
+ *   exist, or for a path with directory components that don't exist.  For
+ *   example, a caller might generate the absolute path for a file that it's
+ *   about to create, or a hypothetical filename for path comparison
+ *   purposes.  The function should succeed even if the file or any path
+ *   components don't exist.  If the file is in relative format, and any path
+ *   elements don't exist but are syntactically well-formed, the result
+ *   should be the path obtained from syntactically combining the working
+ *   directory with the relative path.
+ *   
+ *   On many systems, a given file might be reachable through more than one
+ *   absolute path.  For example, on Unix it might be possible to reach a
+ *   file through symbolic links to the file itself or to parent directories,
+ *   or hard links to the file.  It's up to the implementation to determine
+ *   which path to use in such cases.
+ *   
+ *   On success, returns true.  If it's not possible to resolve the file name
+ *   to an absolute path, the routine copies the original filename to the
+ *   result buffer exactly as given, and returns false.  
+ */
+int os_get_abs_filename(char *result_buf, size_t result_buf_size,
+                        const char *filename);
+
+/*
+ *   Get the relative version of the given filename path 'filename', relative
+ *   to the given base directory 'basepath'.  Both paths must be given in
+ *   absolute format.
+ *   
+ *   Returns true on success, false if it's not possible to rewrite the path
+ *   in relative terms.  For example, on Windows, it's not possible to
+ *   express a path on the "D:" drive as relative to a base path on the "C:"
+ *   drive, since each drive letter has an independent root folder; there's
+ *   no namespace entity enclosing a drive letter's root folder.  On
+ *   Unix-like systems where the entire namespace has a single hierarchical
+ *   root, it should always be possible to express any path relative to any
+ *   other.
+ *   
+ *   The result should be a relative path that can be combined with
+ *   'basepath' using os_build_full_path() to reconstruct a path that
+ *   identifies the same file as the original 'filename' (it's not important
+ *   that this procedure would result in the identical string - it just has
+ *   to point to the same file).  If it's not possible to express the
+ *   filename relative to the base path, fill in 'result_buf' with the
+ *   original filename and return false.
+ *   
+ *   Windows examples:
+ *   
+ *.    c:\mjr\games | c:\mjr\games\deep.gam  -> deep.gam
+ *.    c:\mjr\games | c:\mjr\games\tads\deep.gam  -> tads\deep.gam
+ *.    c:\mjr\games | c:\mjr\tads\deep.gam  -> ..\tads\deep.gam
+ *.    c:\mjr\games | d:\deep.gam  ->  d:\deep.gam (and return false)
+ *   
+ *   Mac OS examples:
+ *   
+ *.    Mac HD:mjr:games | Mac HD:mjr:games:deep.gam -> deep.gam
+ *.    Mac HD:mjr:games | Mac HD:mjr:games:tads:deep.gam -> :tads:deep.gam
+ *.    Mac HD:mjr:games | Ext Disk:deep.gam -> Ext Disk:deep.gam (return false)
+ *   
+ *   VMS examples:
+ *   
+ *.    SYS$:[mjr.games] | SYS$:[mjr.games]deep.gam -> deep.gam
+ *.    SYS$:[mjr.games] | SYS$:[mjr.games.tads]deep.gam -> [.tads]deep.gam
+ *.    SYS$:[mjr.games] | SYS$:[mjr.tads]deep.gam -> [-.tads]deep.gam
+ *.    SYS$:[mjr.games] | DISK$:[mjr]deep.gam -> DISK$[mjr]deep.gam (ret false)
+ */
+int os_get_rel_path(char *result_buf, size_t result_buf_size,
+                    const char *basepath, const char *filename);
+
+/*
+ *   Determine if the given file is in the given directory.  Returns true if
+ *   so, false if not.  'filename' is a relative or absolute file name;
+ *   'path' is a relative or absolute directory path, such as one returned
+ *   from os_get_path_name().
+ *   
+ *   If 'include_subdirs' is true, the function returns true if the file is
+ *   either directly in the directory 'path', OR it's in any subdirectory of
+ *   'path'.  If 'include_subdirs' is false, the function returns true only
+ *   if the file is directly in the given directory.
+ *   
+ *   If 'match_self' is true, the function returns true if 'filename' and
+ *   'path' are the same directory; otherwise it returns false in this case.
+ *   
+ *   This routine is allowed to return "false negatives" - that is, it can
+ *   claim that the file isn't in the given directory even when it actually
+ *   is.  The reason is that it's not always possible to determine for sure
+ *   that there's not some way for a given file path to end up in the given
+ *   directory.  In contrast, a positive return must be reliable.
+ *   
+ *   If possible, this routine should fully resolve the names through the
+ *   file system to determine the path relationship, rather than merely
+ *   analyzing the text superficially.  This can be important because many
+ *   systems have multiple ways to reach a given file, such as via symbolic
+ *   links on Unix; analyzing the syntax alone wouldn't reveal these multiple
+ *   pathways.
+ *   
+ *   SECURITY NOTE: If possible, implementations should fully resolve all
+ *   symbolic links, relative paths (e.g., Unix ".."), etc, before rendering
+ *   judgment.  One important application for this routine is to determine if
+ *   a file is in a sandbox directory, to enforce security restrictions that
+ *   prevent a program from accessing files outside of a designated folder.
+ *   If the implementation fails to resolve symbolic links or relative paths,
+ *   a malicious program or user could bypass the security restriction by,
+ *   for example, creating a symbolic link within the sandbox directory that
+ *   points to the root folder.  Implementations can avoid this loophole by
+ *   converting the file and directory names to absolute paths and resolving
+ *   all symbolic links and relative notation before comparing the paths.
+ */
+int os_is_file_in_dir(const char *filename, const char *path,
+                      int include_subdirs, int match_self);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Convert an OS filename path to URL-style format.  This isn't a true URL
+ *   conversion; rather, it simply expresses a filename in Unix-style
+ *   notation, as a series of path elements separated by '/' characters.
+ *   Unlike true URLs, we don't use % encoding or a scheme prefix (file://,
+ *   etc).
+ *   
+ *   The result path never ends in a trailing '/', unless the entire result
+ *   path is "/".  This is for consistency; even if the source path ends with
+ *   a local path separator, the result doesn't.
+ *   
+ *   If the local file system syntax uses '/' characters as ordinary filename
+ *   characters, these must be replaced with some other suitable character in
+ *   the result, since otherwise they'd be taken as path separators when the
+ *   URL is parsed.  If possible, the substitution should be reversible with
+ *   respect to os_cvt_dir_url(), so that the same URL read back in on this
+ *   same platform will produce the same original filename.  One particular
+ *   suggestion is that if the local system uses '/' to delimit what would be
+ *   a filename extension on other platforms, replace '/' with '.', since
+ *   this will provide reversibility as well as a good mapping if the URL is
+ *   read back in on another platform.
+ *   
+ *   The local equivalents of "." and "..", if they exist, are converted to
+ *   "." and ".." in the URL notation.
+ *   
+ *   Examples:
+ *   
+ *.   Windows: images\rooms\startroom.jpg -> images/rooms/startroom.jpg
+ *.   Windows: ..\startroom.jpg -> ../startroom.jpg
+ *.   Mac:     :images:rooms:startroom.jpg -> images/rooms/startroom.jpg
+ *.   Mac:     ::startroom.jpg -> ../startroom.jpg
+ *.   VMS:     [.images.rooms]startroom.jpg -> images/rooms/startroom.jpg
+ *.   VMS:     [-.images]startroom.jpg -> ../images/startroom.jpg
+ *.   Unix:    images/rooms/startroom.jpg -> images/rooms/startroom.jpg
+ *.   Unix:    ../images/startroom.jpg -> ../images/startroom.jpg
+ *   
+ *   If the local name is an absolute path in the local file system (e.g.,
+ *   Unix /file, Windows C:\file), translate as follows.  If the local
+ *   operating system uses a volume or device designator (Windows C:, VMS
+ *   SYS$DISK:, etc), make the first element of the path the exact local
+ *   syntax for the device designator: /C:/ on Windows, /SYS$DISK:/ on VMS,
+ *   etc.  Include the local syntax for the device prefix.  For a system like
+ *   Unix with a unified file system root ("/"), simply start with the root
+ *   directory.  Examples:
+ *   
+ *.    Windows:  C:\games\deep.gam         -> /C:/games/deep.gam
+ *.    Windows:  C:games\deep.gam          -> /C:./games/deep.gam
+ *.    Windows:  \\SERVER\DISK\games\deep.gam -> /\\SERVER/DISK/games/deep.gam
+ *.    Mac OS 9: Hard Disk:games:deep.gam  -> /Hard Disk:/games/deep.gam
+ *.    VMS:      SYS$DISK:[games]deep.gam  -> /SYS$DISK:/games/deep.gam
+ *.    Unix:     /games/deep.gam           -> /games/deep.gam
+ *   
+ *   Rationale: it's effectively impossible to create a truly portable
+ *   representation of an absolute path.  Operating systems are too different
+ *   in the way they represent root paths, and even if that were solvable, a
+ *   root path is essentially unusable across machines anyway because it
+ *   creates a dependency on the contents of a particular machine's disk.  So
+ *   if we're called upon to translate an absolute path, we can forget about
+ *   trying to be truly portable and instead focus on round-trip fidelity -
+ *   i.e., making sure that applying os_cvt_url_dir() to our result recovers
+ *   the exact original path, assuming it's done on the same operating
+ *   system.  The approach outlined above should achieve round-trip fidelity
+ *   when a local path is converted to a URL and back on the same machine,
+ *   since the local URL-to-path converter should recognize its own special
+ *   type of local absolute path prefix.  It also produces reasonable results
+ *   on other platforms - see the os_cvt_url_dir() comments below for
+ *   examples of the decoding results for absolute paths moved to new
+ *   platforms.  The result when a device-rooted absolute path is encoded on
+ *   one machine and then decoded on another will generally be a local path
+ *   with a root on the default device/volume and an outermost directory with
+ *   a name based on the original machine's device/volume name.  This
+ *   obviously won't reproduce the exact original path, but since that's
+ *   impossible anyway, this is probably as good an approximation as we can
+ *   create.
+ *   
+ *   Character sets: the input could be in local or UTF-8 character sets.
+ *   The implementation shouldn't care, though - just treat bytes in the
+ *   range 0-127 as plain ASCII, and everything else as opaque.  I.e., do not
+ *   quote or otherwise modify characters outside the 0-127 range.  
+ */
+void os_cvt_dir_url(char *result_buf, size_t result_buf_size,
+                    const char *src_path);
+
+/*
+ *   Convert a URL-style path into a filename path expressed in the local
+ *   file system's syntax.  Fills in result_buf with a file path, constructed
+ *   using the local file system syntax, that corresponds to the path in
+ *   src_url expressed in URL-style syntax.  Examples:
+ *   
+ *   images/rooms/startroom.jpg -> 
+ *.   Windows   -> images\rooms\startroom.jpg
+ *.   Mac OS 9  -> :images:rooms:startroom.jpg
+ *.   VMS       -> [.images.rooms]startroom.jpg
+ *   
+ *   The source format isn't a true URL; it's simply a series of path
+ *   elements separated by '/' characters.  Unlike true URLs, our input
+ *   format doesn't use % encoding and doesn't have a scheme (file://, etc).
+ *   (Any % in the source is treated as an ordinary character and left as-is,
+ *   even if it looks like a %XX sequence.  Anything that looks like a scheme
+ *   prefix is left as-is, with any // treated as path separators.
+ *   
+ *   images/file%20name.jpg ->
+ *.   Windows   -> images\file%20name.jpg
+ *   
+ *   file://images/file.jpg ->
+ *.   Windows   -> file_\\images\file.jpg
+ *   
+ *   Any characters in the path that are invalid in the local file system
+ *   naming rules are converted to "_", unless "_" is itself invalid, in
+ *   which case they're converted to "X".  One exception is that if '/' is a
+ *   valid local filename character (rather than a path separator as it is on
+ *   Unix and Windows), it can be used as the replacement for the character
+ *   that os_cvt_dir_url uses as its replacement for '/', so that this
+ *   substitution is reversible when a URL is generated and then read back in
+ *   on this same platform.
+ *   
+ *   images/file:name.jpg ->
+ *.   Windows   -> images\file_name.jpg
+ *.   Mac OS 9  -> :images:file_name.jpg
+ *.   Unix      -> images/file:name.jpg
+ *   
+ *   The path elements "." and ".." are specifically defined as having their
+ *   Unix meanings: "." is an alias for the preceding path element, or the
+ *   working directory if it's the first element, and ".." is an alias for
+ *   the parent of the preceding element.  When these appear as path
+ *   elements, this routine translates them to the appropriate local
+ *   conventions.  "." may be translated simply by removing it from the path,
+ *   since it reiterates the previous path element.  ".." may be translated
+ *   by removing the previous element - HOWEVER, if ".." appears as the first
+ *   element, it has to be retained and translated to the equivalent local
+ *   notation, since it will have to be applied later, when the result_buf
+ *   path is actually used to open a file, at which point it will combined
+ *   with the working directory or another base path.
+ *   
+ *.  /images/../file.jpg -> [Windows] file.jpg
+ *.  ../images/file.jpg ->
+ *.   Windows  -> ..\images\file.jpg
+ *.   Mac OS 9 -> ::images:file.jpg
+ *.   VMS      -> [-.images]file.jpg
+ *   
+ *   If the URL path is absolute (starts with a '/'), the routine inspects
+ *   the path to see if it was created by the same OS, according to the local
+ *   rules for converting absolute paths in os_cvt_dir_url() (see).  If so,
+ *   we reverse the encoding done there.  If it doesn't appear that the name
+ *   was created by the same operating system - that is, if reversing the
+ *   encoding doesn't produce a valid local filename - then we create a local
+ *   absolute path as follows.  If the local system uses device/volume
+ *   designators, we start with the current working device/volume or some
+ *   other suitable default volume.  We then add the first element of the
+ *   path, if any, as the root directory name, applying the usual "_" or "X"
+ *   substitution for any characters that aren't allowed in local names.  The
+ *   rest of the path is handled in the usual fashion.
+ *   
+ *.  /images/file.jpg ->
+ *.    Windows -> \images\file.jpg
+ *.    Unix    -> /images/file.jpg
+ *   
+ *.  /c:/images/file.jpg ->
+ *.    Windows -> c:\images\file.jpg
+ *.    Unix    -> /c:/images/file.jpg
+ *.    VMS     -> SYS$DISK:[c__.images]file.jpg
+ *   
+ *.  /Hard Disk:/images/file.jpg ->
+ *.    Windows -> \Hard Disk_\images\file.jpg
+ *.    Unix    -> SYS$DISK:[Hard_Disk_.images]file.jpg
+ *   
+ *   Note how the device/volume prefix becomes the top-level directory when
+ *   moving a path across machines.  It's simply not possible to reconstruct
+ *   the exact original path in such cases, since device/volume syntax rules
+ *   have little in common across systems.  But this seems like a good
+ *   approximation in that (a) it produces a valid local path, and (b) it
+ *   gives the user a reasonable basis for creating a set of folders to mimic
+ *   the original source system, if they want to use that approach to port
+ *   the data rather than just changing the paths internally in the source
+ *   material.
+ *   
+ *   Character sets: use the same rules as for os_cvt_dir_url().  
+ */
+void os_cvt_url_dir(char *result_buf, size_t result_buf_size,
+                    const char *src_url);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Get a suitable seed for a random number generator; should use the system
+ *   clock or some other source of an unpredictable and changing seed value.
+ */
+void os_rand(long *val);
+
+/*
+ *   Generate random bytes for use in seeding a PRNG (pseudo-random number
+ *   generator).  This is an extended version of os_rand() for PRNGs that use
+ *   large seed vectors containing many bytes, rather than the simple 32-bit
+ *   seed that os_rand() assumes.
+ *   
+ *   As with os_rand(), this function isn't meant to be used directly as a
+ *   random number source for ongoing use - instead, this is intended mostly
+ *   for seeding a PRNG, which will then be used as the primary source of
+ *   random numbers.  The big problem with using this routine directly as a
+ *   randomness source is that some implementations might rely heavily on
+ *   environmental randomness, such as the real-time clock or OS usage
+ *   statistics.  Such sources tend to provide reasonable entropy from one
+ *   run to the next, but not within a single session, as the underlying data
+ *   sources don't change rapidly enough.
+ *   
+ *   Ideally, this routine should generate *truly* random bytes obtained from
+ *   hardware sources.  Not all systems can provide that, though, so true
+ *   randomness isn't guaranteed.  Here are the suggested implementation
+ *   options, in descending order of desirability:
+ *   
+ *   1.  Use a hardware source of true randomness, such as a /dev/rand type
+ *   of device.  However, note that this call should return reasonably
+ *   quickly, so always use a non-blocking source.  Some Unix /dev/rand
+ *   devices, for example, can block indefinitely to allow sufficient entropy
+ *   to accumulate.
+ *   
+ *   2. Use a cryptographic random number source provided by the OS.  Some
+ *   systems provide this as an API service.  If going this route, be sure
+ *   that the OS generator is itself "seeded" with some kind of true
+ *   randomness source, as it defeats the whole purpose if you always return
+ *   a fixed pseudo-random sequence each time the program runs.
+ *   
+ *   3. Use whatever true random sources are available locally to seed a
+ *   software pseudo-random number generator, then generate bytes from your
+ *   PRNG.  Some commonly available sources of true randomness are a
+ *   high-resolution timer, the system clock, the current process ID,
+ *   logged-in user ID, environment variables, uninitialized pages of memory,
+ *   the IP address; each of these sources might give you a few bits of
+ *   entropy at best, so the best bet is to use an ensemble.  You could, for
+ *   example, concatenate a bunch of this type of information together and
+ *   calculate an MD5 or SHA1 hash to mix the bits more thoroughly.  For the
+ *   PRNG, use a cryptographic generator.  (You could use the ISAAC generator
+ *   from TADS 3, as that's a crypto PRNG, but it's probably better to use a
+ *   different generator here since TADS 3 is going to turn around and use
+ *   this function's output to seed ISAAC - seeding one ISAAC instance with
+ *   another ISAAC's output seems likely to magnify any weaknesses in the
+ *   ISAAC algorithm.)  Note that this option is basically the DIY version of
+ *   option 2.  Option 2 is better because the OS probably has access to
+ *   better sources of true randomness than an application does.  
+ */
+void os_gen_rand_bytes(unsigned char *buf, size_t len);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Display routines.
+ *   
+ *   Our display model is a simple stdio-style character stream.
+ *   
+ *   In addition, we provide an optional "status line," which is a
+ *   non-scrolling area where a line of text can be displayed.  If the status
+ *   line is supported, text should only be displayed in this area when
+ *   os_status() is used to enter status-line mode (mode 1); while in status
+ *   line mode, text is written to the status line area, otherwise (mode 0)
+ *   it's written to the normal main text area.  The status line is normally
+ *   shown in a different color to set it off from the rest of the text.
+ *   
+ *   The OS layer can provide its own formatting (word wrapping in
+ *   particular) if it wants, in which case it should also provide pagination
+ *   using os_more_prompt().  
+ */
+
+/*
+ *   OS_MAXWIDTH - the maximum width of a line of text.  Most platforms use
+ *   135 for this, but you can use more or less as appropriate.  If you use
+ *   OS-level line wrapping, then the true width of a text line is
+ *   irrelevant, and the portable code will use this merely for setting its
+ *   internal buffer sizes.
+ *   
+ *   This must be defined in the os_xxx.h header file for each platform.
+ */
+/*#define OS_MAXWIDTH 135 - example only: define for real in os_xxx.h header*/
+
+/*
+ *   Print a string on the console.  These routines come in two varieties:
+ *   
+ *   os_printz - write a NULL-TERMINATED string
+ *.  os_print - write a COUNTED-LENGTH string, which may not end with a null
+ *   
+ *   These two routines are identical except that os_printz() takes a string
+ *   which is terminated by a null byte, and os_print() instead takes an
+ *   explicit length, and a string that may not end with a null byte.
+ *   
+ *   os_printz(str) may be implemented as simply os_print(str, strlen(str)).
+ *   
+ *   The string is written in one of three ways, depending on the status mode
+ *   set by os_status():
+ *   
+ *   status mode == 0 -> write to main text window
+ *.  status mode == 1 -> write to status line
+ *.  anything else -> do not display the text at all
+ *   
+ *   Implementations are free to omit any status line support, in which case
+ *   they should simply suppress all output when the status mode is anything
+ *   other than zero.
+ *   
+ *   The following special characters must be recognized in the displayed
+ *   text:
+ *   
+ *   '\n' - newline: end the current line and move the cursor to the start of
+ *   the next line.  If the status line is supported, and the current status
+ *   mode is 1 (i.e., displaying in the status line), then two special rules
+ *   apply to newline handling: newlines preceding any other text should be
+ *   ignored, and a newline following any other text should set the status
+ *   mode to 2, so that all subsequent output is suppressed until the status
+ *   mode is changed with an explicit call by the client program to
+ *   os_status().
+ *   
+ *   '\r' - carriage return: end the current line and move the cursor back to
+ *   the beginning of the current line.  Subsequent output is expected to
+ *   overwrite the text previously on this same line.  The implementation
+ *   may, if desired, IMMEDIATELY clear the previous text when the '\r' is
+ *   written, rather than waiting for subsequent text to be displayed.
+ *   
+ *   All other characters may be assumed to be ordinary printing characters.
+ *   The routine need not check for any other special characters.
+ *   
+ */
+void os_printz(const char *str);
+void os_print(const char *str, size_t len);
+
+/*
+ *   Print to the debugger console.  These routines are for interactive
+ *   debugger builds only: they display the given text to a separate window
+ *   within the debugger UI (separate from the main game command window)
+ *   where the debugger displays status information specific to the debugging
+ *   session (such as compiler/build output, breakpoint status messages,
+ *   etc).  For example, TADS Workbench on Windows displays these messages in
+ *   its "Debug Log" window.
+ *   
+ *   These routines only need to be implemented for interactive debugger
+ *   builds, such as TADS Workbench on Windows.  These can be omitted for
+ *   regular interpreter builds.  
+ */
+void os_dbg_printf(const char *fmt, ...);
+void os_dbg_vprintf(const char *fmt, va_list args);
+
+/*
+ *   Allocating sprintf and vsprintf.  These work like the regular C library
+ *   sprintf and vsprintf funtions, but they allocate a return buffer that's
+ *   big enough to hold the result, rather than formatting into a caller's
+ *   buffer.  This protects against buffer overruns and ensures that the
+ *   result isn't truncated.
+ *   
+ *   On return, '*bufptr' is filled in with a pointer to a buffer allocated
+ *   with osmalloc().  This buffer contains the formatted string result.  The
+ *   caller is responsible for freeing the buffer by calling osfree().
+ *   *bufptr can be null on return if an error occurs.
+ *   
+ *   The return value is the number of bytes written to the allocated buffer,
+ *   not including the null terminator.  If an error occurs, the return value
+ *   is -1 and *bufptr is undefined.
+ *   
+ *   Many modern C libraries provide equivalents of these, usually called
+ *   asprintf() and vasprintf(), respectively.  
+ */
+/* int os_asprintf(char **bufptr, const char *fmt, ...); */
+int os_vasprintf(char **bufptr, const char *fmt, va_list ap);
+
+
+/* 
+ *   Set the status line mode.  There are three possible settings:
+ *   
+ *   0 -> main text mode.  In this mode, all subsequent text written with
+ *   os_print() and os_printz() is to be displayed to the main text area.
+ *   This is the normal mode that should be in effect initially.  This mode
+ *   stays in effect until an explicit call to os_status().
+ *   
+ *   1 -> statusline mode.  In this mode, text written with os_print() and
+ *   os_printz() is written to the status line, which is usually rendered as
+ *   a one-line area across the top of the terminal screen or application
+ *   window.  In statusline mode, leading newlines ('\n' characters) are to
+ *   be ignored, and any newline following any other character must change
+ *   the mode to 2, as though os_status(2) had been called.
+ *   
+ *   2 -> suppress mode.  In this mode, all text written with os_print() and
+ *   os_printz() must simply be ignored, and not displayed at all.  This mode
+ *   stays in effect until an explicit call to os_status().  
+ */
+void os_status(int stat);
+
+/* get the status line mode */
+int os_get_status();
+
+/* 
+ *   Set the score value.  This displays the given score and turn counts on
+ *   the status line.  In most cases, these values are displayed at the right
+ *   edge of the status line, in the format "score/turns", but the format is
+ *   up to the implementation to determine.  In most cases, this can simply
+ *   be implemented as follows:
+ *   
+ *.  void os_score(int score, int turncount)
+ *.  {
+ *.     char buf[40];
+ *.     sprintf(buf, "%d/%d", score, turncount);
+ *.     os_strsc(buf);
+ *.  }
+ */
+void os_score(int score, int turncount);
+
+/* display a string in the score area in the status line */
+void os_strsc(const char *p);
+
+/* clear the screen */
+void oscls(void);
+
+/* redraw the screen */
+void os_redraw(void);
+
+/* flush any buffered display output */
+void os_flush(void);
+
+/*
+ *   Update the display - process any pending drawing immediately.  This
+ *   only needs to be implemented for operating systems that use
+ *   event-driven drawing based on window invalidations; the Windows and
+ *   Macintosh GUI's both use this method for drawing window contents.
+ *   
+ *   The purpose of this routine is to refresh the display prior to a
+ *   potentially long-running computation, to avoid the appearance that the
+ *   application is frozen during the computation delay.
+ *   
+ *   Platforms that don't need to process events in the main thread in order
+ *   to draw their window contents do not need to do anything here.  In
+ *   particular, text-mode implementations generally don't need to implement
+ *   this routine.
+ *   
+ *   This routine doesn't absolutely need a non-empty implementation on any
+ *   platform, but it will provide better visual feedback if implemented for
+ *   those platforms that do use event-driven drawing.  
+ */
+void os_update_display();
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Set text attributes.  Text subsequently displayed through os_print() and
+ *   os_printz() are to be displayed with the given attributes.
+ *   
+ *   'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values.  A value
+ *   of zero indicates normal text, with no extra attributes.  
+ */
+void os_set_text_attr(int attr);
+
+/* attribute code: bold-face */
+#define OS_ATTR_BOLD     0x0001
+
+/* attribute code: italic */
+#define OS_ATTR_ITALIC   0x0002
+
+/*
+ *   Abstract attribute codes.  Each platform can choose a custom rendering
+ *   for these by #defining them before this point, in the OS-specific header
+ *   (osdos.h, osmac.h, etc).  We provide *default* definitions in case the
+ *   platform doesn't define these.
+ *   
+ *   For compatibility with past versions, we treat HILITE, EM, and BOLD as
+ *   equivalent.  Platforms that can display multiple kinds of text
+ *   attributes (boldface and italic, say) should feel free to use more
+ *   conventional HTML mappings, such as EM->italic and STRONG->bold.  
+ */
+
+/* 
+ *   "Highlighted" text, as appropriate to the local platform.  On most
+ *   text-mode platforms, the only kind of rendering variation possible is a
+ *   brighter or intensified color.  If actual bold-face is available, that
+ *   can be used instead.  This is the attribute used for text enclosed in a
+ *   TADS2 "\( \)" sequence.  
+ */
+#ifndef OS_ATTR_HILITE
+# define OS_ATTR_HILITE  OS_ATTR_BOLD
+#endif
+
+/* HTML <em> attribute - by default, map this to bold-face */
+#ifndef OS_ATTR_EM
+# define OS_ATTR_EM      OS_ATTR_BOLD
+#endif
+
+/* HTML <strong> attribute - by default, this has no effect */
+#ifndef OS_ATTR_STRONG
+# define OS_ATTR_STRONG  0
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Colors.
+ *   
+ *   There are two ways of encoding a color.  First, a specific color can be
+ *   specified as an RGB (red-green-blue) value, with discreet levels for
+ *   each component's intensity, ranging from 0 to 255.  Second, a color can
+ *   be "parameterized," which doesn't specify a color in absolute terms but
+ *   rather specified one of a number of pre-defined *types* of colors;
+ *   these pre-defined types can be chosen by the OS implementation, or, on
+ *   some systems, selected by the user via a preferences mechanism.
+ *   
+ *   The os_color_t type encodes a color in 32 bits.  The high-order 8 bits
+ *   of a color value give the parameterized color identifier, or are set to
+ *   zero to indicate an RGB color.  An RGB color is encoded in the
+ *   low-order 24 bits, via the following formula:
+ *   
+ *   (R << 16) + (G << 8) + B
+ *   
+ *   R specifies the intensity of the red component of the color, G green,
+ *   and B blue.  Each of R, G, and B must be in the range 0-255.  
+ */
+typedef unsigned long os_color_t;
+
+/* encode an R, G, B triplet into an os_color_t value */
+#define os_rgb_color(r, g, b) (((r) << 16) + ((g) << 8) + (b))
+
+/* 
+ *   Determine if a color is given as an RGB value or as a parameterized
+ *   color value.  Returns true if the color is given as a parameterized
+ *   color (one of the OS_COLOR_xxx values), false if it's given as an
+ *   absolute RGB value.  
+ */
+#define os_color_is_param(color) (((color) & 0xFF000000) != 0)
+
+/* get the red/green/blue components of an os_color_t value */
+#define os_color_get_r(color) ((int)(((color) >> 16) & 0xFF))
+#define os_color_get_g(color) ((int)(((color) >> 8) & 0xFF))
+#define os_color_get_b(color) ((int)((color) & 0xFF))
+
+/*
+ *   Parameterized color codes.  These are os_color_t values that indicate
+ *   colors by type, rather than by absolute RGB values.  
+ */
+
+/* 
+ *   "transparent" - applicable to backgrounds only, this specifies that the
+ *   current screen background color should be used 
+ */
+#define OS_COLOR_P_TRANSPARENT ((os_color_t)0x01000000)
+
+/* "normal text" color (as set via user preferences, if applicable) */
+#define OS_COLOR_P_TEXT        ((os_color_t)0x02000000)
+
+/* normal text background color (from user preferences) */
+#define OS_COLOR_P_TEXTBG      ((os_color_t)0x03000000)
+
+/* "status line" text color (as set via user preferences, if applicable) */
+#define OS_COLOR_P_STATUSLINE  ((os_color_t)0x04000000)
+
+/* status line background color (from user preferences) */
+#define OS_COLOR_P_STATUSBG    ((os_color_t)0x05000000)
+
+/* input text color (as set via user preferences, if applicable) */
+#define OS_COLOR_P_INPUT       ((os_color_t)0x06000000)
+
+/*
+ *   Set the text foreground and background colors.  This sets the text
+ *   color for subsequent os_printf() and os_vprintf() calls.
+ *   
+ *   The background color can be OS_COLOR_TRANSPARENT, in which case the
+ *   background color is "inherited" from the current screen background.
+ *   Note that if the platform is capable of keeping old text for
+ *   "scrollback," then the transparency should be a permanent attribute of
+ *   the character - in other words, it should not be mapped to the current
+ *   screen color in the scrollback buffer, because doing so would keep the
+ *   current screen color even if the screen color changes in the future. 
+ *   
+ *   Text color support is optional.  If the platform doesn't support text
+ *   colors, this can simply do nothing.  If the platform supports text
+ *   colors, but the requested color or attributes cannot be displayed, the
+ *   implementation should use the best available approximation.  
+ */
+void os_set_text_color(os_color_t fg, os_color_t bg);
+
+/*
+ *   Set the screen background color.  This sets the text color for the
+ *   background of the screen.  If possible, this should immediately redraw
+ *   the main text area with this background color.  The color is given as an
+ *   OS_COLOR_xxx value.
+ *   
+ *   If the platform is capable of redisplaying the existing text, then any
+ *   existing text that was originally displayed with 'transparent'
+ *   background color should be redisplayed with the new screen background
+ *   color.  In other words, the 'transparent' background color of previously
+ *   drawn text should be a permanent attribute of the character - the color
+ *   should not be mapped on display to the then-current background color,
+ *   because doing so would lose the transparency and thus retain the old
+ *   screen color on a screen color change.  
+ */
+void os_set_screen_color(os_color_t color);
+
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   os_plain() - Use plain ascii mode for the display.  If possible and
+ *   necessary, turn off any text formatting effects, such as cursor
+ *   positioning, highlighting, or coloring.  If this routine is called,
+ *   the terminal should be treated as a simple text stream; users might
+ *   wish to use this mode for things like text-to-speech converters.
+ *   
+ *   Purely graphical implementations that cannot offer a textual mode
+ *   (such as Mac OS or Windows) can ignore this setting.
+ *   
+ *   If this routine is to be called, it must be called BEFORE os_init().
+ *   The implementation should set a flag so that os_init() will know to
+ *   set up the terminal for plain text output.  
+ */
+#ifndef os_plain
+/* 
+ *   some platforms (e.g. Mac OS) define this to be a null macro, so don't
+ *   define a prototype in those cases 
+ */
+void os_plain(void);
+#endif
+
+/*
+ *   Set the game title.  The output layer calls this routine when a game
+ *   sets its title (via an HTML <title> tag, for example).  If it's
+ *   convenient to do so, the OS layer can use this string to set a window
+ *   caption, or whatever else makes sense on each system.  Most
+ *   character-mode implementations will provide an empty implementation,
+ *   since there's not usually any standard way to show the current
+ *   application title on a character-mode display.  
+ */
+void os_set_title(const char *title);
+
+/*
+ *   Show the system-specific MORE prompt, and wait for the user to respond.
+ *   Before returning, remove the MORE prompt from the screen.
+ *   
+ *   This routine is only used and only needs to be implemented when the OS
+ *   layer takes responsibility for pagination; this will be the case on
+ *   most systems that use proportionally-spaced (variable-pitch) fonts or
+ *   variable-sized windows, since on such platforms the OS layer must do
+ *   most of the formatting work, leaving the standard output layer unable
+ *   to guess where pagination should occur.
+ *   
+ *   If the portable output formatter handles the MORE prompt, which is the
+ *   usual case for character-mode or terminal-style implementations, this
+ *   routine is not used and you don't need to provide an implementation.
+ *   Note that HTML TADS provides an implementation of this routine, because
+ *   the HTML renderer handles line breaking and thus must handle
+ *   pagination.  
+ */
+void os_more_prompt();
+
+/*
+ *   Interpreter Class Configuration.
+ *   
+ *   If this is a TEXT-ONLY interpreter: DO NOT define USE_HTML.
+ *   
+ *   If this is a MULTIMEDIA (HTML TADS) intepreter: #define USE_HTML
+ *   
+ *   (This really should be called something like OS_USE_HTML - the USE_ name
+ *   is for historical reasons.  This purpose of this macro is to configure
+ *   the tads 2 VM-level output formatter's line breaking and MORE mode
+ *   behavior.  In HTML mode, the VM-level formatter knows that it's feeding
+ *   its output to a page layout engine, so the VM-level output is just a
+ *   text stream.  In plain-text mode, the VM formatter *is* the page layout
+ *   engine, so it needs to do all of the word wrapping and MORE prompting
+ *   itself.  The tads 3 output layer does NOT use this macro for its
+ *   equivalent configuration, but instead has different .cpp files for the
+ *   different modes, and you simply link in the one for the configuration
+ *   you want.)  
+ */
+/* #define USE_HTML */
+
+
+/*
+ *   Enter HTML mode.  This is only used when the run-time is compiled
+ *   with the USE_HTML flag defined.  This call instructs the renderer
+ *   that HTML sequences should be parsed; until this call is made, the
+ *   renderer should not interpret output as HTML.  Non-HTML
+ *   implementations do not need to define this routine, since the
+ *   run-time will not call it if USE_HTML is not defined.  
+ */
+void os_start_html(void);
+
+/* exit HTML mode */
+void os_end_html(void);
+
+/*
+ *   Global variables with the height and width (in character cells - rows
+ *   and columns) of the main text display area into which os_printf
+ *   displays.  The height and width are given in text lines and character
+ *   columns, respectively.  The portable code can use these values to
+ *   format text for display via os_printf(); for example, the caller can
+ *   use the width to determine where to put line breaks.
+ *   
+ *   These values are only needed for systems where os_printf() doesn't
+ *   perform its own word-wrap formatting.  On systems such as the Mac,
+ *   where os_printf() performs word wrapping, these sizes aren't really
+ *   important because the portable code doesn't need to perform any real
+ *   formatting.
+ *   
+ *   These variables reflect the size of the "main text area," which is the
+ *   area of the screen excluding the status line and any "banner" windows
+ *   (as created with the os_banner_xxx() interfaces).
+ *   
+ *   The OS code must initialize these variables during start-up, and must
+ *   adjust them whenever the display size is changed by user action or
+ *   other external events (for example, if we're running inside a terminal
+ *   window, and the user resizes the window, the OS code must recalculate
+ *   the layout and adjust these accordingly).  
+ */
+extern int G_os_pagelength;
+extern int G_os_linewidth;
+
+/*
+ *   Global flag that tells the output formatter whether to count lines
+ *   that it's displaying against the total on the screen so far.  If this
+ *   variable is true, lines are counted, and the screen is paused with a
+ *   [More] message when it's full.  When not in MORE mode, lines aren't
+ *   counted.  This variable should be set to false when displaying text
+ *   that doesn't count against the current page, such as status line
+ *   information.
+ *   
+ *   This flag should not be modified by OS code.  Instead, the output
+ *   formatter will set this flag according to its current state; the OS
+ *   code can use this flag to determine whether or not to display a MORE
+ *   prompt during os_printf()-type operations.  Note that this flag is
+ *   normally interesting to the OS code only when the OS code itself is
+ *   handling the MORE prompt.  
+ */
+extern int G_os_moremode;
+
+/*
+ *   Global buffer containing the name of the byte-code file (the "game
+ *   file") loaded into the VM.  This is used only where applicable, which
+ *   generally means in TADS Interpreter builds.  In other application
+ *   builds, this is simply left empty.  The application is responsible for
+ *   setting this during start-up (or wherever else the byte-code filename
+ *   becomes known or changes).  
+ */
+extern char G_os_gamename[OSFNMAX];
+
+/*
+ *   Set non-stop mode.  This tells the OS layer that it should disable any
+ *   MORE prompting it would normally do.
+ *   
+ *   This routine is needed only when the OS layer handles MORE prompting; on
+ *   character-mode platforms, where the prompting is handled in the portable
+ *   console layer, this can be a dummy implementation.  
+ */
+void os_nonstop_mode(int flag);
+
+/* 
+ *   Update progress display with current info, if appropriate.  This can
+ *   be used to provide a status display during compilation.  Most
+ *   command-line implementations will just ignore this notification; this
+ *   can be used for GUI compiler implementations to provide regular
+ *   display updates during compilation to show the progress so far.  
+ */
+/* void os_progress(const char *fname, unsigned long linenum); */
+
+/* 
+ *   Set busy cursor.  If 'flag' is true, provide a visual representation
+ *   that the system or application is busy doing work.  If 'flag' is
+ *   false, remove any visual "busy" indication and show normal status.
+ *   
+ *   We provide a prototype here if your osxxx.h header file does not
+ *   #define a macro for os_csr_busy.  On many systems, this function has
+ *   no effect at all, so the osxxx.h header file simply #define's it to
+ *   do an empty macro.  
+ */
+#ifndef os_csr_busy
+void os_csr_busy(int flag);
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   User Input Routines
+ */
+
+/*
+ *   Ask the user for a filename, using a system-dependent dialog or other
+ *   mechanism.  Returns one of the OS_AFE_xxx status codes (see below).
+ *   
+ *   prompt_type is the type of prompt to provide -- this is one of the
+ *   OS_AFP_xxx codes (see below).  The OS implementation doesn't need to
+ *   pay any attention to this parameter, but it can be used if desired to
+ *   determine the type of dialog to present if the system provides
+ *   different types of dialogs for different types of operations.
+ *   
+ *   file_type is one of the OSFTxxx codes for system file type.  The OS
+ *   implementation is free to ignore this information, but can use it to
+ *   filter the list of files displayed if desired; this can also be used
+ *   to apply a default suffix on systems that use suffixes to indicate
+ *   file type.  If OSFTUNK is specified, it means that no filtering
+ *   should be performed, and no default suffix should be applied.  
+ */
+int os_askfile(const char *prompt, char *fname_buf, int fname_buf_len,
+               int prompt_type, os_filetype_t file_type);
+
+/* 
+ *   os_askfile status codes 
+ */
+
+/* success */
+#define OS_AFE_SUCCESS  0 
+
+/* 
+ *   Generic failure - this is largely provided for compatibility with
+ *   past versions, in which only zero and non-zero error codes were
+ *   meaningful; since TRUE is defined as 1 on most platforms, we assume
+ *   that 1 is probably the generic non-zero error code that most OS
+ *   implementations have traditionally used.  In addition, this can be
+ *   used to indicate any other error for which there is no more specific
+ *   error code.  
+ */
+#define OS_AFE_FAILURE  1
+
+/* user cancelled */
+#define OS_AFE_CANCEL   2
+
+/* 
+ *   os_askfile prompt types
+ *   
+ *   Important note: do not change these values when porting TADS.  These
+ *   values can be used by games, so they must be the same on all
+ *   platforms.  
+ */
+#define OS_AFP_OPEN    1     /* choose an existing file to open for reading */
+#define OS_AFP_SAVE    2          /* choose a filename for saving to a file */
+
+
+/* 
+ *   Read a string of input.  Fills in the buffer with a null-terminated
+ *   string containing a line of text read from the standard input.  The
+ *   returned string should NOT contain a trailing newline sequence.  On
+ *   success, returns 'buf'; on failure, including end of file, returns a
+ *   null pointer.  
+ */
+unsigned char *os_gets(unsigned char *buf, size_t bufl);
+
+/*
+ *   Read a string of input with an optional timeout.  This behaves like
+ *   os_gets(), in that it allows the user to edit a line of text (ideally
+ *   using the same editing keys that os_gets() does), showing the line of
+ *   text under construction during editing.  This routine differs from
+ *   os_gets() in that it returns if the given timeout interval expires
+ *   before the user presses Return (or the local equivalent).
+ *   
+ *   If the user presses Return before the timeout expires, we store the
+ *   command line in the given buffer, just as os_gets() would, and we return
+ *   OS_EVT_LINE.  We also update the display in the same manner that
+ *   os_gets() would, by moving the cursor to a new line and scrolling the
+ *   displayed text as needed.
+ *   
+ *   If a timeout occurs before the user presses Return, we store the command
+ *   line so far in the given buffer, statically store the cursor position,
+ *   insert mode, buffer text, and anything else relevant to the editing
+ *   state, and we return OS_EVT_TIMEOUT.
+ *   
+ *   If the implementation does not support the timeout operation, this
+ *   routine should simply return OS_EVT_NOTIMEOUT immediately when called;
+ *   the routine should not allow the user to perform any editing if the
+ *   timeout is not supported.  Callers must use the ordinary os_gets()
+ *   routine, which has no timeout capabilities, if the timeout is not
+ *   supported.
+ *   
+ *   When we return OS_EVT_TIMEOUT, the caller is responsible for doing one
+ *   of two things.
+ *   
+ *   The first possibility is that the caller performs some work that doesn't
+ *   require any display operations (in other words, the caller doesn't
+ *   invoke os_printf, os_getc, or anything else that would update the
+ *   display), and then calls os_gets_timeout() again.  In this case, we will
+ *   use the editing state that we statically stored before we returned
+ *   OS_EVT_TIMEOUT to continue editing where we left off.  This allows the
+ *   caller to perform some computation in the middle of user command editing
+ *   without interrupting the user - the extra computation is transparent to
+ *   the user, because we act as though we were still in the midst of the
+ *   original editing.
+ *   
+ *   The second possibility is that the caller wants to update the display.
+ *   In this case, the caller must call os_gets_cancel() BEFORE making any
+ *   display changes.  Then, the caller must do any post-input work of its
+ *   own, such as updating the display mode (for example, closing HTML font
+ *   tags that were opened at the start of the input).  The caller is now
+ *   free to do any display work it wants.
+ *   
+ *   If we have information stored from a previous call that was interrupted
+ *   by a timeout, and os_gets_cancel(TRUE) was never called, we will resume
+ *   editing where we left off when the cancelled call returned; this means
+ *   that we'll restore the cursor position, insertion state, and anything
+ *   else relevant.  Note that if os_gets_cancel(FALSE) was called, we must
+ *   re-display the command line under construction, but if os_gets_cancel()
+ *   was never called, we will not have to make any changes to the display at
+ *   all.
+ *   
+ *   Note that when resuming an interrupted editing session (interrupted via
+ *   os_gets_cancel()), the caller must re-display the prompt prior to
+ *   invoking this routine.
+ *   
+ *   Note that we can return OS_EVT_EOF in addition to the other codes
+ *   mentioned above.  OS_EVT_EOF indicates that an error occurred reading,
+ *   which usually indicates that the application is being terminated or that
+ *   some hardware error occurred reading the keyboard.  
+ *   
+ *   If 'use_timeout' is false, the timeout should be ignored.  Without a
+ *   timeout, the function behaves the same as os_gets(), except that it will
+ *   resume editing of a previously-interrupted command line if appropriate.
+ *   (This difference is why the timeout is optional: a caller might not need
+ *   a timeout, but might still want to resume a previous input that did time
+ *   out, in which case the caller would invoke this routine with
+ *   use_timeout==FALSE.  The regular os_gets() would not satisfy this need,
+ *   because it cannot resume an interrupted input.)
+ *   
+ *   Note that a zero timeout has the same meaning as for os_get_event(): if
+ *   input is available IMMEDIATELY, return the input, otherwise return
+ *   immediately with the OS_EVT_TIMEOUT result code.  
+ */
+int os_gets_timeout(unsigned char *buf, size_t bufl,
+                    unsigned long timeout_in_milliseconds, int use_timeout);
+
+/*
+ *   Cancel an interrupted editing session.  This MUST be called if any
+ *   output is to be displayed after a call to os_gets_timeout() returns
+ *   OS_EVT_TIMEOUT.
+ *   
+ *   'reset' indicates whether or not we will forget the input state saved
+ *   by os_gets_timeout() when it last returned.  If 'reset' is true, we'll
+ *   clear the input state, so that the next call to os_gets_timeout() will
+ *   start with an empty input buffer.  If 'reset' is false, we will retain
+ *   the previous input state, if any; this means that the next call to
+ *   os_gets_timeout() will re-display the same input buffer that was under
+ *   construction when it last returned.
+ *   
+ *   This routine need not be called if os_gets_timeout() is to be called
+ *   again with no other output operations between the previous
+ *   os_gets_timeout() call and the next one.
+ *   
+ *   Note that this routine needs only a trivial implementation when
+ *   os_gets_timeout() is not supported (i.e., the function always returns
+ *   OS_EVT_NOTIMEOUT).  
+ */
+void os_gets_cancel(int reset);
+
+/* 
+ *   Read a character from the keyboard.  For extended keystrokes, this
+ *   function returns zero, and then returns the CMD_xxx code for the
+ *   extended keystroke on the next call.  For example, if the user presses
+ *   the up-arrow key, the first call to os_getc() should return 0, and the
+ *   next call should return CMD_UP.  Refer to the CMD_xxx codes below.
+ *   
+ *   os_getc() should return a high-level, translated command code for
+ *   command editing.  This means that, where a functional interpretation of
+ *   a key and the raw key-cap interpretation both exist as CMD_xxx codes,
+ *   the functional interpretation should be returned.  For example, on Unix,
+ *   Ctrl-E is conventionally used in command editing to move to the end of
+ *   the line, following Emacs key bindings.  Hence, os_getc() should return
+ *   CMD_END for this keystroke, rather than a single 0x05 character (ASCII
+ *   Ctrl-E), because CMD_END is the high-level command code for the
+ *   operation.
+ *   
+ *   The translation ability of this function allows for system-dependent key
+ *   mappings to functional meanings.  
+ */
+int os_getc(void);
+
+/*
+ *   Read a character from the keyboard, following the same protocol as
+ *   os_getc() for CMD_xxx codes (i.e., when an extended keystroke is
+ *   encountered, os_getc_raw() returns zero, then returns the CMD_xxx code
+ *   on the subsequent call).
+ *   
+ *   This function differs from os_getc() in that this function returns the
+ *   low-level, untranslated key code whenever possible.  This means that,
+ *   when a functional interpretation of a key and the raw key-cap
+ *   interpretation both exist as CMD_xxx codes, this function returns the
+ *   key-cap interpretation.  For the Unix Ctrl-E example in the comments
+ *   describing os_getc() above, this function should return 5 (the ASCII
+ *   code for Ctrl-E), because the ASCII control character interpretation is
+ *   the low-level key code.
+ *   
+ *   This function should return all control keys using their ASCII control
+ *   codes, whenever possible.  Similarly, this function should return ASCII
+ *   27 for the Escape key, if possible.  
+ *   
+ *   For keys for which there is no portable ASCII representation, this
+ *   should return the CMD_xxx sequence.  So, this function acts exactly the
+ *   same as os_getc() for arrow keys, function keys, and other special keys
+ *   that have no ASCII representation.  This function returns a
+ *   non-translated version ONLY when an ASCII representation exists - in
+ *   practice, this means that this function and os_getc() vary only for CTRL
+ *   keys and Escape.  
+ */
+int os_getc_raw(void);
+
+
+/* wait for a character to become available from the keyboard */
+void os_waitc(void);
+
+/*
+ *   Constants for os_getc() when returning commands.  When used for
+ *   command line editing, special keys (arrows, END, etc.)  should cause
+ *   os_getc() to return 0, and return the appropriate CMD_ value on the
+ *   NEXT call.  Hence, os_getc() must keep the appropriate information
+ *   around statically for the next call when a command key is issued.
+ *   
+ *   The comments indicate which CMD_xxx codes are "translated" codes and
+ *   which are "raw"; the difference is that, when a particular keystroke
+ *   could be interpreted as two different CMD_xxx codes, one translated
+ *   and the other raw, os_getc() should always return the translated
+ *   version of the key, and os_getc_raw() should return the raw version.
+ */
+#define CMD_UP    1                        /* move up/up arrow (translated) */
+#define CMD_DOWN  2                    /* move down/down arrow (translated) */
+#define CMD_RIGHT 3                  /* move right/right arrow (translated) */
+#define CMD_LEFT  4                    /* move left/left arrow (translated) */
+#define CMD_END   5              /* move cursor to end of line (translated) */
+#define CMD_HOME  6            /* move cursor to start of line (translated) */
+#define CMD_DEOL  7                   /* delete to end of line (translated) */
+#define CMD_KILL  8                      /* delete entire line (translated) */
+#define CMD_DEL   9                /* delete current character (translated) */
+#define CMD_SCR   10                 /* toggle scrollback mode (translated) */
+#define CMD_PGUP  11                                /* page up (translated) */
+#define CMD_PGDN  12                              /* page down (translated) */
+#define CMD_TOP   13                            /* top of file (translated) */
+#define CMD_BOT   14                         /* bottom of file (translated) */
+#define CMD_F1    15                               /* function key F1 (raw) */
+#define CMD_F2    16                               /* function key F2 (raw) */
+#define CMD_F3    17                               /* function key F3 (raw) */
+#define CMD_F4    18                               /* function key F4 (raw) */
+#define CMD_F5    19                               /* function key F5 (raw) */
+#define CMD_F6    20                               /* function key F6 (raw) */
+#define CMD_F7    21                               /* function key F7 (raw) */
+#define CMD_F8    22                               /* function key F8 (raw) */
+#define CMD_F9    23                               /* function key F9 (raw) */
+#define CMD_F10   24                              /* function key F10 (raw) */
+#define CMD_CHOME 25                                  /* control-home (raw) */
+#define CMD_TAB   26                                    /* tab (translated) */
+#define CMD_SF2   27                                      /* shift-F2 (raw) */
+/* not used (obsolete) - 28 */
+#define CMD_WORD_LEFT  29      /* word left (ctrl-left on dos) (translated) */
+#define CMD_WORD_RIGHT 30    /* word right (ctrl-right on dos) (translated) */
+#define CMD_WORDKILL 31                   /* delete word right (translated) */
+#define CMD_EOF   32                                   /* end-of-file (raw) */
+#define CMD_BREAK 33     /* break (Ctrl-C or local equivalent) (translated) */
+#define CMD_INS   34                                    /* insert key (raw) */
+
+
+/*
+ *   ALT-keys - add alphabetical code to CMD_ALT: ALT-A == CMD_ALT + 0,
+ *   ALT-B == CMD_ALT + 1, ALT-C == CMD_ALT + 2, etc
+ *   
+ *   These keys are all raw (untranslated).  
+ */
+#define CMD_ALT   128                                  /* start of ALT keys */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Event information structure for os_get_event.  The appropriate union
+ *   member should be filled in, depending on the type of event that
+ *   occurs. 
+ */
+union os_event_info_t
+{
+    /* 
+     *   OS_EVT_KEY - this returns the one or two characters of the
+     *   keystroke.  If the key is an extended key, so that os_getc() would
+     *   return a two-character sequence for the keystroke, the first
+     *   character should be zero and the second the extended key code.
+     *   Otherwise, the first character should simply be the ASCII key code.
+     *   
+     *   The key code here is the "raw" keycode, equivalent to the codes
+     *   returned by os_getc_raw().  Note in particular that this means that
+     *   CTRL and Escape keys are presented as one-byte ASCII control
+     *   characters, not as two-byte CMD_xxx sequences.  
+     *   
+     *   For multi-byte character sets (Shift-JIS, for example), note that
+     *   os_get_event() must NOT return a complete two-byte character here.
+     *   The two bytes here are exclusively used to represent special
+     *   CMD_xxx keys (such as arrow keys and function keys).  For a
+     *   keystroke that is represented in a multi-byte character set using
+     *   more than one byte, os_get_event() must return a series of events.
+     *   Return an ordinary OS_EVT_KEY for the first byte of the sequence,
+     *   putting the byte in key[0]; then, return the second byte as a
+     *   separate OS_EVT_KEY as the next event; and so on for any additional
+     *   bytes.  This will allow callers that are not multibyte-aware to
+     *   treat multi-byte characters as though they were sequences of
+     *   one-byte characters.  
+     */
+    int key[2];
+
+    /*
+     *   OS_EVT_HREF - this returns the text of the HREF as a
+     *   null-terminated string.  
+     */
+    char href[256];
+
+    /* command ID (for OS_EVT_COMMAND) */
+    int cmd_id;
+};
+typedef union os_event_info_t os_event_info_t;
+
+/*
+ *   Event types for os_get_event 
+ */
+
+/* invalid/no event */
+#define OS_EVT_NONE      0x0000
+
+/* OS_EVT_KEY - user typed a key on the keyboard */
+#define OS_EVT_KEY       0x0001
+
+/* OS_EVT_TIMEOUT - no event occurred before the timeout elapsed */
+#define OS_EVT_TIMEOUT   0x0002
+
+/* 
+ *   OS_EVT_HREF - user clicked on a <A HREF> link.  This only applies to
+ *   the HTML-enabled run-time. 
+ */
+#define OS_EVT_HREF      0x0003
+
+/* 
+ *   OS_EVT_NOTIMEOUT - caller requested a timeout, but timeout is not
+ *   supported by this version of the run-time 
+ */
+#define OS_EVT_NOTIMEOUT 0x0004
+
+/*
+ *   OS_EVT_EOF - an error occurred reading the event.  This generally
+ *   means that the application is quitting or we can no longer read from
+ *   the keyboard or terminal. 
+ */
+#define OS_EVT_EOF       0x0005
+
+/* 
+ *   OS_EVT_LINE - user entered a line of text on the keyboard.  This event
+ *   is not returned from os_get_event(), but rather from os_gets_timeout().
+ */
+#define OS_EVT_LINE      0x0006
+
+
+/*
+ *   Get an input event.  The event types are shown above.  If use_timeout is
+ *   false, this routine should simply wait until one of the events it
+ *   recognizes occurs, then return the appropriate information on the event.
+ *   If use_timeout is true, this routine should return OS_EVT_TIMEOUT after
+ *   the given number of milliseconds elapses if no event occurs first.
+ *   
+ *   This function is not obligated to obey the timeout.  If a timeout is
+ *   specified and it is not possible to obey the timeout, the function
+ *   should simply return OS_EVT_NOTIMEOUT.  The trivial implementation thus
+ *   checks for a timeout, returns an error if specified, and otherwise
+ *   simply waits for the user to press a key.
+ *   
+ *   A timeout value of 0 does *not* mean that there's no timeout (i.e., it
+ *   doesn't mean we should wait indefinitely) - that's specified by passing
+ *   FALSE for use_timeout.  A zero timeout also doesn't meant that the
+ *   function should unconditionally return OS_EVT_TIMEOUT.  Instead, a zero
+ *   timeout specifically means that IF an event is available IMMEDIATELY,
+ *   without blocking the thread, we should return that event; otherwise we
+ *   should immediately return a timeout event.  
+ */
+int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout,
+                 os_event_info_t *info);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Extended os_get_event() codes.
+ *   
+ *   THESE ARE NOT USED in the basic osifc implementation - these are only
+ *   used if the interpreter supports the "extended" interface defined in
+ *   osifcext.h.  
+ */
+
+/*
+ *   System menu command event.  Some interpreters (such as HTML TADS for
+ *   Windows) provide menu commands for common system-level game verbs -
+ *   SAVE, RESTORE, UNDO, QUIT.  By default, these commands are returned as
+ *   literal command strings ("save", "restore", etc) via os_gets(), as
+ *   though the user had typed the commands at the keyboard.  The extended OS
+ *   interface allows the program to receive these commands via
+ *   os_get_event().  When a command is enabled for os_get_event() via the
+ *   extended OS interface, and the player selects the command via a menu (or
+ *   toolbar button, etc) during a call to os_get_event(), os_get_event()
+ *   returns this event code, with the menu ID stored in the cmd_id field of
+ *   the event structure.  
+ */
+#define OS_EVT_COMMAND   0x0100
+
+/* command IDs for OS_EVT_COMMAND */
+#define OS_CMD_NONE      0x0000     /* invalid command ID, for internal use */
+#define OS_CMD_SAVE      0x0001                                /* save game */
+#define OS_CMD_RESTORE   0x0002                             /* restore game */
+#define OS_CMD_UNDO      0x0003                           /* undo last turn */
+#define OS_CMD_QUIT      0x0004                                /* quit game */
+#define OS_CMD_CLOSE     0x0005                    /* close the game window */
+#define OS_CMD_HELP      0x0006                           /* show game help */
+
+/* highest command ID used in this version of the interface */
+#define OS_CMD_LAST      0x0006
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Ask for input through a dialog.
+ *   
+ *   'prompt' is a text string to display as a prompting message.  For
+ *   graphical systems, this message should be displayed in the dialog;
+ *   for text systems, this should be displayed on the terminal after a
+ *   newline.
+ *   
+ *   'standard_button_set' is one of the OS_INDLG_xxx values defined
+ *   below, or zero.  If this value is zero, no standard button set is to
+ *   be used; the custom set of buttons defined in 'buttons' is to be used
+ *   instead.  If this value is non-zero, the appropriate set of standard
+ *   buttons, with labels translated to the local language if possible, is
+ *   to be used.
+ *   
+ *   'buttons' is an array of strings to use as button labels.
+ *   'button_count' gives the number of entries in the 'buttons' array.
+ *   'buttons' and 'button_count' are ignored if 'standard_button_set' is
+ *   non-zero, since a standard set of buttons is used instead.  If
+ *   'buttons' and 'button_count' are to be used, each entry contains the
+ *   label of a button to show.  
+ */
+/*   
+ *   An ampersand ('&') character in a label string indicates that the
+ *   next character after the '&' is to be used as the short-cut key for
+ *   the button, if supported.  The '&' should NOT be displayed in the
+ *   string; instead, the character should be highlighted according to
+ *   local system conventions.  On Windows, for example, the short-cut
+ *   character should be shown underlined; on a text display, the response
+ *   might be shown with the short-cut character enclosed in parentheses.
+ *   If there is no local convention for displaying a short-cut character,
+ *   then the '&' should simply be removed from the displayed text.  
+ *   
+ *   The short-cut key specified by each '&' character should be used in
+ *   processing responses.  If the user presses the key corresponding to a
+ *   button's short-cut, the effect should be the same as if the user
+ *   clicked the button with the mouse.  If local system conventions don't
+ *   allow for short-cut keys, any short-cut keys can be ignored.
+ *   
+ *   'default_index' is the 1-based index of the button to use as the
+ *   default.  If this value is zero, there is no default response.  If
+ *   the user performs the appropriate system-specific action to select
+ *   the default response for the dialog, this is the response that is to
+ *   be selected.  On Windows, for example, pressing the "Return" key
+ *   should select this item.
+ *   
+ *   'cancel_index' is the 1-based index of the button to use as the
+ *   cancel response.  If this value is zero, there is no cancel response.
+ *   This is the response to be used if the user cancels the dialog using
+ *   the appropriate system-specific action.  On Windows, for example,
+ *   pressing the "Escape" key should select this item.  
+ */
+/*
+ *   icon_id is one of the OS_INDLG_ICON_xxx values defined below.  If
+ *   possible, an appropriate icon should be displayed in the dialog.
+ *   This can be ignored in text mode, and also in GUI mode if there is no
+ *   appropriate system icon.
+ *   
+ *   The return value is the 1-based index of the response selected.  If
+ *   an error occurs, return 0.  
+ */
+int os_input_dialog(int icon_id, const char *prompt, int standard_button_set,
+                    const char **buttons, int button_count,
+                    int default_index, int cancel_index);
+
+/*
+ *   Standard button set ID's 
+ */
+
+/* OK */
+#define OS_INDLG_OK            1
+
+/* OK, Cancel */
+#define OS_INDLG_OKCANCEL      2
+
+/* Yes, No */
+#define OS_INDLG_YESNO         3
+
+/* Yes, No, Cancel */
+#define OS_INDLG_YESNOCANCEL   4
+
+/*
+ *   Dialog icons 
+ */
+
+/* no icon */
+#define OS_INDLG_ICON_NONE     0
+
+/* warning */
+#define OS_INDLG_ICON_WARNING  1
+
+/* information */
+#define OS_INDLG_ICON_INFO     2
+
+/* question */
+#define OS_INDLG_ICON_QUESTION 3
+
+/* error */
+#define OS_INDLG_ICON_ERROR    4
+
+
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   OS main entrypoint 
+ */
+int os0main(int oargc, char **oargv,
+            int (*mainfn)(int, char **, char *), 
+            const char *before, const char *config);
+
+/* 
+ *   new-style OS main entrypoint - takes an application container context 
+ */
+int os0main2(int oargc, char **oargv,
+             int (*mainfn)(int, char **, struct appctxdef *, char *),
+             const char *before, const char *config,
+             struct appctxdef *appctx);
+
+/*
+ *   OBSOLETE - Get filename from startup parameter, if possible; returns
+ *   true and fills in the buffer with the parameter filename on success,
+ *   false if no parameter file could be found.
+ *   
+ *   (This was used until TADS 2.2.5 for the benefit of the Mac interpreter,
+ *   and interpreters on systems with similar desktop shells, to allow the
+ *   user to launch the terp by double-clicking on a saved game file.  The
+ *   terp would read the launch parameters, discover that a saved game had
+ *   been used to invoke it, and would then stash away the saved game info
+ *   for later retrieval from this function.  This functionality was replaced
+ *   in 2.2.5 with a command-line parameter: the terp now uses the desktop
+ *   launch data to synthesize a suitable argv[] vectro to pass to os0main()
+ *   or os0main2().  This function should now simply be stubbed out - it
+ *   should simply return FALSE.)  
+ */
+int os_paramfile(char *buf);
+
+/* 
+ *   Initialize.  This should be called during program startup to
+ *   initialize the OS layer and check OS-specific command-line arguments.
+ *   
+ *   If 'prompt' and 'buf' are non-null, and there are no arguments on the
+ *   given command line, the OS code can use the prompt to ask the user to
+ *   supply a filename, then store the filename in 'buf' and set up
+ *   argc/argv to give a one-argument command string.  (This mechanism for
+ *   prompting for a filename is obsolescent, and is retained for
+ *   compatibility with a small number of existing implementations only;
+ *   new implementations should ignore this mechanism and leave the
+ *   argc/argv values unchanged.)  
+ */
+int os_init(int *argc, char *argv[], const char *prompt,
+            char *buf, int bufsiz);
+
+/*
+ *   Termination functions.  There are three main termination functions,
+ *   described individually below; here's a brief overview of the
+ *   relationship among the functions.  The important thing to realize is
+ *   that these functions have completely independent purposes; they should
+ *   never call one another, and they should never do any of the work that's
+ *   intended for the others.
+ *   
+ *   os_uninit() is meant to undo the effects of os_init().  On many
+ *   systems, os_init() has some global effect, such as setting the terminal
+ *   to some special input or output mode.  os_uninit's purpose is to undo
+ *   these global effects, returning the terminal mode (and whatever else)
+ *   to the conditions they were in at program startup.  Portable callers
+ *   are meant to call this routine at some point before terminating if they
+ *   ever called os_init().  Note that this routine DOES NOT terminate the
+ *   program - it should simply undo anything that os_init() did and return,
+ *   to let the caller do any additional termination work of its own.
+ *   
+ *   os_expause() optionally pauses before termination, to allow the user to
+ *   acknowledge any text the program displays just before exiting.  This
+ *   doesn't have to do anything at all, but it's useful on systems where
+ *   program termination will do something drastic like close the window:
+ *   without a pause, the user wouldn't have a chance to read any text the
+ *   program displayed after the last interactive input, because the window
+ *   would abruptly disappear moments after the text was displayed.  For
+ *   systems where termination doesn't have such drastic effects, there's no
+ *   need to do anything in this routine.  Because it's up to this routine
+ *   whether or not to pause, this routine must display a prompt if it's
+ *   going to pause for user input - the portable caller obviously can't do
+ *   so, because the caller doesn't know if the routine is going to pause or
+ *   not (so if the caller displayed a prompt assuming the routine would
+ *   pause, the prompt would show up even on systems where there actually is
+ *   no pause, which would be confusing).  This routine DOES NOT terminate
+ *   the program; it simply pauses if necessary to allow the user to
+ *   acknowledge the last bit of text the program displayed, then returns to
+ *   allow the caller to carry on with its own termination work.
+ *   
+ *   os_term() is meant to perform the same function as the C standard
+ *   library routine exit(): this actually terminates the program, exiting
+ *   to the operating system.  This routine is not meant to return to its
+ *   caller, because it's supposed to exit the program directly.  For many
+ *   systems, this routine can simply call exit(); the portable code calls
+ *   this routine instead of calling exit() directly, because some systems
+ *   have their own OS-specific way of terminating rather than using exit().
+ *   This routine MUST NOT undo the effects of os_init(), because callers
+ *   might not have ever called os_init(); callers are required to call
+ *   os_uninit() if they ever called os_init(), before calling os_term(), so
+ *   this routine can simply assume that any global modes set by os_init()
+ *   have already been undone by the time this is called.  
+ */
+
+/*
+ *   Uninitialize.  This is called prior to progam termination to reverse
+ *   the effect of any changes made in os_init().  For example, if
+ *   os_init() put the terminal in raw mode, this should restore the
+ *   previous terminal mode.  This routine should not terminate the
+ *   program (so don't call exit() here) - the caller might have more
+ *   processing to perform after this routine returns.  
+ */
+void os_uninit(void);
+
+/* 
+ *   Pause prior to exit, if desired.  This is meant to be called by
+ *   portable code just before the program is to be terminated; it can be
+ *   implemented to show a prompt and wait for user acknowledgment before
+ *   proceeding.  This is useful for implementations that are using
+ *   something like a character-mode terminal window running on a graphical
+ *   operating system: this gives the implementation a chance to pause
+ *   before exiting, so that the window doesn't just disappear
+ *   unceremoniously.
+ *   
+ *   This is allowed to do nothing at all.  For regular character-mode
+ *   systems, this routine usually doesn't do anything, because when the
+ *   program exits, the terminal will simply return to the OS command
+ *   prompt; none of the text displayed just before the program exited will
+ *   be lost, so there's no need for any interactive pause.  Likewise, for
+ *   graphical systems where the window will remain open, even after the
+ *   program exits, until the user explicitly closes the window, there's no
+ *   need to do anything here.
+ *   
+ *   If this is implemented to pause, then this routine MUST show some kind
+ *   of prompt to let the user know we're waiting.  In the simple case of a
+ *   text-mode terminal window on a graphical OS, this should simply print
+ *   out some prompt text ("Press a key to exit...") and then wait for the
+ *   user to acknowledge the prompt (by pressing a key, for example).  For
+ *   graphical systems, the prompt could be placed in the window's title
+ *   bar, or status-bar, or wherever is appropriate for the OS.  
+ */
+void os_expause(void);
+
+/* 
+ *   Terminate.  This should exit the program with the given exit status.
+ *   In general, this should be equivalent to the standard C library
+ *   exit() function, but we define this interface to allow the OS code to
+ *   do any necessary pre-termination cleanup.  
+ */
+void os_term(int status);
+
+/* 
+ *   Install/uninstall the break handler.  If possible, the OS code should
+ *   set (if 'install' is true) or clear (if 'install' is false) a signal
+ *   handler for keyboard break signals (control-C, etc, depending on
+ *   local convention).  The OS code should set its own handler routine,
+ *   which should note that a break occurred with an internal flag; the
+ *   portable code uses os_break() from time to time to poll this flag.  
+ */
+void os_instbrk(int install);
+
+/*
+ *   Check for user break ("control-C", etc) - returns true if a break is
+ *   pending, false if not.  If this returns true, it should "consume" the
+ *   pending break (probably by simply clearing the OS code's internal
+ *   break-pending flag).  
+ */
+int os_break(void);
+
+/*
+ *   Sleep for a given interval.  This should simply pause for the given
+ *   number of milliseconds, then return.  On multi-tasking systems, this
+ *   should use a system API to suspend the current process for the desired
+ *   delay; on single-tasking systems, this can simply sit in a wait loop
+ *   until the desired interval has elapsed.  
+ */
+void os_sleep_ms(long delay_in_milliseconds);
+
+/*
+ *   Yield CPU; returns TRUE if user requested an interrupt (a "control-C"
+ *   type of operation to abort the entire program), FALSE to continue.
+ *   Portable code should call this routine from time to time during lengthy
+ *   computations that don't involve any UI operations; if practical, this
+ *   routine should be invoked roughly every 10 to 100 milliseconds.
+ *   
+ *   The purpose of this routine is to support "cooperative multitasking"
+ *   systems, such as pre-X MacOS, where it's necessary for each running
+ *   program to call the operating system explicitly in order to yield the
+ *   CPU from time to time to other concurrently running programs.  On
+ *   cooperative multitasking systems, a program can only lose control of
+ *   the CPU by making specific system calls, usually related to GUI events;
+ *   a program can never lose control of the CPU asynchronously.  So, a
+ *   program that performs lengthy computations without any UI interaction
+ *   can cause the rest of the system to freeze up until the computations
+ *   are finished; but if a compute-intensive program explicitly yields the
+ *   CPU from time to time, it allows other programs to remain responsive.
+ *   Yielding the CPU at least every 100 milliseconds or so will generally
+ *   allow the UI to remain responsive; yielding more frequently than every
+ *   10 ms or so will probably start adding noticeable overhead.
+ *   
+ *   On single-tasking systems (such as MS-DOS), there's only one program
+ *   running at a time, so there's no need to yield the CPU; on virtually
+ *   every modern system, the OS automatically schedules CPU time without
+ *   the running programs having any say in the matter, so again there's no
+ *   need for a program to yield the CPU.  For systems where this routine
+ *   isn't needed, the system header should simply #define os_yield to
+ *   something like "((void)0)" - this will allow the compiler to completely
+ *   ignore calls to this routine for systems where they aren't needed.
+ *   
+ *   Note that this routine is NOT meant to provide scheduling hinting to
+ *   modern systems with true multitasking, so a trivial implementation is
+ *   fine for any modern system.  
+ */
+#ifndef os_yield
+int os_yield(void);
+#endif
+
+/*
+ *   Set the default saved-game extension.  This routine will NOT be called
+ *   when we're using the standard saved game extension; this routine will be
+ *   invoked only if we're running as a stand-alone game, and the game author
+ *   specified a non-standard saved-game extension when creating the
+ *   stand-alone game.
+ *   
+ *   This routine is not required if the system does not use the standard,
+ *   semi-portable os0.c implementation.  Even if the system uses the
+ *   standard os0.c implementation, it can provide an empty routine here if
+ *   the system code doesn't need to do anything special with this
+ *   information.
+ *   
+ *   The extension is specified as a null-terminated string.  The extension
+ *   does NOT include the leading period.  
+ */
+void os_set_save_ext(const char *ext);
+
+/* 
+ *   Get the saved game extension previously set with os_set_save_ext().
+ *   Returns null if no custom extension has been set.
+ */
+const char *os_get_save_ext();
+
+
+/* ------------------------------------------------------------------------*/
+/*
+ *   Translate a character from the HTML 4 Unicode character set to the
+ *   current character set used for display.  Takes an HTML 4 character
+ *   code and returns the appropriate local character code.
+ *   
+ *   The result buffer should be filled in with a null-terminated string
+ *   that should be used to represent the character.  Multi-character
+ *   results are possible, which may be useful for certain approximations
+ *   (such as using "(c)" for the copyright symbol).
+ *   
+ *   Note that we only define this prototype if this symbol isn't already
+ *   defined as a macro, which may be the case on some platforms.
+ *   Alternatively, if the function is already defined (for example, as an
+ *   inline function), the defining code can define OS_XLAT_HTML4_DEFINED,
+ *   in which case we'll also omit this prototype.
+ *   
+ *   Important: this routine provides the *default* mapping that is used
+ *   when no external character mapping file is present, and for any named
+ *   entities not defined in the mapping file.  Any entities in the
+ *   mapping file, if used, will override this routine.
+ *   
+ *   A trivial implementation of this routine (that simply returns a
+ *   one-character result consisting of the original input character,
+ *   truncated to eight bits if necessary) can be used if you want to
+ *   require an external mapping file to be used for any game that
+ *   includes HTML character entities.  The DOS version implements this
+ *   routine so that games will still look reasonable when played with no
+ *   mapping file present, but other systems are not required to do this.  
+ */
+#ifndef os_xlat_html4
+# ifndef OS_XLAT_HTML4_DEFINED
+void os_xlat_html4(unsigned int html4_char,
+                   char *result, size_t result_buf_len);
+# endif
+#endif
+
+/*
+ *   Generate a filename for a character-set mapping file.  This function
+ *   should determine the current native character set in use, if
+ *   possible, then generate a filename, according to system-specific
+ *   conventions, that we should attempt to load to get a mapping between
+ *   the current native character set and the internal character set
+ *   identified by 'internal_id'.
+ *   
+ *   The internal character set ID is a string of up to 4 characters.
+ *   
+ *   On DOS, the native character set is a DOS code page.  DOS code pages
+ *   are identified by 3- or 4-digit identifiers; for example, code page
+ *   437 is the default US ASCII DOS code page.  We generate the
+ *   character-set mapping filename by appending the internal character
+ *   set identifier to the DOS code page number, then appending ".TCP" to
+ *   the result.  So, to map between ISO Latin-1 (internal ID = "La1") and
+ *   DOS code page 437, we would generate the filename "437La1.TCP".
+ *   
+ *   Note that this function should do only two things.  First, determine
+ *   the current native character set that's in use.  Second, generate a
+ *   filename based on the current native code page and the internal ID.
+ *   This function is NOT responsible for figuring out the mapping or
+ *   anything like that -- it's simply where we generate the correct
+ *   filename based on local convention.
+ *   
+ *   'filename' is a buffer of at least OSFNMAX characters.
+ *   
+ *   'argv0' is the executable filename from the original command line.
+ *   This parameter is provided so that the system code can look for
+ *   mapping files in the original TADS executables directory, if desired.
+ */
+void os_gen_charmap_filename(char *filename, char *internal_id,
+                             char *argv0);
+
+/*
+ *   Receive notification that a character mapping file has been loaded.
+ *   The caller doesn't require this routine to do anything at all; this
+ *   is purely for the system-dependent code's use so that it can take
+ *   care of any initialization that it must do after the caller has
+ *   loaded a charater mapping file.  'id' is the character set ID, and
+ *   'ldesc' is the display name of the character set.  'sysinfo' is the
+ *   extra system information string that is stored in the mapping file;
+ *   the interpretation of this information is up to this routine.
+ *   
+ *   For reference, the Windows version uses the extra information as a
+ *   code page identifier, and chooses its default font character set to
+ *   match the code page.  On DOS, the run-time requires the player to
+ *   activate an appropriate code page using a DOS command (MODE CON CP
+ *   SELECT) prior to starting the run-time, so this routine doesn't do
+ *   anything at all on DOS. 
+ */
+void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo);
+
+/*
+ *   Generate the name of the character set mapping table for Unicode
+ *   characters to and from the given local character set.  Fills in the
+ *   buffer with the implementation-dependent name of the desired
+ *   character set map.  See below for the character set ID codes.
+ *   
+ *   For example, on Windows, the implementation would obtain the
+ *   appropriate active code page (which is simply a Windows character set
+ *   identifier number) from the operating system, and build the name of
+ *   the Unicode mapping file for that code page, such as "CP1252".  On
+ *   Macintosh, the implementation would look up the current script system
+ *   and return the name of the Unicode mapping for that script system,
+ *   such as "ROMAN" or "CENTEURO".
+ *   
+ *   If it is not possible to determine the specific character set that is
+ *   in use, this function should return "asc7dflt" (ASCII 7-bit default)
+ *   as the character set identifier on an ASCII system, or an appropriate
+ *   base character set name on a non-ASCII system.  "asc7dflt" is the
+ *   generic character set mapping for plain ASCII characters.
+ *   
+ *   The given buffer must be at least 32 bytes long; the implementation
+ *   must limit the result it stores to 32 bytes.  (We use a fixed-size
+ *   buffer in this interface for simplicity, and because there seems no
+ *   need for greater flexibility in the interface; a character set name
+ *   doesn't carry very much information so shouldn't need to be very
+ *   long.  Note that this function doesn't generate a filename, but
+ *   simply a mapping name; in practice, a map name will be used to
+ *   construct a mapping file name.)
+ *   
+ *   Because this function obtains the Unicode mapping name, there is no
+ *   need to specify the internal character set to be used: the internal
+ *   character set is Unicode.  
+ */
+/*
+ *   Implementation note: when porting this routine, the convention that
+ *   you use to name your mapping files is up to you.  You should simply
+ *   choose a convention for this implementation, and then use the same
+ *   convention for packaging the mapping files for your OS release.  In
+ *   most cases, the best convention is to use the names that the Unicode
+ *   consortium uses in their published cross-mapping listings, since
+ *   these listings can be used as the basis of the mapping files that you
+ *   include with your release.  For example, on Windows, the convention
+ *   is to use the code page number to construct the map name, as in
+ *   CP1252 or CP1250.  
+ */
+void os_get_charmap(char *mapname, int charmap_id);
+
+/*
+ *   Character map for the display (i.e., for the user interface).  This
+ *   is the character set which is used for input read from the keyboard,
+ *   and for output displayed on the monitor or terminal.  
+ */
+#define OS_CHARMAP_DISPLAY     1
+
+/* 
+ *   Character map for mapping filename strings.  This should identify the
+ *   character set currently in use on the local system for filenames
+ *   (i.e., for strings identifying objects in the local file system),
+ *   providing a suitable name for use in opening a mapping file.
+ *   
+ *   On many platforms, the entire system uses only one character set at
+ *   the OS level, so the file system character set is the same as the
+ *   display character set.  Some systems define a particular character
+ *   set for file system use, though, because different users might be
+ *   running applications on terminals that display different character
+ *   sets.  
+ */
+#define OS_CHARMAP_FILENAME    2
+
+/*
+ *   Default character map for file contents.  On most systems, this will
+ *   be the same as display.  On some systems, it won't be possible to
+ *   know in general what character set files use; in fact, this isn't
+ *   possible anywhere, since a file might have been copied without
+ *   translation from a different type of computer using a different
+ *   character set.  So, this isn't meant to provide a reliable choice of
+ *   character set for any arbitrary file; it's simply meant to be a good
+ *   guess that most files on this system are likely to use.  
+ */
+#define OS_CHARMAP_FILECONTENTS  3
+
+/*
+ *   Default character map for the command line.  This is the maping we use
+ *   to interpret command line arguments passed to our main() or equivalent.
+ *   On most systems, this will be the same as the display character set.
+ */
+#define OS_CHARMAP_CMDLINE     4
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   External Banner Interface.  This interface provides the ability to
+ *   divide the display window into multiple sub-windows, each with its own
+ *   independent contents.
+ *   
+ *   To determine where a new banner is displayed, we look at the banners as
+ *   a tree, rooted at the "main window," the special banner that the system
+ *   automatically creates initially for the main game text.  We start by
+ *   allocating the entire display (or the entire application window, if
+ *   we're running on a GUI system) to the main window.  We then traverse
+ *   the tree, starting with the root window's children.  For each child
+ *   window, we allocate space for the child out of the parent window's
+ *   area, according to the child's alignment and size settings, and deduct
+ *   this space from the parent window's size.  We then lay out the children
+ *   of the child.
+ *   
+ *   For each banner window, we take its requested space out of the parent
+ *   window's area by starting at the edge of the parent window rectangle as
+ *   indicated by the banner's alignment, and taking the requested `width
+ *   (for a left/right banner) or height (for a top/bottom banner), limiting
+ *   to the available width/height in the parent window's space.  Give the
+ *   banner the full extent of the parent's space in its other dimension (so
+ *   a left/right banner gets the full height of the parent space, and a
+ *   top/bottom banner gets the full width).
+ *   
+ *   Note that the layout proceeds exclusively down the tree (i.e., from the
+ *   root to children to grandchildren, and so on).  It *appears* that a
+ *   child affects its parent, because of the deduction step: a child
+ *   acquires screen space by carving out a chunk of its parent.  The right
+ *   way to think about this, though, is that the parent's full area is the
+ *   union of the parent window and all of its children; when viewed this
+ *   way, the parent's full area is fully determined the instant the parent
+ *   is laid out, and never changes as its children are laid out.  Note in
+ *   particular that a child can never make a parent larger; the only thing
+ *   a child can do to a parent is carve out a chunk of the parent for
+ *   itself, which doesn't affect the boundaries of the union of the parent
+ *   plus its children.
+ *   
+ *   Note also that if the banner has a border, and the implementation
+ *   actually draws borders, the border must be drawn for the *full* area of
+ *   the banner, as defined above.  For example, suppose we have two
+ *   borders: banner A is a child of the main window, is top-aligned, and
+ *   has a border.  Banner B is a child of banner A, right-aligned, with no
+ *   border.  Obviously, without considering banner B, banner A's space runs
+ *   across the entire width of the main window, so its border (at the
+ *   bottom of its area) runs across the entire width of the main window.
+ *   Banner B carves out some space from A's right side for itself, so
+ *   banner A's actual on-screen area runs from the left edge of the main
+ *   window to banner B's left edge.  However, even though banner A itself
+ *   no longer runs the full width of the main window, banner A's *full*
+ *   area - that is, the union of banner A's on-screen area and all of its
+ *   children's full areas - does still run the entire width of the main
+ *   window, hence banner A's border must still run the full width of the
+ *   main window.  The simple way of looking at this is that a banner's
+ *   border is always to be drawn exactly the same way, regardless of
+ *   whether or not the banner has children - simply draw the banner as it
+ *   would be drawn if the banner had no children.
+ *   
+ *   Each time a banner is added or removed, we must recalculate the layout
+ *   of the remaining banners and main text area.  The os_banner_xxx()
+ *   implementation is responsible for this layout refiguring.
+ *   


Commit: e83972f502142b4e6191b962a7be89829bd8d708
    https://github.com/scummvm/scummvm/commit/e83972f502142b4e6191b962a7be89829bd8d708
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS2: Added code for tokenizer & vocabulary

Changed paths:
  A engines/glk/tads/tads2/line_source_file.cpp
  A engines/glk/tads/tads2/line_source_file.h
    engines/glk/module.mk
    engines/glk/tads/osfrobtads.cpp
    engines/glk/tads/osfrobtads.h
    engines/glk/tads/tads2/file_io.h
    engines/glk/tads/tads2/lib.h
    engines/glk/tads/tads2/tads2.cpp
    engines/glk/tads/tads2/tokenizer.cpp
    engines/glk/tads/tads2/tokenizer.h
    engines/glk/tads/tads2/vocabulary.cpp
    engines/glk/tads/tads2/vocabulary.h


diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 43c2523..9f95fc0 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -99,6 +99,7 @@ MODULE_OBJS := \
 	tads/tads2/error.o \
 	tads/tads2/error_handling.o \
 	tads/tads2/file_io.o \
+	tads/tads2/line_source_file.o \
 	tads/tads2/memory_cache.o \
 	tads/tads2/memory_cache_heap.o \
 	tads/tads2/memory_cache_loader.o \
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index fbcf1c1..13229e5 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -34,8 +34,20 @@ osfildef *osfoprb(const char *fname, os_filetype_t typ) {
 		return nullptr;
 }
 
+osfildef *osfoprwtb(const char *fname, os_filetype_t typ) {
+	Common::DumpFile *df = new Common::DumpFile();
+	if (df->open(fname))
+		return df;
+	delete df;
+	return nullptr;
+}
+
 int osfrb(osfildef *fp, void *buf, size_t count) {
-	return fp->read(buf, count);
+	return dynamic_cast<Common::ReadStream *>(fp)->read(buf, count);
+}
+
+bool osfwb(osfildef *fp, void *buf, size_t count) {
+	return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count;
 }
 
 } // End of namespace TADS
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index 2dce84c..b22dbae 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -38,6 +38,12 @@
 namespace Glk {
 namespace TADS {
 
+#define OSPATHCHAR '/'
+#define OSPATHALT ""
+#define OSPATHURL "/"
+#define OSPATHSEP ':'
+#define OS_NEWLINE_SEQ "\n"
+
 
 /* Defined for Gargoyle. */
 #define HAVE_STDINT_
@@ -92,8 +98,12 @@ namespace TADS {
 
 #define OSFNMAX 255
 
-/* File handle structure for osfxxx functions. */
-typedef Common::SeekableReadStream osfildef;
+/**
+ * File handle structure for osfxxx functions
+ * Note that we need to define it as a Common::Stream since the type is used by
+ * TADS for both reading and writing files
+ */
+typedef Common::Stream osfildef;
 
 /* Directory handle for searches via os_open_dir() et al. */
 typedef Common::FSNode *osdirhdl_t;
@@ -188,18 +198,18 @@ osfildef *osfoprwt(const char *fname, os_filetype_t typ);
 #define osfoprs osfoprt
 
 /* Open binary file for reading. */
-osfildef *osfoprb(const char *fname, os_filetype_t typ);
+inline osfildef *osfoprb(const char *fname, os_filetype_t typ);
 
 /* Open binary file for reading/writing.  If the file already exists,
  * keep the existing contents.  Create a new file if it doesn't already
  * exist. */
 osfildef*
-osfoprwb( const char* fname, os_filetype_t typ );
+osfoprwb(const char *fname, os_filetype_t typ);
 
-/* Open binary file for reading/writing.  If the file already exists,
+/* Open binary file for writing.  If the file already exists,
  * truncate the existing contents.  Create a new file if it doesn't
  * already exist. */
-#define osfoprwtb(fname,typ) (fopen((fname),"w+b"))
+inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
 
 /* Get a line of text from a text file. */
 #define osfgets fgets
@@ -208,7 +218,7 @@ osfoprwb( const char* fname, os_filetype_t typ );
 #define osfputs fputs
 
 /* Write bytes to file. */
-#define osfwb(fp,buf,bufl) (fwrite((buf),(bufl),1,(fp))!=1)
+inline bool osfwb(osfildef *fp, void *buf, size_t count);
 
 /* Flush buffered writes to a file. */
 #define osfflush fflush
diff --git a/engines/glk/tads/tads2/file_io.h b/engines/glk/tads/tads2/file_io.h
index 8599c49..0edfbae 100644
--- a/engines/glk/tads/tads2/file_io.h
+++ b/engines/glk/tads/tads2/file_io.h
@@ -28,6 +28,8 @@
 #define GLK_TADS_TADS2_FILE_IO
 
 #include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/memory_cache_loader.h"
+#include "glk/tads/tads2/object.h"
 
 namespace Glk {
 namespace TADS {
@@ -35,6 +37,9 @@ namespace TADS2 {
 
 /* forward declarations */
 struct voccxdef;
+struct tokpdef;
+struct tokthdef;
+struct tokcxdef;
 
 /* load-on-demand context (passed in by mcm in load callback) */
 typedef struct fiolcxdef fiolcxdef;
@@ -65,20 +70,16 @@ void fiowrt(struct mcmcxdef *mctx, voccxdef *vctx,
 #define FIOFLIN2  0x80                            /* new-style line records */
 
 /* read game from binary file; sets up loader callback context */
-void fiord(struct mcmcxdef *mctx, voccxdef *vctx,
-           struct tokcxdef *tctx,
-           char *fname, char *exename,
-           struct fiolcxdef *setupctx, objnum *preinit, uint *flagp,
-           struct tokpdef *path, uchar **fmtsp, uint *fmtlp,
-           uint *pcntptr, int flags, struct appctxdef *appctx,
-           char *argv0);
+void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname,
+	char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp,
+	tokpdef *path, uchar **fmtsp, uint *fmtlp, uint *pcntptr, int flags,
+	appctxdef *appctx, char *argv0);
 
 /* shut down load-on-demand subsystem, close load file */
 void fiorcls(fiolcxdef *ctx);
 
 /* loader callback - load an object on demand */
-void OS_LOADDS fioldobj(void *ctx, mclhd handle, uchar *ptr,
-                        ushort siz);
+void OS_LOADDS fioldobj(void *ctx, mclhd handle, uchar *ptr, ushort siz);
 
 /* 
  *   Save a game - returns TRUE on failure.  We'll save the file to
diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h
index 605f072..095abfc 100644
--- a/engines/glk/tads/tads2/lib.h
+++ b/engines/glk/tads/tads2/lib.h
@@ -134,7 +134,7 @@ void varused();
  *   anything outside of the normal ASCII set as spaces. 
  */
 #define t_isspace(c) \
-    (((unsigned char)(c)) <= 127 && isspace((unsigned char)(c)))
+    (((unsigned char)(c)) <= 127 && Common::isSpace((unsigned char)(c)))
 
 
 /* round a size to worst-case alignment boundary */
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
new file mode 100644
index 0000000..26536c0
--- /dev/null
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -0,0 +1,33 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/line_source_file.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/line_source_file.h b/engines/glk/tads/tads2/line_source_file.h
new file mode 100644
index 0000000..a8b90d8
--- /dev/null
+++ b/engines/glk/tads/tads2/line_source_file.h
@@ -0,0 +1,170 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_LINE_SOURCE_FILE
+#define GLK_TADS_TADS2_LINE_SOURCE_FILE
+
+#include "glk/tads/tads2/lib.h"
+#include "glk/tads/tads2/debug.h"
+#include "glk/tads/tads2/line_source.h"
+#include "glk/tads/tads2/object.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+struct tokpdef;
+
+/* maximum number of pages of debugging records we can keep */
+#define LINFPGMAX 128
+
+/* 
+ *   executable line information structure: this record relates one
+ *   executable line to the object containing the p-code, and the offset
+ *   in the object of the p-code for the start of the line 
+ */
+struct linfinfo {
+    /* 
+     *   OPCLINE data (file seek position or line number, depending on how
+     *   the game was compiled: -ds -> file seek offset, -ds2 -> line
+     *   number) 
+     */
+    ulong fpos;
+    
+    /* object number */
+    objnum objn;
+
+    /* offset from start of code */
+    uint ofs;
+};
+
+/* 
+ *   file line source 
+ */
+struct linfdef {
+    lindef    linflin;                                   /* superclass data */
+    osfildef *linffp;                  /* file pointer for this line source */
+    char      linfbuf[100];                 /* buffer for the line contents */
+    int       linfbufnxt;         /* offset in buffer of start of next line */
+    int       linfnxtlen;                /* length of data after linfbufnxt */
+    ulong     linfnum;                               /* current line number */
+    ulong     linfseek;                    /* seek position of current line */
+    mcmcxdef *linfmem;                            /* memory manager context */
+    mcmon     linfpg[LINFPGMAX];             /* pages for debugging records */
+    ulong     linfcrec;        /* number of debugger records written so far */
+    char      linfnam[1];                        /* name of file being read */
+};
+
+/* initialize a file line source, opening the file for the line source */
+linfdef *linfini(mcmcxdef *mctx, errcxdef *errctx, char *filename,
+                 int flen, tokpdef *path, int must_find_file,
+                 int new_line_records);
+
+/* initialize a pre-allocated linfdef, skipping debugger page setup */
+void linfini2(mcmcxdef *mctx, linfdef *linf,
+              char *filename, int flen, osfildef *fp, int new_line_records);
+
+/* get next line from line source */
+int linfget(lindef *lin);
+
+/* generate printable rep of current position in source (for errors) */
+void linfppos(lindef *lin, char *buf, uint bufl);
+
+/* close line source */
+void linfcls(lindef *lin);
+
+/* generate source-line debug instruction operand */
+void linfglop(lindef *lin, uchar *buf);
+
+/* generate new-style source-line debug instructino operand */
+void linfglop2(lindef *lin, uchar *buf);
+
+/* save line source to binary (.gam) file */
+int linfwrt(lindef *lin, osfildef *fp);
+
+/* load a file-line-source from binary (.gam) file */
+int linfload(osfildef *fp, dbgcxdef *dbgctx, errcxdef *ec,
+             tokpdef *path);
+
+/* add a debugger line record for the current line */
+void linfcmp(lindef *lin, uchar *buf);
+
+/* find nearest line record to a file seek location */
+void linffind(lindef *lin, char *buf, objnum *objp, uint *ofsp);
+
+/* activate line source for debugging */
+void linfact(lindef *lin);
+
+/* disactivate line source */
+void linfdis(lindef *lin);
+
+/* get current seek position */
+void linftell(lindef *lin, uchar *pos);
+
+/* seek */
+void linfseek(lindef *lin, uchar *pos);
+
+/* read */
+int linfread(lindef *lin, uchar *buf, uint siz);
+
+/* add a signed delta to a seek positon */
+void linfpadd(lindef *lin, uchar *pos, long delta);
+
+/* query whether at top of file */
+int linfqtop(lindef *lin, uchar *pos);
+
+/* read one line at current seek position */
+int linfgets(lindef *lin, uchar *buf, uint bufsiz);
+
+/* get name of line source */
+void linfnam(lindef *lin, char *buf);
+
+/* get the current line number */
+ulong linflnum(lindef *lin);
+
+/* go to top or bottom */
+void linfgoto(lindef *lin, int where);
+
+/* return the current offset in the line source */
+long linfofs(lindef *lin);
+
+/* renumber an object */
+void linfren(lindef *lin, objnum oldnum, objnum newnum);
+
+/* delete an object */
+void linfdelnum(lindef *lin, objnum objn);
+
+/* copy line records to an array of linfinfo structures */
+void linf_copy_linerecs(linfdef *linf, linfinfo *info);
+
+/* debugging echo */
+#ifdef DEBUG
+# define LINFDEBUG(x) x
+#else /* DEBUG */
+# define LINFDEBUG(x)
+#endif /* DEBUG */
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/tads2.cpp b/engines/glk/tads/tads2/tads2.cpp
index b391857..e1f5921 100644
--- a/engines/glk/tads/tads2/tads2.cpp
+++ b/engines/glk/tads/tads2/tads2.cpp
@@ -56,6 +56,7 @@ void TADS2::runGame() {
 }
 
 void TADS2::trdmain1(errcxdef *errctx) {
+#ifdef TODO
 	osfildef  *swapfp = (osfildef *)0;
 	runcxdef   runctx;
 	bifcxdef   bifctx;
@@ -99,7 +100,7 @@ void TADS2::trdmain1(errcxdef *errctx) {
 	char      *charmap = 0;                           /* character map file */
 	int        charmap_none;       /* explicitly do not use a character set */
 	int        doublespace = TRUE;        /* formatter double-space setting */
-#ifdef TODO
+
 	NOREG((&loadopen))
 
 		/* initialize the output formatter */
diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp
index 59dceeb..96bd2f1 100644
--- a/engines/glk/tads/tads2/tokenizer.cpp
+++ b/engines/glk/tads/tads2/tokenizer.cpp
@@ -21,12 +21,1536 @@
  */
 
 #include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/os.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-// TODO: Rest of tokenizer stuff
+
+/* special temporary buffers for <<expr>> macro expansion */
+static char tokmac1[]  = ",say((";
+static char tokmac1s[] = "(say((";
+static char tokmac2[]  = "),nil),\"";
+static char tokmac3[]  = "),nil))";
+static char tokmac4[]  = ")";
+
+/* forward definition of static functions */
+static int tokdfhsh(char *sym, int len);
+
+
+/* find a #define symbol */
+static tokdfdef *tok_find_define(tokcxdef *ctx, char *sym, int len)
+{
+    int       hsh;
+    tokdfdef *df;
+
+    /* find the appropriate chain the hash table */
+    hsh = tokdfhsh(sym, len);
+
+    /* search the chain for this symbol */
+    for (df = ctx->tokcxdf[hsh] ; df ; df = df->nxt)
+    {
+        /* if this one matches, return it */
+        if (df->len == len && !memcmp(df->nm, sym, (size_t)len))
+        {
+            /* fix it up if it's the special __FILE__ or __LINE__ symbol */
+            if (len == 8)
+            {
+                if (!memcmp(sym, "__FILE__", (size_t)8))
+                {
+                    size_t elen;
+
+                    /* 
+                     *   put in the opening single quote, since we want
+                     *   the expanded result to be a string 
+                     */
+                    df->expan[0] = '\'';
+
+                    /* get the name */
+                    linnam(ctx->tokcxlin, df->expan+1);
+
+                    /* get the length, and add the closing quote */
+                    elen = strlen(df->expan);
+                    df->expan[elen] = '\'';
+
+                    /* 
+                     *   set the length of the expansion, including the
+                     *   quotes (the first quote was measured in the
+                     *   length originally, but the second quote hasn't
+                     *   been counted yet, so add one to our original
+                     *   length) 
+                     */
+                    df->explen = (int)elen + 1;
+
+                    /* if the expansion is too long, it's an error */
+                    if (df->explen >= OSFNMAX)
+                        errsig(ctx->tokcxerr, ERR_LONG_FILE_MACRO);
+                }
+                else if (!memcmp(sym, "__LINE__", (size_t)8))
+                {
+                    ulong l;
+
+                    /* get the line number */
+                    l = linlnum(ctx->tokcxlin);
+
+                    /* convert it to a textual format for the expansion */
+                    sprintf(df->expan, "%lu", l);
+
+                    /* set the expanded value's length */
+                    df->explen = strlen(df->expan);
+
+                    /* make sure the expansion isn't too long */
+                    if (df->explen >= 40)
+                        errsig(ctx->tokcxerr, ERR_LONG_LINE_MACRO);
+                }
+            }
+            
+            /* return it */
+            return df;
+        }
+    }
+
+    /* didn't find anything */
+    return 0;
+}
+
+/*
+ *   Write preprocessor state to a file 
+ */
+void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
+{
+    int        i;
+    tokdfdef **dfp;
+    tokdfdef  *df;
+    char       buf[4];
+
+    /* write each element of the hash chains */
+    for (i = TOKDFHSHSIZ, dfp = ctx->tokcxdf ; i ; ++dfp, --i)
+    {
+        /* write each entry in this hash chain */
+        for (df = *dfp ; df ; df = df->nxt)
+        {
+            /* write this entry */
+            oswp2(buf, df->len);
+            oswp2(buf + 2, df->explen);
+            if (osfwb(fp, buf, 4)
+                || osfwb(fp, df->nm, df->len)
+                || (df->explen != 0 && osfwb(fp, df->expan, df->explen)))
+                errsig(ec, ERR_WRTGAM);
+        }
+
+        /* write a zero-length entry to indicate the end of this chain */
+        oswp2(buf, 0);
+        if (osfwb(fp, buf, 4)) errsig(ec, ERR_WRTGAM);
+    }
+}
+
+/*
+ *   Read preprocessor state from a file 
+ */
+void tok_read_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
+{
+    int        i;
+    tokdfdef **dfp;
+    tokdfdef  *df;
+    char       buf[4];
+
+    /* write each element of the hash chains */
+    for (i = TOKDFHSHSIZ, dfp = ctx->tokcxdf ; i ; ++dfp, --i)
+    {
+        /* read this hash chain */
+        for (;;)
+        {
+            /* read the next entry's header, and stop if this is the end */
+            if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM);
+            if (osrp2(buf) == 0) break;
+
+            /* set up a new symbol of the appropriate size */
+            df = (tokdfdef *)mchalo(ec,
+                                    (sizeof(tokdfdef) + osrp2(buf)
+                                     + osrp2(buf+2) - 1),
+                                    "tok_read_defines");
+            df->explen = osrp2(buf+2);
+            df->nm = df->expan + df->explen;
+            df->len = osrp2(buf);
+
+            /* read the rest of the symbol */
+            if (osfrb(fp, df->nm, df->len)
+                || (df->explen != 0 && osfrb(fp, df->expan, df->explen)))
+                errsig(ec, ERR_RDGAM);
+
+            /*
+             *   If a symbol with this name already exists in the table,
+             *   discard the new one -- the symbols defined by -D and the
+             *   current set of built-in symbols takes precedence over the
+             *   set loaded from the file.  
+             */
+            if (tok_find_define(ctx, df->nm, df->len))
+            {
+                /* simply discard this symbol */
+                mchfre(df);
+            }
+            else
+            {
+                /* link it into this hash chain */
+                df->nxt = *dfp;
+                *dfp = df;
+            }
+        }
+    }
+}
+
+
+
+/* compute a #define symbol's hash value */
+static int tokdfhsh(char *sym, int len)
+{
+    uint hsh;
+
+    for (hsh = 0 ; len ; ++sym, --len)
+        hsh = (hsh + *sym) & TOKDFHSHMASK;
+    return hsh;
+}
+
+/* convert a #define symbol to lower case if folding case */
+static char *tok_casefold_defsym(tokcxdef *ctx, char *outbuf,
+                                 char *src, int len)
+{
+    if (ctx->tokcxflg & TOKCXCASEFOLD)
+    {
+        char *dst;
+        int   rem;
+
+        /* make a lower-case copy of the symbol */
+        rem = (len > TOKNAMMAX ? TOKNAMMAX : len);
+        for (dst = outbuf ; rem > 0 ; ++dst, ++src, --rem)
+            *dst = (Common::isUpper((uchar)*src) ? Common::isLower((uchar)*src) : *src);
+
+        /* use the lower-case copy instead of the original */
+        return outbuf;
+    }
+    else
+    {
+        /* return the original unchanged */
+        return src;
+    }
+}
+
+/*
+ *   convert a token to lower-case if we're folding case 
+ */
+void tok_case_fold(tokcxdef *ctx, tokdef *tok)
+{
+    /* if we're in case-insensitive mode, convert the token to lower-case */
+    if (ctx->tokcxflg & TOKCXCASEFOLD)
+    {
+        char *p;
+        int   len;
+
+        /* convert each character in the token to lower-case */
+        for (p = tok->toknam, len = tok->toklen ; len != 0 ; ++p, --len)
+        {
+            /* if this character is upper-case, convert it to lower-case */
+            if (Common::isUpper((uchar)*p))
+                *p = Common::isLower((uchar)*p);
+        }
+    }
+}
+
+/* add a symbol to the #define symbol table, folding case if necessary */
+void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len,
+                            char *expan, int explen)
+{
+    char mysym[TOKNAMMAX];
+    
+    /* convert to lower-case if necessary */
+    sym = tok_casefold_defsym(ctx, mysym, sym, len);
+
+    /* add the symbol */
+    tok_add_define(ctx, sym, len, expan, explen);
+}
+
+/* add a symbol to the #define symbol table */
+void tok_add_define(tokcxdef *ctx, char *sym, int len,
+                    char *expan, int explen)
+{
+    int       hsh;
+    tokdfdef *df;
+
+    /* if it's already defined, ignore it */
+    if (tok_find_define(ctx, sym, len))
+        return;
+
+    /* find the appropriate entry in the hash table */
+    hsh = tokdfhsh(sym, len);
+
+    /* allocate space for the symbol */
+    df = (tokdfdef *)mchalo(ctx->tokcxerr,
+                            (sizeof(tokdfdef) + len + explen - 1),
+                            "tok_add_define");
+
+    /* set up the new symbol */
+    df->nm = df->expan + explen;
+    df->len = len;
+    df->explen = explen;
+    memcpy(df->expan, expan, explen);
+    memcpy(df->nm, sym, len);
+
+    /* link it into the hash chain */
+    df->nxt = ctx->tokcxdf[hsh];
+    ctx->tokcxdf[hsh] = df;
+}
+
+/* add a #define symbol with a numeric value */
+void tok_add_define_num_cvtcase(tokcxdef *ctx, char *sym, int len, int num)
+{
+    char buf[20];
+    
+    /* convert the value to a string */
+    sprintf(buf, "%d", num);
+
+    /* add the text value */
+    tok_add_define_cvtcase(ctx, sym, len, buf, strlen(buf));
+}
+
+/* undefine a #define symbol */
+void tok_del_define(tokcxdef *ctx, char *sym, int len)
+{
+    int       hsh;
+    tokdfdef *df;
+    tokdfdef *prv;
+    
+    /* find the appropriate chain the hash table */
+    hsh = tokdfhsh(sym, len);
+
+    /* search the chain for this symbol */
+    for (prv = 0, df = ctx->tokcxdf[hsh] ; df ; prv = df, df = df->nxt)
+    {
+        /* if this one matches, delete it */
+        if (df->len == len && !memcmp(df->nm, sym, (size_t)len))
+        {
+            /* unlink it from the chain */
+            if (prv)
+                prv->nxt = df->nxt;
+            else
+                ctx->tokcxdf[hsh] = df->nxt;
+
+            /* delete this symbol, and we're done */
+            mchfre(df);
+            break;
+        }
+    }
+}
+
+/* scan a #define symbol to see how long it is */
+static int tok_scan_defsym(tokcxdef *ctx, char *p, int len)
+{
+    int symlen;
+
+    /* make sure it's a valid symbol */
+    if (!(Common::isAlpha((uchar)*p) || *p == '_' || *p == '$'))
+    {
+        errlog(ctx->tokcxerr, ERR_REQSYM);
+        return 0;
+    }
+
+    /* count characters as long as we have valid symbol characters */
+    for (symlen = 0 ; len && TOKISSYM(*p) ; ++p, --len, ++symlen) ;
+    return symlen;
+}
+
+/* process a #define */
+static void tokdefine(tokcxdef *ctx, char *p, int len)
+{
+    char *sym;
+    int   symlen;
+    char *expan;
+    char  mysym[TOKNAMMAX];
+    
+    /* get the symbol */
+    sym = p;
+    if (!(symlen = tok_scan_defsym(ctx, p, len)))
+        return;
+
+    /* if it's already in the table, log an error */
+    if (tok_find_define(ctx, sym, symlen))
+    {
+        errlog(ctx->tokcxerr, ERR_DEFREDEF);
+        return;
+    }
+
+    /* skip whitespace following the symbol */
+    expan = sym + symlen;
+    len -= symlen;
+    while (len && t_isspace(*expan)) --len, ++expan;
+
+    /* if we're folding case, convert the symbol to lower case */
+    sym = tok_casefold_defsym(ctx, mysym, sym, symlen);
+
+    /* define the symbol */
+    tok_add_define(ctx, sym, symlen, expan, len);
+}
+
+/* 
+ *   Update the #if status for the current nesting.  Any enclosing
+ *   negative #if will override everything inside, so we need to look
+ *   through the nesting from the outside in until we either determine
+ *   that everything is affirmative or we find a negative anywhere in the
+ *   nesting.  
+ */
+static void tok_update_if_stat(tokcxdef *ctx)
+{
+    int i;
+
+    /* look through nesting from the outermost level */
+    for (i = 0 ; i < ctx->tokcxifcnt ; ++i)
+    {
+        /* assume this level will apply to everything inside */
+        ctx->tokcxifcur = ctx->tokcxif[i];
+
+        /* if this level is off, everything inside is off */
+        switch (ctx->tokcxif[i])
+        {
+        case TOKIF_IF_NO:
+        case TOKIF_ELSE_NO:
+            /* 
+             *   this level is off, hence everything inside is off -- stop
+             *   here with the current (negative) determination 
+             */
+            return;
+
+        default:
+            /* so far we're in the "on" section, so keep looking */
+            break;
+        }
+    }
+}
+
+/* process an #ifdef or a #ifndef */
+static void tok_ifdef_ifndef(tokcxdef *ctx, char *p, int len, int is_ifdef)
+{
+    int   symlen;
+    char *sym;
+    int   stat;
+    int   found;
+    char  mysym[TOKNAMMAX];
+
+    /* get the symbol */
+    sym = p;
+    if (!(symlen = tok_scan_defsym(ctx, p, len)))
+        return;
+
+    /* if we're folding case, convert the symbol to lower case */
+    sym = tok_casefold_defsym(ctx, mysym, sym, symlen);
+
+    /* see if we can find it in the table, and set the status accordingly */
+    found = (tok_find_define(ctx, sym, symlen) != 0);
+
+    /* invert the test if this is an ifndef */
+    if (!is_ifdef) found = !found;
+
+    /* set the #if status accordingly */
+    if (found)
+        stat = TOKIF_IF_YES;
+    else
+        stat = TOKIF_IF_NO;
+    ctx->tokcxif[ctx->tokcxifcnt] = stat;
+
+    /* allocate a new #if level (making sure we have room) */
+    if (ctx->tokcxifcnt >= TOKIFNEST)
+    {
+        errlog(ctx->tokcxerr, ERR_MANYPIF);
+        return;
+    }
+    ctx->tokcxifcnt++;
+
+    /* update the current status */
+    tok_update_if_stat(ctx);
+}
+
+/* process a #error */
+static void tok_p_error(tokcxdef *ctx, char *p, int len)
+{
+    errlog1(ctx->tokcxerr, ERR_P_ERROR,
+            ERRTSTR, errstr(ctx->tokcxerr, p, len));
+}
+
+/* process a #ifdef */
+static void tokifdef(tokcxdef *ctx, char *p, int len)
+{
+    tok_ifdef_ifndef(ctx, p, len, TRUE);
+}
+
+/* process a #ifndef */
+static void tokifndef(tokcxdef *ctx, char *p, int len)
+{
+    tok_ifdef_ifndef(ctx, p, len, FALSE);
+}
+
+/* process a #if */
+static void tokif(tokcxdef *ctx, char *p, int len)
+{
+    errsig(ctx->tokcxerr, ERR_PIF_NA);
+}
+
+/* process a #elif */
+static void tokelif(tokcxdef *ctx, char *p, int len)
+{
+    errsig(ctx->tokcxerr, ERR_PELIF_NA);
+}
+
+/* process a #else */
+static void tokelse(tokcxdef *ctx, char *p, int len)
+{
+    int cnt;
+    
+    /* if we're not expecting #else, it's an error */
+    cnt = ctx->tokcxifcnt;
+    if (cnt == 0 || ctx->tokcxif[cnt-1] == TOKIF_ELSE_YES
+        || ctx->tokcxif[cnt-1] == TOKIF_ELSE_NO)
+    {
+        errlog(ctx->tokcxerr, ERR_BADPELSE);
+        return;
+    }
+
+    /* switch to the appropriate #else state (opposite the #if state) */
+    if (ctx->tokcxif[cnt-1] == TOKIF_IF_YES)
+        ctx->tokcxif[cnt-1] = TOKIF_ELSE_NO;
+    else
+        ctx->tokcxif[cnt-1] = TOKIF_ELSE_YES;
+
+    /* update the current status */
+    tok_update_if_stat(ctx);
+}
+
+/* process a #endif */
+static void tokendif(tokcxdef *ctx, char *p, int len)
+{
+    /* if we're not expecting #endif, it's an error */
+    if (ctx->tokcxifcnt == 0)
+    {
+        errlog(ctx->tokcxerr, ERR_BADENDIF);
+        return;
+    }
+
+    /* remove the #if level */
+    ctx->tokcxifcnt--;
+
+    /* update the current status */
+    tok_update_if_stat(ctx);
+}
+
+/* process a #undef */
+static void tokundef(tokcxdef *ctx, char *p, int len)
+{
+    char *sym;
+    int   symlen;
+    char  mysym[TOKNAMMAX];
+    
+    /* get the symbol */
+    sym = p;
+    if (!(symlen = tok_scan_defsym(ctx, p, len)))
+        return;
+
+    /* if we're folding case, convert the symbol to lower case */
+    sym = tok_casefold_defsym(ctx, mysym, sym, symlen);
+
+    /* if it's not defined, log a warning */
+    if (!tok_find_define(ctx, sym, symlen))
+    {
+        errlog(ctx->tokcxerr, ERR_PUNDEF);
+        return;
+    }
+
+    /* undefine the symbol */
+    tok_del_define(ctx, sym, symlen);
+}
+
+/* process a #pragma directive */
+static void tokpragma(tokcxdef *ctx, char *p, int len)
+{
+    /* ignore empty pragmas */
+    if (len == 0)
+    {
+        errlog(ctx->tokcxerr, ERR_PRAGMA);
+        return;
+    }
+
+    /* see what we have */
+    if (len > 1
+        && (*p == 'c' || *p == 'C')
+        && (*(p+1) == '+' || *(p+1) == '-' || t_isspace(*(p+1))))
+    {
+        /* skip spaces after the 'C', if any */
+        for (++p, --len ; len && t_isspace(*p) ; ++p, --len) ;
+
+        /* look for the + or - flag */
+        if (len && *p == '+')
+            ctx->tokcxflg |= TOKCXFCMODE;
+        else if (len && *p == '-')
+            ctx->tokcxflg &= ~TOKCXFCMODE;
+        else
+        {
+            errlog(ctx->tokcxerr, ERR_PRAGMA);
+            return;
+        }
+    }
+    else
+    {
+        errlog(ctx->tokcxerr, ERR_PRAGMA);
+    }
+}
+
+/* process a #include directive */
+static void tokinclude(tokcxdef *ctx, char *p, int len)
+{
+    linfdef *child;
+    tokpdef *path;
+    char    *fname;
+    int      match;
+    int      flen;
+    linfdef *lin;
+    char    *q;
+    size_t   flen2;
+
+    /* find the filename portion */
+    fname = p + 1;                            /* remember start of filename */
+    path = ctx->tokcxinc;                    /* start with first path entry */
+
+    if (!len)
+    {
+        errlog(ctx->tokcxerr, ERR_INCNOFN);
+        return;
+    }
+    
+    switch(*p)
+    {
+    case '<':
+        match = '>';
+        if (path && path->tokpnxt) path = path->tokpnxt;   /* skip 1st path */
+        goto find_matching_delim;
+
+    case '"':
+        match = '"';
+
+    find_matching_delim:
+        for (++p, --len ; len && *p != match ; --len, ++p) ;
+        if (len == 0 || *p != match) errlog(ctx->tokcxerr, ERR_INCMTCH);
+        break;
+        
+    default:
+        errlog(ctx->tokcxerr, ERR_INCSYN);
+        return;
+    }
+    
+    flen = p - fname;                         /* compute length of filename */
+    for (q = p, flen2 = 0 ;
+         q > fname && *(q-1) != OSPATHCHAR && !strchr(OSPATHALT, *(q-1)) ;
+         --q, ++flen2) ;
+    
+    /* check to see if this file has already been included */
+    for (lin = ctx->tokcxhdr ; lin ; lin = (linfdef *)lin->linflin.linnxt)
+    {
+        char *p2 = lin->linfnam;
+        
+        p2 += strlen(p2);
+        
+        while (p2 > lin->linfnam && *(p2-1) != OSPATHCHAR
+               && !strchr(OSPATHALT, *(p2-1)))
+            --p2;
+        if (strlen(p2) == flen2
+            && !memicmp(p2, q, flen2))
+        {
+            errlog1(ctx->tokcxerr, ERR_INCRPT, ERRTSTR,
+                    errstr(ctx->tokcxerr, fname, flen));
+            return;
+        }
+    }
+    
+    /* initialize the line source */
+    child = linfini(ctx->tokcxmem, ctx->tokcxerr, fname, flen, path, TRUE,
+                    (ctx->tokcxflg & TOKCXFLIN2) != 0);
+    
+    /* if not found, signal an error */
+    if (!child) errsig1(ctx->tokcxerr, ERR_INCSEAR,
+                        ERRTSTR, errstr(ctx->tokcxerr, fname, flen));
+    
+    /* link into tokenizer list of line records */
+    child->linflin.linnxt = (lindef *)ctx->tokcxhdr;
+    ctx->tokcxhdr = child;
+
+    /* if we're tracking sources for debugging, add into the chain */
+    if (ctx->tokcxdbg)
+    {
+        ctx->tokcxdbg->dbgcxlin = &child->linflin;
+        child->linflin.linid = ctx->tokcxdbg->dbgcxfid++;
+    }
+
+    /* remember my C-mode setting */
+    if (ctx->tokcxflg & TOKCXFCMODE)
+        ctx->tokcxlin->linflg |= LINFCMODE;
+    else
+        ctx->tokcxlin->linflg &= ~LINFCMODE;
+    
+    child->linflin.linpar = ctx->tokcxlin;   /* remember parent line source */
+    ctx->tokcxlin = &child->linflin;   /* make the child the current source */
+}
+
+/* get a new line from line source, processing '#' directives */
+static int tokgetlin(tokcxdef *ctx, int dopound)
+{
+    for (;;)
+    {
+        if (linget(ctx->tokcxlin))
+        {
+            /* at eof in current source; resume parent if there is one */
+            if (ctx->tokcxlin->linpar)
+            {
+                lindef *parent;
+                
+                parent = ctx->tokcxlin->linpar;          /* remember parent */
+                lincls(ctx->tokcxlin);               /* close included file */
+                if (!ctx->tokcxdbg)               /* if no debug context... */
+                    mchfre(ctx->tokcxlin);              /* free line source */
+                ctx->tokcxlin = parent;      /* reset to parent line source */
+                if (parent->linflg & LINFCMODE)
+                    ctx->tokcxflg |= TOKCXFCMODE;
+                else
+                    ctx->tokcxflg &= ~TOKCXFCMODE;
+                continue;                       /* back for another attempt */
+            }
+            else
+            {
+                /* check for outstanding #if/#ifdef */
+                if (ctx->tokcxifcnt)
+                    errlog(ctx->tokcxerr, ERR_NOENDIF);
+
+                /* return end-of-file indication */
+                return TRUE;
+            }
+        }
+        
+        /* if this is a multi-segment line, copy it into our own buffer */
+        if (ctx->tokcxlin->linflg & LINFMORE)
+        {
+            char *p;
+            uint  rem;
+            int   done;
+            
+            if (!ctx->tokcxbuf)
+            {
+                /* allocate 1k as a default buffer */
+                ctx->tokcxbuf = (char *)mchalo(ctx->tokcxerr, 1024,
+                                               "tok");
+                ctx->tokcxbsz = 1024;
+            }
+            ctx->tokcxlen = 0;
+            
+            for (done = FALSE, p = ctx->tokcxbuf, rem = ctx->tokcxbsz ;
+                 !done ; )
+            {
+                size_t len = ctx->tokcxlin->linlen;
+
+                /* add the current segment's length into line length */
+                ctx->tokcxlen += len;
+                
+                /* we're done after this piece if the last fetch was all */
+                done = !(ctx->tokcxlin->linflg & LINFMORE);
+                if (len + 1 > rem)
+                {
+                    char *newp;
+
+                    /* increase the size of the buffer */
+                    if (ctx->tokcxbsz > (unsigned)0x8000)
+                        errsig(ctx->tokcxerr, ERR_LONGLIN);
+                    rem += 4096;
+                    ctx->tokcxbsz += 4096;
+                    
+                    /* allocate a new buffer and copy line into it */
+                    newp = (char *)mchalo(ctx->tokcxerr, ctx->tokcxbsz, "tok");
+                    memcpy(newp, ctx->tokcxbuf, (size_t)(p - ctx->tokcxbuf));
+                    
+                    /* free the original buffer, and use the new one */
+                    p = (p - ctx->tokcxbuf) + newp;
+                    mchfre(ctx->tokcxbuf);
+                    ctx->tokcxbuf = newp;
+                }
+                
+                /* add the line to the buffer */
+                memcpy(p, ctx->tokcxlin->linbuf, len);
+                p += len;
+                rem -= len;
+                
+                /* get the next piece of the line if there is one */
+                if (!done)
+                {
+                    if (linget(ctx->tokcxlin)) break;
+                }
+            }
+            
+            /* null-terminate the buffer, and use it for input */
+            *p = '\0';
+            ctx->tokcxptr = ctx->tokcxbuf;
+        }
+        else
+        {
+            ctx->tokcxptr = ctx->tokcxlin->linbuf;
+            ctx->tokcxlen = ctx->tokcxlin->linlen;
+        }
+        
+        /* check for preprocessor directives */
+        if (dopound && ctx->tokcxlen != 0 && ctx->tokcxptr[0] == '#'
+            && !(ctx->tokcxlin->linflg & LINFNOINC))
+        {
+            char   *p;
+            int     len;
+            static  struct
+            {
+                char  *nm;
+                int    len;
+                int    ok_in_if;
+                void (*fn)(tokcxdef *, char *, int);
+            }
+            *dirp, dir[] =
+            {
+                { "include", 7, FALSE, tokinclude },
+                { "pragma",  6, FALSE, tokpragma },
+                { "define",  6, FALSE, tokdefine },
+                { "ifdef",   5, TRUE, tokifdef },
+                { "ifndef",  6, TRUE, tokifndef },
+                { "if",      2, TRUE, tokif },
+                { "else",    4, TRUE, tokelse },
+                { "elif",    4, TRUE, tokelif },
+                { "endif",   5, TRUE, tokendif },
+                { "undef",   5, FALSE, tokundef },
+                { "error",   5, FALSE, tok_p_error }
+            };
+            int  i;
+
+            /* scan off spaces between '#' and directive */
+            for (len = ctx->tokcxlen - 1, p = &ctx->tokcxptr[1] ;
+                 len && t_isspace(*p) ; --len, ++p) ;
+
+            /* find and process the directive */
+            for (dirp = dir, i = sizeof(dir)/sizeof(dir[0]) ; i ; --i, ++dirp)
+            {
+                /* compare this directive; if it wins, call its function */
+                if (len >= dirp->len && !memcmp(p, dirp->nm, (size_t)dirp->len)
+                    && (len == dirp->len || t_isspace(*(p + dirp->len))))
+                {
+                    int cnt;
+                    int stat;
+                    
+                    /*
+                     *   if we're not in a #if's false part, or if the
+                     *   directive is processed even in #if false parts,
+                     *   process the line, otherwise skip it 
+                     */
+                    cnt = ctx->tokcxifcnt;
+                    if (dirp->ok_in_if || cnt == 0
+                        || ((stat = ctx->tokcxifcur) == TOKIF_IF_YES
+                            || stat == TOKIF_ELSE_YES))
+                    {
+                        /* skip whitespace following the directive */
+                        for (p += dirp->len, len -= dirp->len ;
+                             len && t_isspace(*p) ;
+                             --len, ++p) ;
+
+                        /* invoke the function to process this directive */
+                        (*dirp->fn)(ctx, p, len);
+                    }
+
+                    /* there's no need to look at more directives */
+                    break;
+                }
+            }
+
+            /* if we didn't find anything, flag the error */
+            if (i == 0)
+                errlog(ctx->tokcxerr, ERR_PRPDIR);
+
+            /* ignore this line */
+            continue;
+        }
+        else
+        {
+            /*
+             *   Check the #if level.  If we're in an #if, and we're to
+             *   ignore lines (because of a false condition or an #else
+             *   part for a true condition), skip this line. 
+             */
+            if (ctx->tokcxifcnt != 0)
+            {
+                switch(ctx->tokcxifcur)
+                {
+                case TOKIF_IF_NO:
+                case TOKIF_ELSE_NO:
+                    /* ignore this line */
+                    continue;
+
+                default:
+                    /* we're in a true part - keep the line */
+                    break;
+                }
+            }
+            
+            ctx->tokcxlin->linflg &= ~LINFDBG;       /* no debug record yet */
+            return(FALSE);                      /* return the line we found */
+        }
+    }
+}
+
+/* get the next token, removing it from the input stream */
+int toknext(tokcxdef *ctx)
+{
+    char   *p;
+    tokdef *tok = &ctx->tokcxcur;
+    int     len;
+
+    /* 
+     *   Check for the special case that we pushed an open paren prior to
+     *   a string containing an embedded expression.  If this is the case,
+     *   immediately return the string we previously parsed. 
+     */
+    if ((ctx->tokcxflg & TOKCXF_EMBED_PAREN_PRE) != 0)
+    {
+        /* 
+         *   convert the token to a string - note that the offset
+         *   information for the string is already in the current token
+         *   structure, since we set everything up for it on the previous
+         *   call where we actually parsed the beginning of the string 
+         */
+        tok->toktyp = TOKTDSTRING;
+
+        /* clear the special flag - we've now consumed the pushed string */
+        ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_PRE;
+
+        /* immediately return the string */
+        return tok->toktyp;
+    }
+
+    /* set up at the current scanning position */
+    p = ctx->tokcxptr;
+    len = ctx->tokcxlen;
+
+    /* scan off whitespace and comments until we find something */
+    do
+    {
+    skipblanks:
+        /* if there's nothing on this line, get the next one */
+        if (len == 0)
+        {
+            /* if we're in a macro expansion, continue after it */
+            if (ctx->tokcxmlvl)
+            {
+                ctx->tokcxmlvl--;
+                p = ctx->tokcxmsav[ctx->tokcxmlvl];
+                len = ctx->tokcxmsvl[ctx->tokcxmlvl];
+            }
+            else
+            {
+                if (tokgetlin(ctx, TRUE))
+                {
+                    tok->toktyp = TOKTEOF;
+                    goto done;
+                }
+                p = ctx->tokcxptr;
+                len = ctx->tokcxlen;
+            }
+        }
+        while (len && t_isspace(*p)) ++p, --len;     /* scan off whitespace */
+        
+        /* check for comments, and remove if present */
+        if (len >= 2 && *p == '/' && *(p+1) == '/')
+            len = 0;
+        else if (len >= 2 && *p == '/' && *(p+1) == '*')
+        {
+            while (len < 2 || *p != '*' || *(p+1) != '/')
+            {
+                if (len != 0)
+                    ++p, --len;
+
+                if (len == 0)
+                {
+                    if (ctx->tokcxmlvl != 0)
+                    {
+                        ctx->tokcxmlvl--;
+                        p = ctx->tokcxmsav[ctx->tokcxmlvl];
+                        len = ctx->tokcxmsvl[ctx->tokcxmlvl];
+                    }
+                    else
+                    {
+                        if (tokgetlin(ctx, FALSE))
+                        {
+                            ctx->tokcxptr = p;
+                            tok->toktyp = TOKTEOF;
+                            goto done;
+                        }
+                        p = ctx->tokcxptr;
+                        len = ctx->tokcxlen;
+                    }
+                }
+            }
+            p += 2;
+            len -= 2;
+            goto skipblanks;
+        }
+    } while (len == 0);
+    
+nexttoken:
+    if (Common::isAlpha((uchar)*p) || *p == '_' || *p == '$')
+    {
+        int       l;
+        int       hash;
+        char     *q;
+        toktdef  *tab;
+        int       found = FALSE;
+        uchar     thischar;
+        tokdfdef *df;
+        
+        for (hash = 0, l = 0, q = tok->toknam ;
+             len != 0 && TOKISSYM(*p) && l < TOKNAMMAX ;
+             (thischar = ((Common::isUpper((uchar)*p)
+                           && (ctx->tokcxflg & TOKCXCASEFOLD))
+                          ? Common::isLower((uchar)*p) : *p)),
+             (hash = ((hash + thischar) & (TOKHASHSIZE - 1))),
+             (*q++ = thischar), ++p, --len, ++l) ;
+        *q = '\0';
+        if (len != 0 && TOKISSYM(*p))
+        {
+            while (len != 0 && TOKISSYM(*p)) ++p, --len;
+            errlog1(ctx->tokcxerr, ERR_TRUNC, ERRTSTR,
+                    errstr(ctx->tokcxerr, tok->toknam, tok->toklen));
+        }
+        tok->toklen = l;
+        tok->tokhash = hash;
+
+        /*
+         *   check for the special defined() preprocessor operator 
+         */
+        if (l == 9 && !memcmp(tok->toknam,
+                              ((ctx->tokcxflg & TOKCXCASEFOLD)
+                               ? "__defined" : "__DEFINED"),
+                              (size_t)9)
+            && len > 2 && *p == '(' && TOKISSYM(*(p+1))
+            && !Common::isDigit((uchar)*(p+1)))
+        {
+            int symlen;
+            char mysym[TOKNAMMAX];
+            
+            /* find the matching ')', allowing only symbolic characters */
+            ++p, --len;
+            for (symlen = 0, q = p ; len && *p != ')' && TOKISSYM(*p) ;
+                 ++p, --len, ++symlen) ;
+
+            /* make sure we found the closing paren */
+            if (!len || *p != ')')
+                errsig(ctx->tokcxerr, ERR_BADISDEF);
+            ++p, --len;
+
+            /* if we're folding case, convert the symbol to lower case */
+            q = tok_casefold_defsym(ctx, mysym, q, symlen);
+
+            /* check to see if it's defined */
+            tok->toktyp = TOKTNUMBER;
+            tok->tokval = (tok_find_define(ctx, q, symlen) != 0);
+            goto done;
+        }
+
+        /* substitute the preprocessor #define, if any */
+        if ((df = tok_find_define(ctx, tok->toknam, l)) != 0)
+        {
+            /* save the current parsing position */
+            if (ctx->tokcxmlvl >= TOKMACNEST)
+                errsig(ctx->tokcxerr, ERR_MACNEST);
+            ctx->tokcxmsav[ctx->tokcxmlvl] = p;
+            ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
+            ctx->tokcxmlvl++;
+
+            /* point to the token's expansion and keep going */
+            p = df->expan;
+            len = df->explen;
+            goto nexttoken;
+        }
+        
+        /* look up in symbol table(s), if any */
+        for (tab = ctx->tokcxstab ; tab ; tab = tab->toktnxt)
+        {
+            if ((found = (*tab->toktfsea)(tab, tok->toknam, l, hash,
+                                          &tok->toksym)) != 0)
+                break;
+        }
+        
+        if (found && tok->toksym.tokstyp == TOKSTKW)
+            tok->toktyp = tok->toksym.toksval;
+        else
+        {
+            tok->toktyp = TOKTSYMBOL;
+            if (!found) tok->toksym.tokstyp = TOKSTUNK;
+        }
+        goto done;
+    }
+    else if (Common::isDigit((uchar)*p))
+    {
+        long acc = 0;
+        
+        /* check for octal/hex */
+        if (*p == '0')
+        {
+            ++p, --len;
+            if (len && (*p == 'x' || *p == 'X'))
+            {
+                /* hex */
+                ++p, --len;
+                while (len && TOKISHEX(*p))
+                {
+                    acc = (acc << 4) + TOKHEX2INT(*p);
+                    ++p, --len;
+                }
+            }
+            else
+            {
+                /* octal */
+                while (len && TOKISOCT(*p))
+                {
+                    acc = (acc << 3) + TOKOCT2INT(*p);
+                    ++p, --len;
+                }
+            }
+        }
+        else
+        {
+            /* decimal */
+            while (len && Common::isDigit((uchar)*p))
+            {
+                acc = (acc << 1) + (acc << 3) + TOKDEC2INT(*p);
+                ++p, --len;
+            }
+        }
+        tok->tokval = acc;
+        tok->toktyp = TOKTNUMBER;
+        goto done;
+    }
+    else if (*p == '"' || *p == '\'')
+    {
+        char  delim;                 /* closing delimiter we're looking for */
+        char *strstart;                       /* pointer to start of string */
+        int   warned;
+        
+        delim = *p;
+        --len;
+        strstart = ++p;
+
+        if (delim == '"' && len >= 2 && *p == '<' && *(p+1) == '<')
+        {
+            /* save the current parsing position */
+            if (ctx->tokcxmlvl >= TOKMACNEST)
+                errsig(ctx->tokcxerr, ERR_MACNEST);
+            ctx->tokcxmsav[ctx->tokcxmlvl] = p + 2;
+            ctx->tokcxmsvl[ctx->tokcxmlvl] = len - 2;
+            ctx->tokcxmlvl++;
+
+            /* 
+             *   read from the special "<<" expansion string - use the
+             *   version for a "<<" at the very beginning of the string 
+             */
+            p = tokmac1s;
+            len = strlen(p);
+            ctx->tokcxflg |= TOKCXFINMAC;
+            goto nexttoken;
+        }
+        tok->toktyp = (delim == '"' ? TOKTDSTRING : TOKTSSTRING);
+        
+        tok->tokofs = (*ctx->tokcxsst)(ctx->tokcxscx);  /* start the string */
+        for (warned = FALSE ;; )
+        {
+            if (len >= 2 && *p == '\\')
+            {
+                if (*(p+1) == '"' || *(p+1) == '\'')
+                {
+                    (*ctx->tokcxsad)(ctx->tokcxscx, strstart,
+                                     (ushort)(p - strstart));
+                    strstart = p + 1;
+                }
+                p += 2;
+                len -= 2;
+            }
+            else if (len == 0 || *p == delim ||
+                     (delim == '"' && len >= 2 && *p == '<' && *(p+1) == '<'
+                      && !(ctx->tokcxflg & TOKCXFINMAC)))
+            {
+                (*ctx->tokcxsad)(ctx->tokcxscx, strstart,
+                                 (ushort)(p - strstart));
+                if (len == 0)
+                {
+                    if (ctx->tokcxmlvl)
+                    {
+                        ctx->tokcxmlvl--;
+                        p = ctx->tokcxmsav[ctx->tokcxmlvl];
+                        len = ctx->tokcxmsvl[ctx->tokcxmlvl];
+                    }
+                    else
+                        (*ctx->tokcxsad)(ctx->tokcxscx, " ", (ushort)1);
+                    
+                    while (len == 0)
+                    {
+                        if (tokgetlin(ctx, FALSE))
+                            errsig(ctx->tokcxerr, ERR_STREOF);
+                        p = ctx->tokcxptr;
+                        len = ctx->tokcxlen;
+
+                        /* warn if it looks like the end of an object */
+                        if (!warned && len && (*p == ';' || *p == '}'))
+                        {
+                            errlog(ctx->tokcxerr, ERR_STREND);
+                            warned = TRUE;     /* warn only once per string */
+                        }
+
+                        /* scan past whitespace at start of line */
+                        while (len && t_isspace(*p)) ++p, --len;
+                    }
+                    strstart = p;
+                }
+                else break;
+            }
+            else
+                ++p, --len;
+        }
+
+        /* end the string */
+        (*ctx->tokcxsend)(ctx->tokcxscx);
+
+        /* check to see how it ended */
+        if (len != 0 && *p == delim)
+        {
+            /* 
+             *   We ended with the matching delimiter.  Move past the
+             *   closing delimiter. 
+             */
+            ++p;
+            --len;
+
+            /*
+             *   If we have a pending close paren we need to put in
+             *   because of an embedded expression that occurred earlier
+             *   in the string, parse the macro to provide the paren.  
+             */
+            if ((ctx->tokcxflg & TOKCXF_EMBED_PAREN_AFT) != 0
+                && !(ctx->tokcxflg & TOKCXFINMAC))
+            {
+                /* clear the flag */
+                ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_AFT;
+
+                /* push the current parsing position */
+                if (ctx->tokcxmlvl >= TOKMACNEST)
+                    errsig(ctx->tokcxerr, ERR_MACNEST);
+                ctx->tokcxmsav[ctx->tokcxmlvl] = p;
+                ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
+                ctx->tokcxmlvl++;
+
+                /* parse the macro */
+                p = tokmac4;
+                len = strlen(p);
+            }
+        }
+        else if (len != 0 && *p == '<')
+        {
+            /* save the current parsing position */
+            if (ctx->tokcxmlvl >= TOKMACNEST)
+                errsig(ctx->tokcxerr, ERR_MACNEST);
+            ctx->tokcxmsav[ctx->tokcxmlvl] = p + 2;
+            ctx->tokcxmsvl[ctx->tokcxmlvl] = len - 2;
+            ctx->tokcxmlvl++;
+
+            /* read from the "<<" expansion */
+            p = tokmac1;
+            len = strlen(p);
+            ctx->tokcxflg |= TOKCXFINMAC;
+
+            /* 
+             *   Set the special push-a-paren flag: we'll return an open
+             *   paren now, so that we have an open paren before the
+             *   string, and then on the next call to toknext() we'll
+             *   immediately return the string we've already parsed here.
+             *   This will ensure that everything in the string is
+             *   properly grouped together as a single indivisible
+             *   expression.
+             *   
+             *   Note that we only need to do this for the first embedded
+             *   expression in a string.  Once we have a close paren
+             *   pending, we don't need more open parens.  
+             */
+            if (!(ctx->tokcxflg & TOKCXF_EMBED_PAREN_AFT))
+            {
+                ctx->tokcxflg |= TOKCXF_EMBED_PAREN_PRE;
+                tok->toktyp = TOKTLPAR;
+            }
+        }
+        goto done;
+    }
+    else if (len >= 2 && *p == '>' && *(p+1) == '>'
+             && (ctx->tokcxflg & TOKCXFINMAC) != 0)
+    {
+        /* skip the ">>" */
+        ctx->tokcxflg &= ~TOKCXFINMAC;
+        p += 2;
+        len -= 2;
+
+        /* save the current parsing position */
+        if (ctx->tokcxmlvl >= TOKMACNEST)
+            errsig(ctx->tokcxerr, ERR_MACNEST);
+        ctx->tokcxmsav[ctx->tokcxmlvl] = p;
+        ctx->tokcxmsvl[ctx->tokcxmlvl] = len;
+        ctx->tokcxmlvl++;
+
+        if (*p == '"')
+        {
+            ++(ctx->tokcxmsav[ctx->tokcxmlvl - 1]);
+            --(ctx->tokcxmsvl[ctx->tokcxmlvl - 1]);
+            p = tokmac3;
+
+            /* 
+             *   we won't need an extra closing paren now, since tokmac3
+             *   provides it 
+             */
+            ctx->tokcxflg &= ~TOKCXF_EMBED_PAREN_AFT;
+        }
+        else
+        {
+            /* 
+             *   The string is continuing.  Set a flag to note that we
+             *   need to provide a close paren after the end of the
+             *   string, and parse the glue (tokmac2) that goes between
+             *   the expression and the resumption of the string. 
+             */
+            ctx->tokcxflg |= TOKCXF_EMBED_PAREN_AFT;
+            p = tokmac2;
+        }
+
+        len = strlen(p);
+        goto nexttoken;
+    }
+    else
+    {
+        tokscdef *sc;
+        
+        for (sc = ctx->tokcxsc[ctx->tokcxinx[(uchar)*p]] ; sc ;
+             sc = sc->tokscnxt)
+        {
+            if (toksceq(sc->tokscstr, p, sc->toksclen, len))
+            {
+                tok->toktyp = sc->toksctyp;
+                p += sc->toksclen;
+                len -= sc->toksclen;
+                goto done;
+            }
+        }
+        errsig(ctx->tokcxerr, ERR_INVTOK);
+    }
+    
+done:
+    ctx->tokcxptr = p;
+    ctx->tokcxlen = len;
+    return(tok->toktyp);
+}
+
+/* initialize a linear symbol table */
+void toktlini(errcxdef *errctx, toktldef *toktab, uchar *mem, uint siz)
+{
+    CLRSTRUCT(*toktab);
+    
+    /* initialize superclass data */
+    toktab->toktlsc.toktfadd = toktladd;           /* set add-symbol method */
+    toktab->toktlsc.toktfsea = toktlsea;         /* set search-table method */
+    toktab->toktlsc.toktfeach = toktleach;             /* set 'each' method */
+    toktab->toktlsc.toktfset = toktlset;             /* set 'update' method */
+    toktab->toktlsc.tokterr = errctx;         /* set error handling context */
+    
+    /* initialize class data */
+    toktab->toktlptr = mem;
+    toktab->toktlnxt = mem;
+    toktab->toktlsiz = siz;
+}
+
+/* add a symbol to a linear symbol table */
+void toktladd(toktdef *toktab1, char *name, int namel,
+              int typ, int val, int hash)
+{
+    uint      siz = sizeof(toks1def) + namel;
+    toksdef  *sym;
+    toktldef *toktab = (toktldef *)toktab1;
+    
+    VARUSED(hash);
+    
+    if (toktab->toktlsiz < siz)
+        errsig(toktab->toktlsc.tokterr, ERR_NOLCLSY);
+    
+    sym = (toksdef *)toktab->toktlnxt;
+    siz = osrndsz(siz);
+    toktab->toktlnxt += siz;
+    if (siz > toktab->toktlsiz) toktab->toktlsiz = 0;
+    else toktab->toktlsiz -= siz;
+
+    /* set up symbol */
+    sym->toksval = val;
+    sym->tokslen = namel;
+    sym->tokstyp = typ;
+    sym->toksfr  = 0;
+    memcpy(sym->toksnam, name, (size_t)(namel + 1));
+    
+    /* indicate there's one more symbol in the table */
+    ++(toktab->toktlcnt);
+}
+
+/* delete all symbols from a linear symbol table */
+void toktldel(toktldef *tab)
+{
+    tab->toktlcnt = 0;
+    tab->toktlsiz += tab->toktlnxt - tab->toktlptr;
+    tab->toktlnxt = tab->toktlptr;
+}
+
+/* call a function for every symbol in a linear symbol table */
+void toktleach(toktdef *tab1,
+               void (*cb)(void *ctx, toksdef *sym), void *ctx)
+{
+    toksdef  *p;
+    uint      cnt;
+    toktldef *tab = (toktldef *)tab1;
+    
+    for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
+    {
+        (*cb)(ctx, p);
+        p = (toksdef *)(((uchar *)p)
+                        + osrndsz(p->tokslen + sizeof(toks1def)));
+    }
+}
+
+/* search a linear symbol table */
+int toktlsea(toktdef *tab1, char *name, int namel, int hash, toksdef *ret)
+{
+    toksdef  *p;
+    uint      cnt;
+    toktldef *tab = (toktldef *)tab1;
+    
+    VARUSED(hash);
+    
+    for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
+    {
+        if (p->tokslen == namel && !memcmp(p->toksnam, name, (size_t)namel))
+        {
+            memcpy(ret, p, (size_t)(sizeof(toks1def) + namel));
+            return(TRUE);
+        }
+        
+        p = (toksdef *)(((uchar *)p)
+                        + osrndsz(p->tokslen + sizeof(toks1def)));
+    }
+
+    /* nothing found - indicate by returning FALSE */
+    return(FALSE);
+}
+
+/* update a symbol in a linear symbol table */
+void toktlset(toktdef *tab1, toksdef *newsym)
+{
+    toksdef  *p;
+    uint      cnt;
+    toktldef *tab = (toktldef *)tab1;
+    
+    for (p = (toksdef *)tab->toktlptr, cnt = tab->toktlcnt ; cnt ; --cnt )
+    {
+        if (p->tokslen == newsym->tokslen
+            && !memcmp(p->toksnam, newsym->toksnam, (size_t)newsym->tokslen))
+        {
+            p->toksval = newsym->toksval;
+            p->tokstyp = newsym->tokstyp;
+            return;
+        }
+        
+        p = (toksdef *)(((uchar *)p)
+                        + osrndsz(p->tokslen + sizeof(toks1def)));
+    }
+}
+
+tokcxdef *tokcxini(errcxdef *errctx, mcmcxdef *mcmctx, tokldef *sctab)
+{
+    int       i;
+    int       cnt;
+    tokldef  *p;
+    uchar     c;
+    uchar     index[256];
+    tokcxdef *ret;
+    tokscdef *sc;
+    ushort    siz;
+    
+    /* set up index table: finds tokcxsc entry from character value */
+    memset(index, 0, (size_t)sizeof(index));
+    for (i = cnt = 0, p = sctab ; (c = p->toklstr[0]) != 0 ; ++cnt, ++p)
+        if (!index[c]) index[c] = ++i;
+    
+    /* allocate memory for table plus the tokscdef's */
+    siz = sizeof(tokcxdef) + (i * sizeof(tokscdef *))
+          + ((cnt + 1) * sizeof(tokscdef));
+    ret = (tokcxdef *)mchalo(errctx, siz, "tokcxini");
+    memset(ret, 0, (size_t)siz);
+    
+    /* copy the index, set up fixed part */
+    memcpy(ret->tokcxinx, index, sizeof(ret->tokcxinx));
+    ret->tokcxerr = errctx;
+    ret->tokcxmem = mcmctx;
+
+    /* start out without an #if */
+    ret->tokcxifcur = TOKIF_IF_YES;
+    
+    /* force the first toknext() to read a line */
+    ret->tokcxptr = "\000";
+    
+    /* figure where the tokscdef's go (right after sc pointer array) */
+    sc = (tokscdef *)&ret->tokcxsc[i+1];
+    
+    /* set up the individual tokscdef entries, and link into lists */
+    for (p = sctab ; (c = p->toklstr[0]) != 0 ; ++p, ++sc)
+    {
+        size_t len;
+        
+        sc->toksctyp = p->tokltyp;
+        len = sc->toksclen = strlen(p->toklstr);
+        memcpy(sc->tokscstr, p->toklstr, len);
+        sc->tokscnxt = ret->tokcxsc[index[c]];
+        ret->tokcxsc[index[c]] = sc;
+    }
+    
+    return(ret);
+}
+
+/* add an include path to a tokdef */
+void tokaddinc(tokcxdef *ctx, char *path, int pathlen)
+{
+    tokpdef *newpath;
+    tokpdef *last;
+    
+    /* find the tail of the include path list, if any */
+    for (last = ctx->tokcxinc ; last && last->tokpnxt ;
+         last = last->tokpnxt) ;
+    
+    /* allocate storage for and set up a new path structure */
+    newpath = (tokpdef *)mchalo(ctx->tokcxerr,
+                                (sizeof(tokpdef) + pathlen - 1),
+                                "tokaddinc");
+    newpath->tokplen = pathlen;
+    newpath->tokpnxt = (tokpdef *)0;
+    memcpy(newpath->tokpdir, path, (size_t)pathlen);
+    
+    /* link in at end of list (if no list yet, newpath becomes first entry) */
+    if (last)
+        last->tokpnxt = newpath;
+    else
+        ctx->tokcxinc = newpath;
+}
 
 } // End of namespace TADS2
 } // End of namespace TADS
diff --git a/engines/glk/tads/tads2/tokenizer.h b/engines/glk/tads/tads2/tokenizer.h
index 50a8492..11d76c6 100644
--- a/engines/glk/tads/tads2/tokenizer.h
+++ b/engines/glk/tads/tads2/tokenizer.h
@@ -26,6 +26,7 @@
 #include "glk/tads/tads2/lib.h"
 #include "glk/tads/tads2/error_handling.h"
 #include "glk/tads/tads2/line_source.h"
+#include "glk/tads/tads2/line_source_file.h"
 #include "glk/tads/tads2/memory_cache.h"
 
 namespace Glk {
@@ -356,7 +357,7 @@ struct tokcxdef {
     int       tokcxifcnt;           /* number of #endif's we expect to find */
     char      tokcxif[TOKIFNEST];       /* #if state for each nesting level */
     int       tokcxifcur;             /* current #if state, obeying nesting */
-    struct    linfdef  *tokcxhdr;    /* list of previously included headers */
+    linfdef  *tokcxhdr;              /* list of previously included headers */
     tokscdef *tokcxsc[1];                        /* special character table */
 };
 
@@ -453,16 +454,16 @@ void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec);
 
 /* determine if a char is a valid non-initial character in a symbol name */
 #define TOKISSYM(c) \
-    (isalpha((uchar)(c)) || isdigit((uchar)(c)) || (c)=='_' || (c)=='$')
+    (Common::isAlpha((uchar)(c)) || Common::isDigit((uchar)(c)) || (c)=='_' || (c)=='$')
 
 /* numeric conversion and checking macros */
 #define TOKISHEX(c) \
-    (isdigit((uchar)(c))||((c)>='a'&&(c)<='f')||((c)>='A'&&(c)<='F'))
+    (Common::isDigit((uchar)(c))||((c)>='a'&&(c)<='f')||((c)>='A'&&(c)<='F'))
 #define TOKISOCT(c) \
-    (isdigit((uchar)(c))&&!((c)=='8'||(c)=='9'))
+    (Common::isDigit((uchar)(c))&&!((c)=='8'||(c)=='9'))
 
 #define TOKHEX2INT(c) \
-    (isdigit((uchar)c)?(c)-'0':((c)>='a'?(c)-'a'+10:(c)-'A'+10))
+    (Common::isDigit((uchar)c)?(c)-'0':((c)>='a'?(c)-'a'+10:(c)-'A'+10))
 #define TOKOCT2INT(c) ((c)-'0')
 #define TOKDEC2INT(c) ((c)-'0')
 
diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp
index e20bbe3..516e9df 100644
--- a/engines/glk/tads/tads2/vocabulary.cpp
+++ b/engines/glk/tads/tads2/vocabulary.cpp
@@ -22,12 +22,912 @@
 
 #include "glk/tads/tads2/run.h"
 #include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-// TODO: Rest of vocabulary stuff
+
+/*
+ *   Main vocabulary context.  This can be saved globally if desired, so
+ *   that routines that don't have any other access to it (such as
+ *   Unix-style signal handlers) can reach it.
+ */
+voccxdef *main_voc_ctx = 0;
+
+#ifdef VOCW_IN_CACHE
+vocwdef *vocwget(voccxdef *ctx, uint idx)
+{
+    uint pg;
+    
+    if (idx == VOCCXW_NONE)
+        return 0;
+
+    /* get the page we need */
+    pg = idx/VOCWPGSIZ;
+
+    /* if it's not locked, lock it */
+    if (pg != ctx->voccxwplck)
+    {
+        /* unlock the old page */
+        if (ctx->voccxwplck != MCMONINV)
+            mcmunlck(ctx->voccxmem, ctx->voccxwp[ctx->voccxwplck]);
+
+        /* lock the new page */
+        ctx->voccxwpgptr = (vocwdef *)mcmlck(ctx->voccxmem,
+                                             ctx->voccxwp[pg]);
+        ctx->voccxwplck = pg;
+    }
+
+    /* return the entry on that page */
+    return ctx->voccxwpgptr + (idx % VOCWPGSIZ);
+}
+#endif /*VOCW_IN_CACHE */
+
+/* hash value is based on first 6 characters only to allow match-in-6 */
+uint vochsh(uchar *t, int len)
+{
+    uint ret = 0;
+    
+    if (len > 6) len = 6;
+    for ( ; len ; --len, ++t)
+        ret = (ret + (uint)(vocisupper(*t) ? tolower(*t) : *t))
+               & (VOCHASHSIZ - 1);
+    return(ret);
+}
+
+/* copy vocabulary word, and convert to lower case */
+static void voccpy(uchar *dst, uchar *src, int len)
+{
+    for ( ; len ; --len, ++dst, ++src)
+        *dst = vocisupper(*src) ? tolower(*src) : *src;
+}
+
+/* allocate and set up a new vocwdef record, linking into a vocdef's list */
+static void vocwset(voccxdef *ctx, vocdef *v, prpnum p, objnum objn,
+                    int classflg)
+{
+    vocwdef *vw;
+    uint     inx;
+    vocwdef *vw2;
+
+    /*
+     *   look through the vocdef list to see if there's an existing entry
+     *   with the DELETED marker -- if so, simply undelete it 
+     */
+    for (inx = v->vocwlst, vw = vocwget(ctx, inx) ; vw ;
+         inx = vw->vocwnxt, vw = vocwget(ctx, inx))
+    {
+        /* if this entry was deleted, and otherwise matches, undelete it */
+        if ((vw->vocwflg & VOCFDEL)
+            && vw->vocwobj == objn && vw->vocwtyp == p)
+        {
+            /*
+             *   Remove the deleted flag.  We will otherwise leave the
+             *   flags unchanged, since the VOCFDEL flag applies only to
+             *   statically allocated objects, and hence the original
+             *   flags should take precedence over any run-time flags. 
+             */
+            vw->vocwflg &= ~VOCFDEL;
+
+            /* we're done */
+            return;
+        }
+    }
+
+    /* make sure the word+object+type record isn't already defined */
+    for (inx = v->vocwlst, vw = vocwget(ctx, inx) ; vw ;
+         inx = vw->vocwnxt, vw = vocwget(ctx, inx))
+    {
+        if (vw->vocwobj == objn && vw->vocwtyp == p
+            && (vw->vocwflg & VOCFCLASS) == (classflg & VOCFCLASS))
+        {
+            /* it matches - don't add a redundant record */
+            return;
+        }
+    }
+    
+    /* look in the free list for an available vocwdef */
+    if (ctx->voccxwfre != VOCCXW_NONE)
+    {
+        inx = ctx->voccxwfre;
+        vw = vocwget(ctx, inx);                     /* get the free vocwdef */
+        ctx->voccxwfre = vw->vocwnxt;              /* unlink from free list */
+    }
+    else
+    {
+        /* allocate another page of vocwdef's if necssary */
+        if ((ctx->voccxwalocnt % VOCWPGSIZ) == 0)
+        {
+            int pg = ctx->voccxwalocnt / VOCWPGSIZ;
+            
+            /* make sure we haven't exceeded the available page count */
+            if (pg >= VOCWPGMAX) errsig(ctx->voccxerr, ERR_VOCMNPG);
+            
+            /* allocate on the new page */
+#ifdef VOCW_IN_CACHE
+            mcmalo(ctx->voccxmem, (ushort)(VOCWPGSIZ * sizeof(vocwdef)),
+                                           &ctx->voccxwp[pg]);
+            mcmunlck(ctx->voccxmem, ctx->voccxwp[pg]);
+#else
+            ctx->voccxwp[pg] =
+                (vocwdef *)mchalo(ctx->voccxerr,
+                                  (VOCWPGSIZ * sizeof(vocwdef)),
+                                  "vocwset");
+#endif
+        }
+
+        /* get the next entry, and increment count of used entries */
+        inx = ctx->voccxwalocnt++;
+        vw = vocwget(ctx, inx);
+    }
+
+    /* link the new vocwdef into the vocdef's relation list */
+    vw->vocwnxt = v->vocwlst;
+    v->vocwlst = inx;
+
+    /* set up the new vocwdef */
+    vw->vocwtyp = (uchar)p;
+    vw->vocwobj = objn;
+    vw->vocwflg = classflg;
+
+    /* 
+     *   Scan the list and make sure we're not adding a redundant verb.
+     *   Don't bother with the warning if this is a class. 
+     */
+    if (p == PRP_VERB && (ctx->voccxflg & VOCCXFVWARN)
+        && (vw->vocwflg & VOCFCLASS) == 0)
+    {
+        for (vw2 = vocwget(ctx, v->vocwlst) ; vw2 ;
+             vw2 = vocwget(ctx, vw2->vocwnxt))
+        {
+            /* 
+             *   if this is a different object, and it's not a class, and
+             *   it's defined as a verb, warn about it 
+             */
+            if (vw2 != vw
+                && (vw2->vocwflg & VOCFCLASS) == 0
+                && vw2->vocwtyp == PRP_VERB)
+            {
+                if (v->vocln2 != 0)
+                    errlog2(ctx->voccxerr, ERR_VOCREVB,
+                            ERRTSTR,
+                            errstr(ctx->voccxerr,
+                                   (char *)v->voctxt, v->voclen),
+                            ERRTSTR,
+                            errstr(ctx->voccxerr,
+                                   (char *)v->voctxt + v->voclen, v->vocln2));
+                else
+                    errlog1(ctx->voccxerr, ERR_VOCREVB,
+                            ERRTSTR,
+                            errstr(ctx->voccxerr,
+                                   (char *)v->voctxt, v->voclen));
+                break;
+            }
+        }
+    }
+}
+
+/* set up a vocdef record, and link into hash table */
+static void vocset(voccxdef *ctx, vocdef *v, prpnum p, objnum objn,
+                   int classflg, uchar *wrdtxt, int len,
+                   uchar *wrd2, int len2)
+{
+    uint hshval = vochsh(wrdtxt, len);
+    
+    v->vocnxt = ctx->voccxhsh[hshval];
+    ctx->voccxhsh[hshval] = v;
+    
+    v->voclen = len;
+    v->vocln2 = len2;
+    voccpy(v->voctxt, wrdtxt, len);
+    if (wrd2) voccpy(v->voctxt + len, wrd2, len2);
+
+    /* allocate and initialize a vocwdef for the object */
+    vocwset(ctx, v, p, objn, classflg);
+}
+
+/* internal addword - already parsed into two words and have lengths */
+void vocadd2(voccxdef *ctx, prpnum p, objnum objn, int classflg,
+             uchar *wrdtxt, int len, uchar *wrd2, int len2)
+{
+    vocdef  *v;
+    vocdef  *prv;
+    uint     need;
+    uint     hshval;
+
+    /* if the word is null, ignore it entirely */
+    if (len == 0 && len2 == 0)
+        return;
+
+    /* look for a vocdef entry with the same word text */
+    hshval = vochsh(wrdtxt, len);
+    for (v = ctx->voccxhsh[hshval] ; v ; v = v->vocnxt)
+    {
+        /* if it matches on both words, use this entry */
+        if (v->voclen == len && !memcmp(wrdtxt, v->voctxt, (size_t)len)
+            && ((!wrd2 && v->vocln2 == 0)
+                || (v->vocln2 == len2 &&
+                    !memcmp(wrd2, v->voctxt + len, (size_t)len2))))
+        {
+            vocwset(ctx, v, p, objn, classflg);
+            return;
+        }
+    }
+
+    /* look for a free vocdef entry of the same size */
+    for (prv = (vocdef *)0, v = ctx->voccxfre ; v ; prv = v, v = v->vocnxt)
+        if (v->voclen == len + len2) break;
+    
+    if (v)
+    {
+        /* we found something - unlink from free list */
+        if (prv) prv->vocnxt = v->vocnxt;
+        else ctx->voccxfre = v->vocnxt;
+        
+        /* reuse the entry */
+        v->vocwlst = VOCCXW_NONE;
+        vocset(ctx, v, p, objn, classflg, wrdtxt, len, wrd2, len2);
+        return;
+    }
+    
+    /* didn't find an existing vocdef; allocate a new one */
+    need = sizeof(vocdef) + len + len2 - 1;
+    if (ctx->voccxrem < need)
+    {
+        /* not enough space in current page; allocate a new one */
+        ctx->voccxpool = mchalo(ctx->voccxerr, VOCPGSIZ, "vocadd2");
+        ctx->voccxrem = VOCPGSIZ;
+    }
+    
+    /* use top of current pool, and update pool pointer and size */
+    v = (vocdef *)ctx->voccxpool;
+    need = osrndsz(need);
+    ctx->voccxpool += need;
+    if (ctx->voccxrem > need) ctx->voccxrem -= need;
+    else ctx->voccxrem = 0;
+    
+    /* set up new vocdef */
+    v->vocwlst = VOCCXW_NONE;
+    vocset(ctx, v, p, objn, classflg, wrdtxt, len, wrd2, len2);
+}
+
+static void voc_parse_words(char **wrdtxt, int *len, char **wrd2, int *len2)
+{
+    /* get length and pointer to actual text */
+    *len = osrp2(*wrdtxt) - 2;
+    *wrdtxt += 2;
+    
+    /* see if there's a second word - look for a space */
+    for (*wrd2 = *wrdtxt, *len2 = *len ; *len2 && !vocisspace(**wrd2) ;
+         ++*wrd2, --*len2) ;
+    if (*len2)
+    {
+        *len -= *len2;
+        while (*len2 && vocisspace(**wrd2)) ++*wrd2, --*len2;
+    }
+    else
+    {
+        /* no space ==> no second word */
+        *wrd2 = (char *)0;
+    }
+}
+
+void vocadd(voccxdef *ctx, prpnum p, objnum objn, int classflg, char *wrdtxt)
+{
+    int     len;
+    char   *wrd2;
+    int     len2;
+
+    voc_parse_words(&wrdtxt, &len, &wrd2, &len2);
+    vocadd2(ctx, p, objn, classflg, (uchar *)wrdtxt, len, (uchar *)wrd2, len2);
+}
+
+/* make sure we have a page table entry for an object, allocating one if not */
+void vocialo(voccxdef *ctx, objnum obj)
+{
+    if (!ctx->voccxinh[obj >> 8])
+    {
+        ctx->voccxinh[obj >> 8] =
+            (vocidef **)mchalo(ctx->voccxerr,
+                               (256 * sizeof(vocidef *)), "vocialo");
+        memset(ctx->voccxinh[obj >> 8], 0, (size_t)(256 * sizeof(vocidef *)));
+    }
+}
+
+/* add an inheritance/location record */
+void vociadd(voccxdef *ctx, objnum obj, objnum loc,
+             int numsc, objnum *sc, int flags)
+{
+    vocidef *v;
+    vocidef *min;
+    vocidef *prv;
+	vocidef *minprv = nullptr;
+
+    /* make sure we have a page table entry for this object */
+    vocialo(ctx, obj);
+
+    /* look in free list for an entry that's big enough */
+    for (prv = (vocidef *)0, min = (vocidef *)0, v = ctx->voccxifr ; v ;
+         prv = v, v = v->vocinxt)
+    {
+        if (v->vocinsc == numsc)
+        {
+            min = v;
+            minprv = prv;
+            break;
+        }
+        else if (v->vocinsc > numsc)
+        {
+            if (!min || v->vocinsc < min->vocinsc)
+            {
+                min = v;
+                minprv = prv;
+            }
+        }
+    }
+    
+    if (!min)
+    {
+        uint need;
+        
+        /* nothing in free list; allocate a new entry */
+        need = osrndsz(sizeof(vocidef) + (numsc - 1)*sizeof(objnum));
+        if (ctx->voccxilst + need >= VOCISIZ)
+        {
+            /* nothing left on current page; allocate a new page */
+            ctx->voccxip[++(ctx->voccxiplst)] =
+                mchalo(ctx->voccxerr, VOCISIZ, "vociadd");
+            ctx->voccxilst = 0;
+        }
+
+        /* allocate space out of current page */
+        v = (vocidef *)(ctx->voccxip[ctx->voccxiplst] + ctx->voccxilst);
+        ctx->voccxilst += need;
+    }
+    else
+    {
+        /* unlink from chain and use */
+        v = min;
+        if (minprv)
+            minprv->vocinxt = v->vocinxt;
+        else
+            ctx->voccxifr = v->vocinxt;
+    }
+
+    /* set up the entry */
+    if (vocinh(ctx, obj) != (vocidef *)0) errsig(ctx->voccxerr, ERR_VOCINUS);
+    v->vociloc = loc;
+    v->vociilc = MCMONINV;
+    v->vociflg = (flags & ~VOCIFXLAT);
+    v->vocinsc = numsc;
+    if (numsc)
+    {
+        if (flags & VOCIFXLAT)
+        {
+            int i;
+            
+            for (i = 0 ; i < numsc ; ++i)
+                v->vocisc[i] = osrp2(&sc[i]);
+        }
+        else
+            memcpy(v->vocisc, sc, (size_t)(numsc * sizeof(objnum)));
+    }
+    vocinh(ctx, obj) = v;                           /* set page table entry */
+}
+
+/* revert all objects to original state, using inheritance records */
+void vocrevert(voccxdef *vctx)
+{
+    vocidef ***vpg;
+    vocidef  **v;
+    int        i;
+    int        j;
+    objnum     obj;
+
+    /*
+     *   Go through the inheritance records.  Delete each dynamically
+     *   allocated object, and revert each static object to its original
+     *   load state. 
+     */
+    for (vpg = vctx->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
+    {
+        if (!*vpg) continue;
+        for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
+        {
+            if (*v)
+            {
+                /* if the object was dynamically allocated, delete it */
+                if ((*v)->vociflg & VOCIFNEW)
+                {
+                    /* delete vocabulary and inheritance data for the object */
+                    vocidel(vctx, obj);
+                    vocdel(vctx, obj);
+
+                    /* delete the object */
+                    mcmfre(vctx->voccxmem, (mcmon)obj);
+                }
+                else
+                {
+                    /* revert the object */
+                    mcmrevert(vctx->voccxmem, (mcmon)obj);
+                }
+            }
+        }
+    }
+
+    /*
+     *   Revert the vocabulary list: delete all newly added words, and
+     *   undelete all original words marked as deleted.  
+     */
+    vocdel1(vctx, MCMONINV, (char *)0, 0, TRUE, TRUE, FALSE);
+}
+
+/* initialize voc context */
+void vocini(voccxdef *vocctx, errcxdef *errctx, mcmcxdef *memctx,
+            runcxdef *runctx, objucxdef *undoctx,
+            int fuses, int daemons, int notifiers)
+{
+    CLRSTRUCT(*vocctx);
+    vocctx->voccxerr = errctx;
+    vocctx->voccxiplst = (uint)-1;
+    vocctx->voccxilst = VOCISIZ;
+    vocctx->voccxmem = memctx;
+    vocctx->voccxrun = runctx;
+    vocctx->voccxundo = undoctx;
+
+    vocctx->voccxme  =
+    vocctx->voccxme_init = 
+    vocctx->voccxvtk =
+    vocctx->voccxstr =
+    vocctx->voccxnum =
+    vocctx->voccxit  =
+    vocctx->voccxhim =
+    vocctx->voccxprd =
+    vocctx->voccxpre =
+    vocctx->voccxpre2 =
+    vocctx->voccxppc =
+    vocctx->voccxlsv =
+    vocctx->voccxpreinit =
+    vocctx->voccxper =
+    vocctx->voccxprom =
+    vocctx->voccxpostprom =
+    vocctx->voccxpdis =
+    vocctx->voccxper2 =
+    vocctx->voccxperp =
+    vocctx->voccxpdef =
+    vocctx->voccxpdef2 =
+    vocctx->voccxpask =
+    vocctx->voccxpask2 =
+    vocctx->voccxpask3 =
+    vocctx->voccxinitrestore =
+    vocctx->voccxpuv =
+    vocctx->voccxpnp =
+    vocctx->voccxpostact =
+    vocctx->voccxendcmd =
+    vocctx->voccxher = MCMONINV;
+    vocctx->voccxthc = 0;
+#ifdef VOCW_IN_CACHE
+    vocctx->voccxwplck = MCMONINV;
+#endif
+
+    vocctx->voccxactor = MCMONINV;
+    vocctx->voccxverb = MCMONINV;
+    vocctx->voccxprep = MCMONINV;
+    vocctx->voccxdobj = 0;
+    vocctx->voccxiobj = 0;
+
+    vocctx->voccxunknown = 0;
+    vocctx->voccxlastunk = 0;
+
+    vocctx->voc_stk_ptr = 0;
+    vocctx->voc_stk_cur = 0;
+    vocctx->voc_stk_end = 0;
+
+    /* allocate fuses, daemons, notifiers */
+    vocinialo(vocctx, &vocctx->voccxfus, (vocctx->voccxfuc = fuses));
+    vocinialo(vocctx, &vocctx->voccxdmn, (vocctx->voccxdmc = daemons));
+    vocinialo(vocctx, &vocctx->voccxalm, (vocctx->voccxalc = notifiers));
+
+    /* no entries in vocwdef free list yet */
+    vocctx->voccxwfre = VOCCXW_NONE;
+}
+
+/* uninitialize the voc context */
+void vocterm(voccxdef *ctx)
+{
+    /* delete the fuses, daemons, and notifiers */
+    voctermfree(ctx->voccxfus);
+    voctermfree(ctx->voccxdmn);
+    voctermfree(ctx->voccxalm);
+
+    /* delete the private stack */
+    if (ctx->voc_stk_ptr != 0)
+        mchfre(ctx->voc_stk_ptr);
+}
+
+/* clean up the vocab context */
+void voctermfree(vocddef *what)
+{
+    if (what != 0)
+        mchfre(what);
+}
+
+/*
+ *   Iterate through all words for a particular object, calling a
+ *   function with each vocwdef found.  If objn == MCMONINV, we'll call
+ *   the callback for every word.  
+ */
+void voc_iterate(voccxdef *ctx, objnum objn,
+                 void (*fn)(void *, vocdef *, vocwdef *), void *fnctx)
+{
+    int       i;
+    vocdef   *v;
+    vocdef  **vp;
+    vocwdef  *vw;
+    uint      idx;
+
+    /* go through each hash value looking for matching words */
+    for (i = VOCHASHSIZ, vp = ctx->voccxhsh ; i ; ++vp, --i)
+    {
+        /* go through all words in this hash chain */
+        for (v = *vp ; v ; v = v->vocnxt)
+        {
+            /* go through each object relation for this word */
+            for (idx = v->vocwlst, vw = vocwget(ctx, idx) ; vw ;
+                 idx = vw->vocwnxt, vw = vocwget(ctx, idx))
+            {
+                /*
+                 *   if this word is for this object, call the callback
+                 */
+                if (objn == MCMONINV || vw->vocwobj == objn)
+                    (*fn)(fnctx, v, vw);
+            }
+        }
+    }
+}
+
+/* callback context for voc_count */
+struct voc_count_ctx
+{
+    int     cnt;
+    int     siz;
+    prpnum  prp;
+};
+
+/* callback for voc_count */
+static void voc_count_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+    struct voc_count_ctx *ctx = (struct voc_count_ctx *)ctx0;
+    
+    VARUSED(vocw);
+
+    /*
+     *   If it matches the property (or we want all properties), count
+     *   it.  Don't count deleted objects. 
+     */
+    if ((ctx->prp == 0 || ctx->prp == vocw->vocwtyp)
+        && !(vocw->vocwflg & VOCFDEL))
+    {
+        /* count the word */
+        ctx->cnt++;
+        
+        /* count the size */
+        ctx->siz += voc->voclen + voc->vocln2;
+    }
+}
+
+/*
+ *   Get the number and size of words defined for an object.  The size
+ *   returns the total byte count from all the words involved.  Do not
+ *   include deleted words in the count.  
+ */
+void voc_count(voccxdef *ctx, objnum objn, prpnum prp, int *cnt, int *siz)
+{
+    struct voc_count_ctx fnctx;
+
+    /* set up the context with zero initial counts */
+    fnctx.cnt = 0;
+    fnctx.siz = 0;
+    fnctx.prp = prp;
+
+    /* iterate over all words for the object with our callback */
+    voc_iterate(ctx, objn, voc_count_cb, &fnctx);
+
+    /* return the data */
+    if (cnt) *cnt = fnctx.cnt;
+    if (siz) *siz = fnctx.siz;
+}
+
+
+/*
+ *   Delete a particular word associated with an object, or all words if
+ *   the word pointer is null.  If the word isn't marked as added at
+ *   runtime (i.e., the VOCFNEW flag is not set for the word), the word is
+ *   simply marked as deleted, rather than being actually deleted.
+ *   However, if the 'really_delete' flag is set, the word is actually
+ *   deleted.  If the 'revert' flag is true, this routine deletes _every_
+ *   dynamically created word, and undeletes all dynamically deleted words
+ *   that were in the original vocabulary.  
+ */
+void vocdel1(voccxdef *ctx, objnum objn, char *wrd1, prpnum prp,
+             int really_delete, int revert, int keep_undo)
+{
+    int       i;
+    vocdef   *v;
+    vocdef   *prv;
+    vocdef   *nxt;
+    vocdef  **vp;
+    vocwdef  *vw;
+    vocwdef  *prvw;
+    vocwdef  *nxtw;
+    uint      nxtidx;
+    uint      idx;
+    int       deleted_vocdef;
+	char     *wrd2 = nullptr;
+    int       len1 = 0, len2 = 0;
+    int       do_del;
+    char     *orgwrd;
+
+    /* parse the word if provided */
+    orgwrd = wrd1;
+    if (wrd1)
+        voc_parse_words(&wrd1, &len1, &wrd2, &len2);
+    
+    /* go through each hash value looking for matching words */
+    for (i = VOCHASHSIZ, vp = ctx->voccxhsh ; i ; ++vp, --i)
+    {
+        /* go through all words in this hash chain */
+        for (prv = (vocdef *)0, v = *vp ; v ; v = nxt)
+        {
+            /* remember next word in hash chain */
+            nxt = v->vocnxt;
+
+            /* if this word doesn't match, skip it */
+            if (wrd1)
+            {
+                /* compare the first word */
+                if (v->voclen != len1
+                    || memicmp((char *)v->voctxt, wrd1, (size_t)len1))
+                {
+                    prv = v;
+                    continue;
+                }
+
+                /* if there's a second word, compare it as well */
+                if (wrd2 && (v->vocln2 != len2
+                             || memicmp((char *)v->voctxt + len1,
+                                        wrd2, (size_t)len2)))
+                {
+                    prv = v;
+                    continue;
+                }
+            }
+
+            /* presume we're not going to delete this vocdef */
+            deleted_vocdef = FALSE;
+            
+            /* go through all object relations for this word */
+            for (prvw = 0, idx = v->vocwlst, vw = vocwget(ctx, idx) ; vw ;
+                 vw = nxtw, idx = nxtidx)
+            {
+                /* remember next word in relation list */
+                nxtidx = vw->vocwnxt;
+                nxtw = vocwget(ctx, nxtidx);
+
+                /*
+                 *   figure out whether to delete this word, based on the
+                 *   caller's specified operating mode 
+                 */
+                if (revert)
+                {
+                    /* if reverting, delete all new words */
+                    do_del = (vw->vocwflg & VOCFNEW);
+
+                    /* also, remove the DELETED flag if present */
+                    vw->vocwflg &= ~VOCFDEL;
+                }
+                else
+                {
+                    /*
+                     *   delete the word if the object number matches,
+                     *   AND either we're not searching for a specific
+                     *   vocabulary word (in which case wrd1 will be null)
+                     *   or the word matches the vocabulary word we're
+                     *   seeking (in which case the part of speech must
+                     *   match) 
+                     */
+                    do_del = (vw->vocwobj == objn
+                              && (wrd1 == 0 || vw->vocwtyp == prp));
+
+                    /*
+                     *   if we're not in really_delete mode, and the word
+                     *   matches and it wasn't dynamically added at
+                     *   run-time, simply mark it as deleted -- this will
+                     *   allow it to be undeleted if the game is reverted
+                     *   to RESTART conditions 
+                     */
+                    if (do_del && !really_delete && !(vw->vocwflg & VOCFNEW))
+                    {
+                        /* geneate undo for the operation */
+                        if (keep_undo && orgwrd)
+                            vocdusave_delwrd(ctx, objn, prp,
+                                             vw->vocwflg, orgwrd);
+                        
+                        /* just mark the word for deletion, but keep vw */
+                        vw->vocwflg |= VOCFDEL;
+                        do_del = FALSE;
+                    }
+                }
+
+                /* now delete the structure if we decided we should */
+                if (do_del)
+                {
+                    /* geneate undo for the operation */
+                    if (keep_undo && orgwrd)
+                        vocdusave_delwrd(ctx, objn, prp, vw->vocwflg, orgwrd);
+                                         
+                    /* unlink this vocwdef from the vocdef's list */
+                    if (prvw)
+                        prvw->vocwnxt = vw->vocwnxt;
+                    else
+                        v->vocwlst = vw->vocwnxt;
+
+                    /* link the vocwdef into the vocwdef free list */
+                    vw->vocwnxt = ctx->voccxwfre;
+                    ctx->voccxwfre = idx;
+
+                    /*
+                     *   if there's nothing left in the vocdef's list,
+                     *   delete the entire vocdef as well 
+                     */
+                    if (v->vocwlst == VOCCXW_NONE)
+                    {
+                        if (prv) prv->vocnxt = v->vocnxt;
+                        else *vp = v->vocnxt;
+                    
+                        /* link into free chain */
+                        v->vocnxt = ctx->voccxfre;
+                        ctx->voccxfre = v;
+
+                        /* note that it's been deleted */
+                        deleted_vocdef = TRUE;
+                    }
+                }
+                else
+                {
+                    /* we're not deleting the word, so move prvw forward */
+                    prvw = vw;
+                }
+            }
+
+            /* if we didn't delete this vocdef, move prv forward onto it */
+            if (!deleted_vocdef)
+                prv = v;
+        }
+    }
+}
+
+/* delete all vocabulary for an object */
+void vocdel(voccxdef *ctx, objnum objn)
+{
+    vocdel1(ctx, objn, (char *)0, (prpnum)0, TRUE, FALSE, FALSE);
+}
+
+/* delete object inheritance records for a particular object */
+void vocidel(voccxdef *ctx, objnum obj)
+{
+    vocidef *v;
+    
+    /* get entry out of page table, and clear page table slot */
+    v = vocinh(ctx, obj);
+    vocinh(ctx, obj) = (vocidef *)0;
+    
+    /* link into free list */
+    if (v)
+    {
+        v->vocinxt = ctx->voccxifr;
+        ctx->voccxifr = v;
+    }
+}
+
+/*
+ *   Find template matching a verb+object+prep combination; return TRUE
+ *   if a suitable template is found, FALSE otherwise.
+ */
+int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep,
+              uchar *tplout, int *newstyle)
+{
+    uchar  *tplptr;
+    uchar  *thistpl;
+    int     found;
+    int     tplcnt;
+    uint    tplofs;
+    objnum  verb;
+
+    /* look for a new-style template first */
+    tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL2, &verb, FALSE);
+    if (tplofs)
+    {
+        /* flag the presence of the new-style template */
+        *newstyle = TRUE;
+    }
+    else
+    {
+        /* no new-style template - look for old version */
+        tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL, &verb, FALSE);
+        *newstyle = FALSE;
+    }
+
+    /* inherit templates until we run out of them */
+    for (;;)
+    {
+        /* if we found something already, use it */
+        if (tplofs)
+        {
+            size_t siz;
+
+            /* figure the size of this template style */
+            siz = (*newstyle ? VOCTPL2SIZ : VOCTPLSIZ);
+            
+            /* lock the verb object, and get the property value pointer */
+            tplptr  = mcmlck(ctx->voccxmem, verb);
+            thistpl = prpvalp(tplptr + tplofs);
+            
+            /* first byte is number of templates in array */
+            tplcnt = *thistpl++;
+            
+            /* look for a template that matches the preposition object */
+            for (found = FALSE ; tplcnt ; thistpl += siz, --tplcnt)
+            {
+                if (voctplpr(thistpl) == prep)
+                {
+                    found = TRUE;
+                    break;
+                }
+            }
+            
+            /* unlock the object and return the value if we found one */
+            mcmunlck(ctx->voccxmem, verb);
+            if (found)
+            {
+                memcpy(tplout, thistpl, siz);
+                return(TRUE);
+            }
+        }
+
+        /* try inheriting a template (new-style, then old-style) */
+        tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL2, &verb, TRUE);
+        if (tplofs)
+            *newstyle = TRUE;
+        else
+        {
+            tplofs = objgetap(ctx->voccxmem, verb_in, PRP_TPL, &verb, TRUE);
+            *newstyle = FALSE;
+        }
+
+        /* return not-found if we couldn't inherit it */
+        if (!tplofs)
+            return FALSE;
+
+        /* use the newly found verb */
+        verb_in = verb;
+    }
+}
+
+/*
+ *   Set the "Me" object 
+ */
+void voc_set_me(voccxdef *ctx, objnum new_me)
+{
+    /* save undo for the change */
+    vocdusave_me(ctx, ctx->voccxme);
+
+    /* set the new "Me" object */
+    ctx->voccxme = new_me;
+}
 
 } // End of namespace TADS2
 } // End of namespace TADS
diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h
index 5574675..f4c058e 100644
--- a/engines/glk/tads/tads2/vocabulary.h
+++ b/engines/glk/tads/tads2/vocabulary.h
@@ -27,6 +27,7 @@
 #ifndef GLK_TADS_TADS2_VOCABULARY
 #define GLK_TADS_TADS2_VOCABULARY
 
+#include "common/util.h"
 #include "glk/tads/tads2/lib.h"
 #include "glk/tads/tads2/object.h"
 #include "glk/tads/tads2/property.h"
@@ -645,11 +646,11 @@ void vocdusave_me(voccxdef *ctx, objnum old_me);
 uint vochsh(uchar *t, int len);
 
 /* TADS versions of isalpha, isspace, isdigit, etc */
-#define vocisupper(c) ((uchar)(c) <= 127 && isupper((uchar)(c)))
-#define vocislower(c) ((uchar)(c) <= 127 && islower((uchar)(c)))
-#define vocisalpha(c) ((uchar)(c) > 127 || isalpha((uchar)(c)))
-#define vocisspace(c) ((uchar)(c) <= 127 && isspace((uchar)(c)))
-#define vocisdigit(c) ((uchar)(c) <= 127 && isdigit((uchar)(c)))
+#define vocisupper(c) ((uchar)(c) <= 127 && Common::isUpper((uchar)(c)))
+#define vocislower(c) ((uchar)(c) <= 127 && Common::isLower((uchar)(c)))
+#define vocisalpha(c) ((uchar)(c) > 127 || Common::isAlpha((uchar)(c)))
+#define vocisspace(c) ((uchar)(c) <= 127 && Common::isSpace((uchar)(c)))
+#define vocisdigit(c) ((uchar)(c) <= 127 && Common::isDigit((uchar)(c)))
 
 
 /*


Commit: 105a1b94bd9d5a0f10752e135671f4e9a4b0d8da
    https://github.com/scummvm/scummvm/commit/105a1b94bd9d5a0f10752e135671f4e9a4b0d8da
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS2: Added code for output, run, various miscellaneous

Changed paths:
  A engines/glk/tads/os_glk.h
  A engines/glk/tads/tads2/list.cpp
  A engines/glk/tads/tads2/list.h
  A engines/glk/tads/tads2/output.cpp
    common/util.cpp
    common/util.h
    engines/glk/module.mk
    engines/glk/tads/osfrobtads.cpp
    engines/glk/tads/osfrobtads.h
    engines/glk/tads/tads2/character_map.cpp
    engines/glk/tads/tads2/data.cpp
    engines/glk/tads/tads2/data.h
    engines/glk/tads/tads2/error_handling.h
    engines/glk/tads/tads2/lib.h
    engines/glk/tads/tads2/os.h
    engines/glk/tads/tads2/run.cpp
    engines/glk/tads/tads2/run.h
    engines/glk/tads/tads2/tokenizer.cpp
    engines/glk/tads/tads2/vocabulary.cpp


diff --git a/common/util.cpp b/common/util.cpp
index 62a1baf..a6c7958 100644
--- a/common/util.cpp
+++ b/common/util.cpp
@@ -23,6 +23,7 @@
 #define FORBIDDEN_SYMBOL_EXCEPTION_isalnum
 #define FORBIDDEN_SYMBOL_EXCEPTION_isalpha
 #define FORBIDDEN_SYMBOL_EXCEPTION_isdigit
+#define FORBIDDEN_SYMBOL_EXCEPTION_isxdigit
 #define FORBIDDEN_SYMBOL_EXCEPTION_isnumber
 #define FORBIDDEN_SYMBOL_EXCEPTION_islower
 #define FORBIDDEN_SYMBOL_EXCEPTION_isspace
@@ -132,6 +133,11 @@ bool isDigit(int c) {
 	return isdigit((byte)c);
 }
 
+bool isXDigit(int c) {
+	ENSURE_ASCII_CHAR(c);
+	return isxdigit((byte)c);
+}
+
 bool isLower(int c) {
 	ENSURE_ASCII_CHAR(c);
 	return islower((byte)c);
diff --git a/common/util.h b/common/util.h
index 77d7523..5f853da 100644
--- a/common/util.h
+++ b/common/util.h
@@ -142,6 +142,16 @@ bool isAlpha(int c);
 bool isDigit(int c);
 
 /**
+ * Test whether the given character is a hwzadecimal-digit (0-9 or A-F).
+ * If the parameter is outside the range of a signed or unsigned char, then
+ * false is returned.
+ *
+ * @param c		the character to test
+ * @return		true if the character is a hexadecimal-digit, false otherwise.
+ */
+bool isXDigit(int c);
+
+/**
  * Test whether the given character is a lower-case letter (a-z).
  * If the parameter is outside the range of a signed or unsigned char, then
  * false is returned.
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 9f95fc0..93eff12 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -100,11 +100,13 @@ MODULE_OBJS := \
 	tads/tads2/error_handling.o \
 	tads/tads2/file_io.o \
 	tads/tads2/line_source_file.o \
+	tads/tads2/list.o \
 	tads/tads2/memory_cache.o \
 	tads/tads2/memory_cache_heap.o \
 	tads/tads2/memory_cache_loader.o \
 	tads/tads2/memory_cache_swap.o \
 	tads/tads2/os.o \
+	tads/tads2/output.o \
 	tads/tads2/regex.o \
 	tads/tads2/run.o \
 	tads/tads2/tads2.o \
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
new file mode 100644
index 0000000..e58868b
--- /dev/null
+++ b/engines/glk/tads/os_glk.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* TADS OS interface file type definitions
+ *
+ * Defines certain datatypes used in the TADS operating system interface
+ */
+
+#ifndef GLK_TADS_OS_GLK
+#define GLK_TADS_OS_GLK
+
+namespace Glk {
+namespace TADS {
+
+#define OSPATHCHAR '/'
+#define OSPATHALT ""
+#define OSPATHURL "/"
+#define OSPATHSEP ':'
+#define OS_NEWLINE_SEQ "\n"
+
+/* maximum width (in characters) of a line of text */
+#define OS_MAXWIDTH  255
+
+/* round a size to worst-case alignment boundary */
+#define osrndsz(s) (((s)+3) & ~3)
+
+/* round a pointer to worst-case alignment boundary */
+#define osrndpt(p) ((uchar *)((((unsigned long)(p)) + 3) & ~3))
+
+/* read unaligned portable unsigned 2-byte value, returning int */
+#define osrp2(p) READ_LE_UINT16(p)
+
+/* read unaligned portable signed 2-byte value, returning int */
+#define osrp2s(p) READ_LE_INT16(p)
+
+/* write int to unaligned portable 2-byte value */
+#define oswp2(p, i) WRITE_LE_UINT16(p, i)
+#define oswp2s(p, i) WRITE_LE_INT16(p, i)
+
+/* read unaligned portable 4-byte value, returning unsigned long */
+#define osrp4(p) READ_LE_UINT32(p)
+#define osrp4s(p) READ_LE_INT32(p)
+
+#define oswp4(p, l) WRITE_LE_UINT32(p, l)
+#define oswp4s(p, l) WRITE_LE_INT32(p, l)
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index 13229e5..df265be 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -50,5 +50,13 @@ bool osfwb(osfildef *fp, void *buf, size_t count) {
 	return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count;
 }
 
+void osfflush(osfildef *fp) {
+	dynamic_cast<Common::WriteStream *>(fp)->flush();
+}
+
+osfildef *osfopwt(const char *fname, os_filetype_t typ) {
+	return osfoprwtb(fname, typ);
+}
+
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index b22dbae..ce1624d 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -38,13 +38,6 @@
 namespace Glk {
 namespace TADS {
 
-#define OSPATHCHAR '/'
-#define OSPATHALT ""
-#define OSPATHURL "/"
-#define OSPATHSEP ':'
-#define OS_NEWLINE_SEQ "\n"
-
-
 /* Defined for Gargoyle. */
 #define HAVE_STDINT_
 
@@ -178,7 +171,7 @@ int osfmode( const char* fname, int follow_links, unsigned long* mode,
 #define osfoprt(fname,typ) (fopen((fname),"r"))
 
 /* Open text file for writing. */
-#define osfopwt(fname,typ) (fopen((fname),"w"))
+osfildef *osfopwt(const char *fname, os_filetype_t typ);
 
 /* Open text file for reading and writing, keeping the file's existing
  * contents if the file already exists or creating a new file if no
@@ -221,7 +214,7 @@ inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
 inline bool osfwb(osfildef *fp, void *buf, size_t count);
 
 /* Flush buffered writes to a file. */
-#define osfflush fflush
+inline void osfflush(osfildef *fp);
 
 /* Read bytes from file. */
 int osfrb(osfildef *fp, void *buf, size_t count);
diff --git a/engines/glk/tads/tads2/character_map.cpp b/engines/glk/tads/tads2/character_map.cpp
index 91daa86..9191835 100644
--- a/engines/glk/tads/tads2/character_map.cpp
+++ b/engines/glk/tads/tads2/character_map.cpp
@@ -26,6 +26,7 @@
 #include "glk/tads/tads2/os.h"
 #include "glk/tads/tads2/text_io.h"
 #include "glk/tads/osfrobtads.h"
+#include "glk/tads/os_glk.h"
 #include "common/algorithm.h"
 
 namespace Glk {
diff --git a/engines/glk/tads/tads2/data.cpp b/engines/glk/tads/tads2/data.cpp
index b19f45f..fe002a3 100644
--- a/engines/glk/tads/tads2/data.cpp
+++ b/engines/glk/tads/tads2/data.cpp
@@ -24,48 +24,49 @@
 #include "glk/tads/tads2/lib.h"
 #include "glk/tads/tads2/run.h"
 #include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
 #include "common/algorithm.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-Data::Data(DataType type) : _type(type) {
-}
-
-size_t Data::size() const {
-	switch (_type) {
-	case DAT_NUMBER:
-		return 4;		// numbers are in 4-byte lsb-first format
-
-	case DAT_OBJECT:
-		return 2;      // object numbers are in 2-byte lsb-first format
+/* return size of a data value */
+uint datsiz(dattyp typ, void *val)
+{
+    switch(typ)
+    {
+    case DAT_NUMBER:
+        return(4);                /* numbers are in 4-byte lsb-first format */
 
-	case DAT_SSTRING:
-	case DAT_DSTRING:
-	case DAT_LIST:
-		return osrp2(_ptr);
+    case DAT_OBJECT:
+        return(2);         /* object numbers are in 2-byte lsb-first format */
 
-	case DAT_NIL:
-	case DAT_TRUE:
-		return 0;
+    case DAT_SSTRING:
+    case DAT_DSTRING:
+    case DAT_LIST:
+        return(osrp2((char *)val));
 
-	case DAT_PROPNUM:
-	case DAT_SYN:
-	case DAT_FNADDR:
-	case DAT_REDIR:
-		return 2;
+    case DAT_NIL:
+    case DAT_TRUE:
+        return(0);
 
-	case DAT_TPL:
-		// template is counted array of 10-byte entries, plus length byte */
-		return 1 + ((*(uchar *)_ptr) * VOCTPLSIZ);
+    case DAT_PROPNUM:
+    case DAT_SYN:
+    case DAT_FNADDR:
+    case DAT_REDIR:
+        return(2);
+        
+    case DAT_TPL:
+        /* template is counted array of 10-byte entries, plus length byte */
+        return(1 + ((*(uchar *)val) * VOCTPLSIZ));
 
-	case DAT_TPL2:
-		return 1 + ((*(uchar *)_ptr) * VOCTPL2SIZ);
+    case DAT_TPL2:
+        return(1 + ((*(uchar *)val) * VOCTPL2SIZ));
 
-	default:
-		return 0;
-	}
+    default:
+        return(0);
+    }
 }
 
 } // End of namespace TADS2
diff --git a/engines/glk/tads/tads2/data.h b/engines/glk/tads/tads2/data.h
index 8d122c4..e573730 100644
--- a/engines/glk/tads/tads2/data.h
+++ b/engines/glk/tads/tads2/data.h
@@ -47,23 +47,10 @@ enum DataType {
 	DAT_REDIR   = 16,		///< redirection to different object
 	DAT_TPL2    = 17		///< new-style template
 };
-typedef DataType dattyp;
+typedef int dattyp;
 
-class Data {
-private:
-	DataType _type;
-	void *_ptr;
-public:
-	/**
-	 * Constructor
-	 */
-	Data(DataType type);
-
-	/**
-	 * Return the size of the data
-	 */
-	size_t size() const;
-};
+/* determine the size of a piece of data */
+uint datsiz(dattyp typ, void *valptr);
 
 } // End of namespace TADS2
 } // End of namespace TADS
diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h
index a225bbe..ab1f86e 100644
--- a/engines/glk/tads/tads2/error_handling.h
+++ b/engines/glk/tads/tads2/error_handling.h
@@ -67,9 +67,6 @@ namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-// TODO: Clean away use of jmp_buf, since ScummVM doesn't allow it
-struct jmp_buf {};
-
 /**
  * Maximum length of a facility identifier
  */
@@ -86,7 +83,7 @@ struct errdef {
     char           errfac[ERRFACMAX+1];        /* facility of current error */
     erradef        erraav[10];                      /* parameters for error */
     int            erraac;                   /* count of parameters in argc */
-    jmp_buf        errbuf;           /* jump buffer for current error frame */
+    //jmp_buf        errbuf;        ScummVM doesn't support using jump buffers
 };
 
 #define ERRBUFSIZ 512
@@ -120,7 +117,7 @@ struct errcxdef {
 #define ERRBEGIN(ctx) \
   { \
     errdef fr_; \
-    if ((fr_.errcode = setjmp(fr_.errbuf)) == 0) \
+    if (1 /*(fr_.errcode = setjmp(fr_.errbuf)) == 0 */) \
     { \
       fr_.errprv = (ctx)->errcxptr; \
       (ctx)->errcxptr = &fr_;
diff --git a/engines/glk/tads/tads2/lib.h b/engines/glk/tads/tads2/lib.h
index 095abfc..a7ed2aa 100644
--- a/engines/glk/tads/tads2/lib.h
+++ b/engines/glk/tads/tads2/lib.h
@@ -76,20 +76,12 @@ typedef uint32         uint32_t;
 #define bic(va, bt) ((va) &= ~(bt))
 
 /*
- *   noreg/NOREG - use for variables changed in error-protected code that
- *   are used in error handling code.  Use 'noreg' on the declaration like
- *   a storage class qualifier.  Use 'NOREG' as an argument call, passing
- *   the addresses of all variables declared noreg.  For non-ANSI
- *   compilers, a routine osnoreg(/o_ void *arg0, ... _o/) must be
- *   defined.
+ *   noreg/NOREG - was used for variables changed in error-protected code that
+ *   are used in error handling code. However, since ScummVM doesn't support
+ *   setjmp for portability reasons, the following can be left as blank defines
  */
-#ifdef OSANSI
-# define noreg volatile
-# define NOREG(arglist)
-#else /* OSANSI */
 # define noreg
-# define NOREG(arglist) osnoreg arglist ;
-#endif /* OSANSI */
+# define NOREG(arglist)
 
 /* 
  *   Linting directives.  You can define these before including this file
@@ -136,27 +128,6 @@ void varused();
 #define t_isspace(c) \
     (((unsigned char)(c)) <= 127 && Common::isSpace((unsigned char)(c)))
 
-
-/* round a size to worst-case alignment boundary */
-#define osrndsz(s) (((s)+3) & ~3)
-
-/* round a pointer to worst-case alignment boundary */
-#define osrndpt(p) ((uchar *)((((unsigned long)(p)) + 3) & ~3))
-
-/* read unaligned portable unsigned 2-byte value, returning int */
-#define osrp2(p) READ_LE_UINT16(p)
-
-/* read unaligned portable signed 2-byte value, returning int */
-#define osrp2s(p) READ_LE_INT16(p)
-
-/* write int to unaligned portable 2-byte value */
-#define oswp2(p, i) WRITE_LE_UINT16(p, i)
-#define oswp2s(p, i) WRITE_LE_INT16(p, i)
-
-/* read unaligned portable 4-byte value, returning unsigned long */
-#define osrp4(p) READ_LE_UINT32(p)
-#define osrp4s(p) READ_LE_INT32(p)
-
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/tads2/list.cpp b/engines/glk/tads/tads2/list.cpp
new file mode 100644
index 0000000..88cfad4
--- /dev/null
+++ b/engines/glk/tads/tads2/list.cpp
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/data.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+void lstadv(uchar **lstp, uint *sizp)
+{
+	uint siz;
+
+	siz = datsiz(**lstp, (*lstp) + 1) + 1;
+	assert(siz <= *sizp);
+	*lstp += siz;
+	*sizp -= siz;
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/list.h b/engines/glk/tads/tads2/list.h
new file mode 100644
index 0000000..bb5eb2b
--- /dev/null
+++ b/engines/glk/tads/tads2/list.h
@@ -0,0 +1,47 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* List definitions
+ *
+ * A TADS run-time list is essentially a packed counted array.
+ * The first thing in a list is a ushort, which specifies the
+ * number of elements in the list.  The list elements are then
+ * packed into the list immediately following.
+ */
+
+#ifndef GLK_TADS_TADS2_LIST
+#define GLK_TADS_TADS2_LIST
+
+#include "glk/tads/tads2/lib.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* advance a list pointer/size pair to the next element of a list */
+void lstadv(uchar **lstp, uint *sizp);
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/os.h b/engines/glk/tads/tads2/os.h
index 5dcc83c..666a5cb 100644
--- a/engines/glk/tads/tads2/os.h
+++ b/engines/glk/tads/tads2/os.h
@@ -393,8 +393,7 @@ int os_get_zoneinfo_key(char *buf, size_t buflen);
  *   timezone's clock settings, name(s), and rules for recurring annual
  *   changes between standard time and daylight time, if applicable.
  */
-struct os_tzrule_t
-{
+struct os_tzrule_t {
     /* 
      *   Day of year, 1-365, NEVER counting Feb 29; set to 0 if not used.
      *   Corresponds to the "J" format in Unix TZ strings.  (Called "Julian
@@ -448,8 +447,7 @@ struct os_tzrule_t
     /* time of day, in seconds after midnight (e.g., 2AM is 120 == 2*60*60) */
     int time;
 };
-struct os_tzinfo_t
-{
+struct os_tzinfo_t {
     /*
      *   The local offset from GMT, in seconds, for standard time and
      *   daylight time in this zone.  These values are positive for zones
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
new file mode 100644
index 0000000..6aa39b5
--- /dev/null
+++ b/engines/glk/tads/tads2/output.cpp
@@ -0,0 +1,3559 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/debug.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/text_io.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* Forward declarations */
+struct out_stream_info;
+
+/* 
+ *   use our own isxxx - anything outside the US ASCII range is not reliably
+ *   classifiable by the normal C isxxx routines 
+ */
+#define outissp(c) (((uchar)(c)) <= 127 && Common::isSpace((uchar)(c)))
+#define outisal(c) (((uchar)(c)) <= 127 && Common::isAlpha((uchar)(c)))
+#define outisdg(c) (((uchar)(c)) <= 127 && Common::isDigit((uchar)(c)))
+#define outisup(c) (((uchar)(c)) <= 127 && Common::isUpper((uchar)(c)))
+#define outislo(c) (((uchar)(c)) <= 127 && Common::isLower((uchar)(c)))
+
+
+/*
+ *   Turn on formatter-level MORE mode, EXCEPT under any of the following
+ *   conditions:
+ *   
+ *   - this is a MAC OS port
+ *.  - this is an HTML TADS interpreter
+ *.  - USE_OS_LINEWRAP is defined
+ *   
+ *   Formatter-level MORE mode and formatter-level line wrapping go together;
+ *   you can't have one without the other.  So, if USE_OS_LINEWRAP is
+ *   defined, we must also use OS-level MORE mode, which means we don't want
+ *   formatter-level MORE mode.
+ *   
+ *   For historical reasons, we check specifically for MAC_OS.  This was the
+ *   first platform for which OS-level MORE mode and OS-level line wrapping
+ *   were invented; at the time, we foolishly failed to anticipate that more
+ *   platforms might eventually come along with the same needs, so we coded a
+ *   test for MAC_OS rather than some more abstract marker.  For
+ *   compatibility, we retain this specific test.
+ *   
+ *   USE_OS_LINEWRAP is intended as the more abstract marker we should
+ *   originally have used.  A port should #define USE_OS_LINEWRAP in its
+ *   system-specific os_xxx.h header to turn on OS-level line wrapping and
+ *   OS-level MORE mode.  Ports should avoid adding new #ifndef tests for
+ *   specific platforms here; we've only retained the MAC_OS test because we
+ *   don't want to break the existing MAC_OS port.  
+ */
+#ifndef MAC_OS
+# ifndef USE_HTML
+#  ifndef USE_OS_LINEWRAP
+#   define USE_MORE                   /* activate formatter-level more-mode */
+#  endif /* USE_OS_LINEWRAP */
+# endif /* USE_HTML */
+#endif /* MAC_OS */
+
+/* 
+ *   In HTML mode, don't use MORE mode.  Note that we explicitly turn MORE
+ *   mode OFF, even though we won't have turned it on above, because it might
+ *   have been turned on by an os_xxx.h header.  This is here for historical
+ *   reasons; in particular, some of the HTML interpreter builds include
+ *   headers that were originally written for the normal builds for those
+ *   same platforms, and those original headers explicitly #define USE_MORE
+ *   somewhere.  So, to be absolutely sure we get it right here, we have to
+ *   explicitly turn off USE_MORE when compiling for HTML mode.  
+ */
+#ifdef USE_HTML
+# ifdef USE_MORE
+#  undef USE_MORE
+# endif
+#endif
+
+/*
+ *   QSPACE is the special character for a quoted space (internally, the
+ *   sequence "\ " (backslash-space) is converted to QSPACE).  It must not
+ *   be any printable character.  The value here may need to be changed in
+ *   the extremely unlikely event that TADS is ever ported to an EBCDIC
+ *   machine.
+ */
+#define QSPACE 26
+
+/*
+ *   QTAB is a special hard tab character indicator.  We use this when we
+ *   need to generate a hard tab to send to the underlying output layer
+ *   (in particular, we use this to send hard tabs to the HTML formatter
+ *   when we're in HTML mode).  
+ */
+#define QTAB 25
+
+
+/* maximum width of the display */
+#define MAXWIDTH  OS_MAXWIDTH
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Globals and statics.  These should really be moved into a context
+ *   structure, so that the output formatter subsystem could be shared
+ *   among multiple clients.  For now, there's no practical problem using
+ *   statics, because we only need a single output subsystem at one time.  
+ */
+
+/* current script (command input) file */
+extern osfildef *scrfp;
+
+/*
+ *   This should be TRUE if the output should have two spaces after a
+ *   period (or other such punctuation. It should generally be TRUE for
+ *   fixed-width fonts, and FALSE for proportional fonts.  
+ */
+static int doublespace = 1;
+
+/*
+ *   Log file handle and name.  If we're copying output to a log file,
+ *   these will tell us about the file.  
+ */
+osfildef *logfp;
+static char logfname[OSFNMAX];
+
+/* flag indicating whether output has occurred since last check */
+static uchar outcnt;
+
+/* flag indicating whether hidden output has occurred */
+static uchar hidout;
+
+/* flag indicating whether to show (TRUE) or hide (FALSE) output */
+static uchar outflag;
+
+/* flag indicating whether output is hidden for debugging purposes */
+int dbghid;
+
+/*
+ *   Current recursion level in formatter invocation
+ */
+static int G_recurse = 0;
+
+/* active stream in current recursion level */
+static out_stream_info *G_cur_stream;
+
+/* watchpoint mode flag */
+static uchar outwxflag;
+
+/*
+ *   User filter function.  When this function is set, we'll invoke this
+ *   function for each string that's displayed through the output
+ *   formatter.
+ */
+static objnum G_user_filter = MCMONINV;
+
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   Hack to run with TADS 2.0 with minimal reworking.  Rather than using
+ *   an allocated output layer context, store our subsystem context
+ *   information in some statics.  This is less clean than using a real
+ *   context, but doesn't create any practical problems as we don't need
+ *   to share the output formatter subsystem among multiple simultaneous
+ *   callers.  
+ */
+static runcxdef *runctx;                               /* execution context */
+static uchar    *fmsbase;                        /* format string area base */
+static uchar    *fmstop;                          /* format string area top */
+static objnum    cmdActor;                                 /* current actor */
+
+/* forward declarations of static functions */
+static void outstring_stream(out_stream_info *stream, char *s);
+static void outchar_noxlat_stream(out_stream_info *stream, char c);
+static char out_parse_entity(char *outbuf, size_t outbuf_size,
+                             char **sp, size_t *slenp);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   HTML lexical analysis mode
+ */
+#define HTML_MODE_NORMAL  0                    /* normal text, not in a tag */
+#define HTML_MODE_TAG     1                         /* parsing inside a tag */
+#define HTML_MODE_SQUOTE  2           /* in a single-quoted string in a tag */
+#define HTML_MODE_DQUOTE  3           /* in a double-quoted string in a tag */
+
+/*
+ *   HTML parsing mode flag for <BR> tags.  We defer these until we've
+ *   read the full tag in order to obey an HEIGHT attribute we find.  When
+ *   we encounter a <BR>, we figure out whether we think we'll need a
+ *   flush or a blank line; if we find a HEIGHT attribute, we may change
+ *   this opinion.  
+ */
+#define HTML_DEFER_BR_NONE   0                           /* no pending <BR> */
+#define HTML_DEFER_BR_FLUSH  1                   /* only need an outflush() */
+#define HTML_DEFER_BR_BLANK  2                        /* need an outblank() */
+
+/*
+ *   If we're compiling for an HTML-enabled underlying output subsystem,
+ *   we want to call the underlying OS layer when switching in and out of
+ *   HTML mode.  If the underlying system doesn't process HTML, we don't
+ *   need to let it know anything about HTML mode. 
+ */
+#ifdef USE_HTML
+# define out_start_html(stream) os_start_html()
+# define out_end_html(stream)   os_end_html()
+#else
+# define out_start_html(stream)
+# define out_end_html(stream)
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Output formatter stream state structure.  This structure encapsulates
+ *   the state of an individual output stream.  
+ */
+struct out_stream_info {
+    /* low-level display routine (va_list version) */
+    void (*do_print)(out_stream_info *stream, const char *str);
+    
+    /* current line position and output column */
+    uchar linepos;
+    uchar linecol;
+
+    /* number of lines on the screen (since last MORE prompt) */
+    int linecnt;
+
+    /* output buffer */
+    char linebuf[MAXWIDTH];
+
+    /* 
+     *   attribute buffer - we keep one attribute entry for each character in
+     *   the line buffer 
+     */
+    int attrbuf[MAXWIDTH];
+
+    /* current attribute for text we're buffering into linebuf */
+    int cur_attr;
+
+    /* last attribute we wrote to the osifc layer */
+    int os_attr;
+
+    /* CAPS mode - next character output is converted to upper-case */
+    uchar capsflag;
+
+    /* NOCAPS mode - next character output is converted to lower-case */
+    uchar nocapsflag;
+
+    /* ALLCAPS mode - all characters output are converted to upper-case */
+    uchar allcapsflag;
+
+    /* capture information */
+    mcmcxdef *capture_ctx;           /* memory context to use for capturing */
+    mcmon     capture_obj;                /* object holding captured output */
+    uint      capture_ofs;                /* write offset in capture object */
+    int       capturing;                 /* true -> we are capturing output */
+
+    /* "preview" state for line flushing */
+    int preview;
+
+    /* flag indicating that we just flushed a new line */
+    int just_did_nl;
+
+    /* this output stream uses "MORE" mode */
+    int use_more_mode;
+
+    /* 
+     *   This output stream uses OS-level line wrapping - if this is set,
+     *   the output formatter will not insert a newline at the end of a
+     *   line that it's flushing for word wrapping, but will instead let
+     *   the underlying OS display layer handle the wrapping. 
+     */
+    int os_line_wrap;
+
+    /*
+     *   Flag indicating that the underlying output system wants to
+     *   receive its output as HTML.
+     *   
+     *   If this is true, we'll pass through HTML to the underlying output
+     *   system, and in addition generate HTML sequences for certain
+     *   TADS-native escapes (for example, we'll convert the "\n" sequence
+     *   to a <BR> sequence).
+     *   
+     *   If this is false, we'll do just the opposite: we'll remove HTML
+     *   from the output stream and convert it into normal text sequences. 
+     */
+    int html_target;
+
+    /*
+     *   Flag indicating that the target uses plain text.  If this flag is
+     *   set, we won't add the OS escape codes for highlighted characters. 
+     */
+    int plain_text_target;
+    
+    /* 
+     *   Flag indicating that the caller is displaying HTML.  We always
+     *   start off in text mode; the client can switch to HTML mode by
+     *   displaying a special escape sequence, and can switch back to text
+     *   mode by displaying a separate special escape sequence.  
+     */
+    int html_mode;
+
+    /* current lexical analysis mode */
+    unsigned int html_mode_flag;
+
+    /* <BR> defer mode */
+    unsigned int html_defer_br;
+
+    /* 
+     *   HTML "ignore" mode - we suppress all output when parsing the
+     *   contents of a <TITLE> or <ABOUTBOX> tag 
+     */
+    int html_in_ignore;
+
+    /*
+     *   HTML <TITLE> mode - when we're in this mode, we're gathering the
+     *   title (i.e., we're inside a <TITLE> tag's contents).  We'll copy
+     *   characters to the title buffer rather than the normal output
+     *   buffer, and then call os_set_title() when we reach the </TITLE>
+     *   tag.  
+     */
+    int html_in_title;
+
+    /* buffer for the title */
+    char html_title_buf[256];
+
+    /* pointer to next available character in title buffer */
+    char *html_title_ptr;
+
+    /* quoting level */
+    int html_quote_level;
+
+    /* PRE nesting level */
+    int html_pre_level;
+
+    /*
+     *   Parsing mode flag for ALT attributes.  If we're parsing a tag
+     *   that allows ALT, such as IMG or SOUND, we'll set this flag, then
+     *   insert the ALT text if we encounter it during parsing.  
+     */
+    int html_allow_alt;
+};
+
+/*
+ *   Default output converter.  This is the output converter for the
+ *   standard display.  Functions in the public interface that do not
+ *   specify an output converter will use this converter by default.  
+ */
+static out_stream_info G_std_disp;
+
+/*
+ *   Log file converter.  This is the output converter for a log file.
+ *   Whenever we open a log file, we'll initialize this converter; as we
+ *   display text to the main display, we'll also copy it to the log file.
+ *   
+ *   We maintain an entire separate conversion context for the log file,
+ *   so that we can perform a different set of conversions on it.  We may
+ *   want, for example, to pass HTML text through to the OS display
+ *   subsystem (this is the case for the HTML-enabled interpreter), but
+ *   we'll still want to convert log file output to text.  By keeping a
+ *   separate display context for the log file, we can format output to
+ *   the log file using an entirely different style than we do for the
+ *   display. 
+ */
+static out_stream_info G_log_disp;
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   low-level output handlers for the standard display and log file 
+ */
+
+/* standard display printer */
+static void do_std_print(out_stream_info *stream, const char *str)
+{
+    VARUSED(stream);
+    
+    /* display the text through the OS layer */
+    os_printz(str);
+}
+
+/* log file printer */
+static void do_log_print(out_stream_info *stream, const char *str)
+{
+    VARUSED(stream);
+
+    /* display to the log file */
+    if (logfp != 0 && G_os_moremode)
+    {
+        os_fprintz(logfp, str);
+        osfflush(logfp);
+    }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   initialize a generic output formatter state structure 
+ */
+static void out_state_init(out_stream_info *stream)
+{
+    /* start out at the first column */
+    stream->linepos = 0;
+    stream->linecol = 0;
+    stream->linebuf[0] = '\0';
+
+    /* set normal text attributes */
+    stream->cur_attr = 0;
+    stream->os_attr = 0;
+
+    /* start out at the first line */
+    stream->linecnt = 0;
+
+    /* we're not in either "caps", "nocaps", or "allcaps" mode yet */
+    stream->capsflag = stream->nocapsflag = stream->allcapsflag = FALSE;
+
+    /* we're not capturing yet */
+    stream->capturing = FALSE;
+    stream->capture_obj = MCMONINV;
+
+    /* we aren't previewing a line yet */
+    stream->preview = 0;
+
+    /* we haven't flushed a new line yet */
+    stream->just_did_nl = FALSE;
+
+    /* presume this stream does not use "MORE" mode */
+    stream->use_more_mode = FALSE;
+
+    /* presume this stream uses formatter-level line wrapping */
+    stream->os_line_wrap = FALSE;
+
+    /* assume that the underlying system is not HTML-enabled */
+    stream->html_target = FALSE;
+
+    /* presume this target accepts OS highlighting sequences */
+    stream->plain_text_target = FALSE;
+
+    /* start out in text mode */
+    stream->html_mode = FALSE;
+
+    /* start out in "normal" lexical state */
+    stream->html_mode_flag = HTML_MODE_NORMAL;
+
+    /* not in an ignored tag yet */
+    stream->html_in_ignore = FALSE;
+
+    /* not in title mode yet */
+    stream->html_in_title = FALSE;
+
+    /* not yet deferring line breaks */
+    stream->html_defer_br = HTML_DEFER_BR_NONE;
+
+    /* not yet in quotes */
+    stream->html_quote_level = 0;
+
+    /* not yet in a PRE block */
+    stream->html_pre_level = 0;
+
+    /* not in an ALT tag yet */
+    stream->html_allow_alt = FALSE;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   initialize a standard display stream 
+ */
+static void out_init_std(out_stream_info *stream)
+{
+    /* there's no user output filter function yet */
+    out_set_filter(MCMONINV);
+    
+    /* initialize the basic stream state */
+    out_state_init(stream);
+
+    /* set up the low-level output routine */
+    G_std_disp.do_print = do_std_print;
+
+#ifdef USE_MORE
+    /* 
+     *   We're compiled for MORE mode, and we're not compiling for an
+     *   underlying HTML formatting layer, so use MORE mode for the
+     *   standard display stream.  
+     */
+    stream->use_more_mode = TRUE;
+#else
+    /*
+     *   We're compiled for OS-layer (or HTML-layer) MORE handling.  For
+     *   this case, use OS-layer (or HTML-layer) line wrapping as well.  
+     */
+    stream->os_line_wrap = TRUE;
+#endif
+
+#ifdef USE_HTML
+    /* 
+     *   if we're compiled for HTML mode, set the standard output stream
+     *   so that it knows it has an HTML target - this will ensure that
+     *   HTML tags are passed through to the underlying stream, and that
+     *   we generate HTML equivalents for our own control sequences 
+     */
+    stream->html_target = TRUE;
+#endif
+}
+
+/*
+ *   initialize a standard log file stream 
+ */
+static void out_init_log(out_stream_info *stream)
+{
+    /* initialize the basic stream state */
+    out_state_init(stream);
+
+    /* set up the low-level output routine */
+    stream->do_print = do_log_print;
+
+    /* use plain text in the log file stream */
+    stream->plain_text_target = TRUE;
+}
+
+
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   table of '&' character name sequences 
+ */
+struct amp_tbl_t {
+    /* entity name */
+    const char *cname;
+
+    /* HTML Unicode character value */
+    uint        html_cval;
+
+    /* native character set expansion */
+    char       *expan;
+};
+
+/*
+ *   HTML entity mapping table.  When we're in non-HTML mode, we keep our
+ *   own expansion table so that we can map HTML entity names into the
+ *   local character set.
+ *   
+ *   The entries in this table must be in sorted order (by HTML entity
+ *   name), because we use a binary search to find an entity name in the
+ *   table.  
+ */
+static struct amp_tbl_t amp_tbl[] = {
+    { "AElig", 198, 0 },
+    { "Aacute", 193, 0 },
+    { "Abreve", 258, 0 },
+    { "Acirc", 194, 0 },
+    { "Agrave", 192, 0 },
+    { "Alpha", 913, 0 },
+    { "Aogon", 260, 0 },
+    { "Aring", 197, 0 },
+    { "Atilde", 195, 0 },
+    { "Auml", 196, 0 },
+    { "Beta", 914, 0 },
+    { "Cacute", 262, 0 },
+    { "Ccaron", 268, 0 },
+    { "Ccedil", 199, 0 },
+    { "Chi", 935, 0 },
+    { "Dagger", 8225, 0 },
+    { "Dcaron", 270, 0 },
+    { "Delta", 916, 0 },
+    { "Dstrok", 272, 0 },
+    { "ETH", 208, 0 },
+    { "Eacute", 201, 0 },
+    { "Ecaron", 282, 0 },
+    { "Ecirc", 202, 0 },
+    { "Egrave", 200, 0 },
+    { "Eogon", 280, 0 },
+    { "Epsilon", 917, 0 },
+    { "Eta", 919, 0 },
+    { "Euml", 203, 0 },
+    { "Gamma", 915, 0 },
+    { "Iacute", 205, 0 },
+    { "Icirc", 206, 0 },
+    { "Igrave", 204, 0 },
+    { "Iota", 921, 0 },
+    { "Iuml", 207, 0 },
+    { "Kappa", 922, 0 },
+    { "Lacute", 313, 0 },
+    { "Lambda", 923, 0 },
+    { "Lcaron", 317, 0 },
+    { "Lstrok", 321, 0 },
+    { "Mu", 924, 0 },
+    { "Nacute", 323, 0 },
+    { "Ncaron", 327, 0 },
+    { "Ntilde", 209, 0 },
+    { "Nu", 925, 0 },
+    { "OElig", 338, 0 },
+    { "Oacute", 211, 0 },
+    { "Ocirc", 212, 0 },
+    { "Odblac", 336, 0 },
+    { "Ograve", 210, 0 },
+    { "Omega", 937, 0 },
+    { "Omicron", 927, 0 },
+    { "Oslash", 216, 0 },
+    { "Otilde", 213, 0 },
+    { "Ouml", 214, 0 },
+    { "Phi", 934, 0 },
+    { "Pi", 928, 0 },
+    { "Prime", 8243, 0 },
+    { "Psi", 936, 0 },
+    { "Racute", 340, 0 },
+    { "Rcaron", 344, 0 },
+    { "Rho", 929, 0 },
+    { "Sacute", 346, 0 },
+    { "Scaron", 352, 0 },
+    { "Scedil", 350, 0 },
+    { "Sigma", 931, 0 },
+    { "THORN", 222, 0 },
+    { "Tau", 932, 0 },
+    { "Tcaron", 356, 0 },
+    { "Tcedil", 354, 0 },
+    { "Theta", 920, 0 },
+    { "Uacute", 218, 0 },
+    { "Ucirc", 219, 0 },
+    { "Udblac", 368, 0 },
+    { "Ugrave", 217, 0 },
+    { "Upsilon", 933, 0 },
+    { "Uring", 366, 0 },
+    { "Uuml", 220, 0 },
+    { "Xi", 926, 0 },
+    { "Yacute", 221, 0 },
+    { "Yuml", 376, 0 },
+    { "Zacute", 377, 0 },
+    { "Zcaron", 381, 0 },
+    { "Zdot", 379, 0 },
+    { "Zeta", 918, 0 },
+    { "aacute", 225, 0 },
+    { "abreve", 259, 0 },
+    { "acirc", 226, 0 },
+    { "acute", 180, 0 },
+    { "aelig", 230, 0 },
+    { "agrave", 224, 0 },
+    { "alefsym", 8501, 0 },
+    { "alpha", 945, 0 },
+    { "amp", '&', 0 },
+    { "and", 8743, 0 },
+    { "ang", 8736, 0 },
+    { "aogon", 261, 0 },
+    { "aring", 229, 0 },
+    { "asymp", 8776, 0 },
+    { "atilde", 227, 0 },
+    { "auml", 228, 0 },
+    { "bdquo", 8222, 0 },
+    { "beta", 946, 0 },
+    { "breve", 728, 0 },
+    { "brvbar", 166, 0 },
+    { "bull", 8226, 0 },
+    { "cacute", 263, 0 },
+    { "cap", 8745, 0 },
+    { "caron", 711, 0 },
+    { "ccaron", 269, 0 },
+    { "ccedil", 231, 0 },
+    { "cedil", 184, 0 },
+    { "cent", 162, 0 },
+    { "chi", 967, 0 },
+    { "circ", 710, 0 },
+    { "clubs", 9827, 0 },
+    { "cong", 8773, 0 },
+    { "copy", 169, 0 },
+    { "crarr", 8629, 0 },
+    { "cup", 8746, 0 },
+    { "curren", 164, 0 },
+    { "dArr", 8659, 0 },
+    { "dagger", 8224, 0 },
+    { "darr", 8595, 0 },
+    { "dblac", 733, 0 },
+    { "dcaron", 271, 0 },
+    { "deg", 176, 0 },
+    { "delta", 948, 0 },
+    { "diams", 9830, 0 },
+    { "divide", 247, 0 },
+    { "dot", 729, 0 },
+    { "dstrok", 273, 0 },
+    { "eacute", 233, 0 },
+    { "ecaron", 283, 0 },
+    { "ecirc", 234, 0 },
+    { "egrave", 232, 0 },
+    { "emdash", 8212, 0 },
+    { "empty", 8709, 0 },
+    { "endash", 8211, 0 },
+    { "eogon", 281, 0 },
+    { "epsilon", 949, 0 },
+    { "equiv", 8801, 0 },
+    { "eta", 951, 0 },
+    { "eth", 240, 0 },
+    { "euml", 235, 0 },
+    { "exist", 8707, 0 },
+    { "fnof", 402, 0 },
+    { "forall", 8704, 0 },
+    { "frac12", 189, 0 },
+    { "frac14", 188, 0 },
+    { "frac34", 190, 0 },
+    { "frasl", 8260, 0 },
+    { "gamma", 947, 0 },
+    { "ge", 8805, 0 },
+    { "gt", '>', 0 },
+    { "hArr", 8660, 0 },
+    { "harr", 8596, 0 },
+    { "hearts", 9829, 0 },
+    { "hellip", 8230, 0 },
+    { "iacute", 237, 0 },
+    { "icirc", 238, 0 },
+    { "iexcl", 161, 0 },
+    { "igrave", 236, 0 },
+    { "image", 8465, 0 },
+    { "infin", 8734, 0 },
+    { "int", 8747, 0 },
+    { "iota", 953, 0 },
+    { "iquest", 191, 0 },
+    { "isin", 8712, 0 },
+    { "iuml", 239, 0 },
+    { "kappa", 954, 0 },
+    { "lArr", 8656, 0 },
+    { "lacute", 314, 0 },
+    { "lambda", 955, 0 },
+    { "lang", 9001, 0 },
+    { "laquo", 171, 0 },
+    { "larr", 8592, 0 },
+    { "lcaron", 318, 0 },
+    { "lceil", 8968, 0 },
+    { "ldq", 8220, 0 },
+    { "ldquo", 8220, 0 },
+    { "le", 8804, 0 },
+    { "lfloor", 8970, 0 },
+    { "lowast", 8727, 0 },
+    { "loz", 9674, 0 },
+    { "lsaquo", 8249, 0 },
+    { "lsq", 8216, 0 },
+    { "lsquo", 8216, 0 },
+    { "lstrok", 322, 0 },
+    { "lt", '<', 0 },
+    { "macr", 175, 0 },
+    { "mdash", 8212, 0 },
+    { "micro", 181, 0 },
+    { "middot", 183, 0 },
+    { "minus", 8722, 0 },
+    { "mu", 956, 0 },
+    { "nabla", 8711, 0 },
+    { "nacute", 324, 0 },
+    { "nbsp", QSPACE, 0 },
+    { "ncaron", 328, 0 },
+    { "ndash", 8211, 0 },
+    { "ne", 8800, 0 },
+    { "ni", 8715, 0 },
+    { "not", 172, 0 },
+    { "notin", 8713, 0 },
+    { "nsub", 8836, 0 },
+    { "ntilde", 241, 0 },
+    { "nu", 957, 0 },
+    { "oacute", 243, 0 },
+    { "ocirc", 244, 0 },
+    { "odblac", 337, 0 },
+    { "oelig", 339, 0 },
+    { "ogon", 731, 0 },
+    { "ograve", 242, 0 },
+    { "oline", 8254, 0 },
+    { "omega", 969, 0 },
+    { "omicron", 959, 0 },
+    { "oplus", 8853, 0 },
+    { "or", 8744, 0 },
+    { "ordf", 170, 0 },
+    { "ordm", 186, 0 },
+    { "oslash", 248, 0 },
+    { "otilde", 245, 0 },
+    { "otimes", 8855, 0 },
+    { "ouml", 246, 0 },
+    { "para", 182, 0 },
+    { "part", 8706, 0 },
+    { "permil", 8240, 0 },
+    { "perp", 8869, 0 },
+    { "phi", 966, 0 },
+    { "pi", 960, 0 },
+    { "piv", 982, 0 },
+    { "plusmn", 177, 0 },
+    { "pound", 163, 0 },
+    { "prime", 8242, 0 },
+    { "prod", 8719, 0 },
+    { "prop", 8733, 0 },
+    { "psi", 968, 0 },
+    { "quot", '"', 0 },
+    { "rArr", 8658, 0 },
+    { "racute", 341, 0 },
+    { "radic", 8730, 0 },
+    { "rang", 9002, 0 },
+    { "raquo", 187, 0 },
+    { "rarr", 8594, 0 },
+    { "rcaron", 345, 0 },
+    { "rceil", 8969, 0 },
+    { "rdq", 8221, 0 },
+    { "rdquo", 8221, 0 },
+    { "real", 8476, 0 },
+    { "reg", 174, 0 },
+    { "rfloor", 8971, 0 },
+    { "rho", 961, 0 },
+    { "rsaquo", 8250, 0 },
+    { "rsq", 8217, 0 },
+    { "rsquo", 8217, 0 },
+    { "sacute", 347, 0 },
+    { "sbquo", 8218, 0 },
+    { "scaron", 353, 0 },
+    { "scedil", 351, 0 },
+    { "sdot", 8901, 0 },
+    { "sect", 167, 0 },
+    { "shy", 173, 0 },
+    { "sigma", 963, 0 },
+    { "sigmaf", 962, 0 },
+    { "sim", 8764, 0 },
+    { "spades", 9824, 0 },
+    { "sub", 8834, 0 },
+    { "sube", 8838, 0 },
+    { "sum", 8721, 0 },
+    { "sup", 8835, 0 },
+    { "sup1", 185, 0 },
+    { "sup2", 178, 0 },
+    { "sup3", 179, 0 },
+    { "supe", 8839, 0 },
+    { "szlig", 223, 0 },
+    { "tau", 964, 0 },
+    { "tcaron", 357, 0 },
+    { "tcedil", 355, 0 },
+    { "there4", 8756, 0 },
+    { "theta", 952, 0 },
+    { "thetasym", 977, 0 },
+    { "thorn", 254, 0 },
+    { "thorn", 254, 0 },
+    { "tilde", 732, 0 },
+    { "times", 215, 0 },
+    { "trade", 8482, 0 },
+    { "uArr", 8657, 0 },
+    { "uacute", 250, 0 },
+    { "uarr", 8593, 0 },
+    { "ucirc", 251, 0 },
+    { "udblac", 369, 0 },
+    { "ugrave", 249, 0 },
+    { "uml", 168, 0 },
+    { "upsih", 978, 0 },
+    { "upsilon", 965, 0 },
+    { "uring", 367, 0 },
+    { "uuml", 252, 0 },
+    { "weierp", 8472, 0 },
+    { "xi", 958, 0 },
+    { "yacute", 253, 0 },
+    { "yen", 165, 0 },
+    { "yuml", 255, 0 },
+    { "zacute", 378, 0 },
+    { "zcaron", 382, 0 },
+    { "zdot", 380, 0 },
+    { "zeta", 950, 0 }
+};
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   turn on CAPS mode for a stream 
+ */
+static void outcaps_stream(out_stream_info *stream)
+{
+    /* turn on CAPS mode */
+    stream->capsflag = TRUE;
+
+    /* turn off NOCAPS and ALLCAPS mode */
+    stream->nocapsflag = FALSE;
+    stream->allcapsflag = FALSE;
+}
+
+/*
+ *   turn on NOCAPS mode for a stream 
+ */
+static void outnocaps_stream(out_stream_info *stream)
+{
+    /* turn on NOCAPS mode */
+    stream->nocapsflag = TRUE;
+
+    /* turn off CAPS and ALLCAPS mode */
+    stream->capsflag = FALSE;
+    stream->allcapsflag = FALSE;
+}
+
+/*
+ *   turn on or off ALLCAPS mode for a stream 
+ */
+static void outallcaps_stream(out_stream_info *stream, int all_caps)
+{
+    /* set the ALLCAPS flag */
+    stream->allcapsflag = all_caps;
+
+    /* clear the CAPS and NOCAPS flags */
+    stream->capsflag = FALSE;
+    stream->nocapsflag = FALSE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   write a string to a stream 
+ */
+static void stream_print(out_stream_info *stream, char *str)
+{
+    /* call the stream's do_print method */
+    (*stream->do_print)(stream, str);
+}
+
+/*
+ *   Write out a line 
+ */
+static void t_outline(out_stream_info *stream, int nl,
+                      const char *txt, const int *attr)
+{
+    extern int scrquiet;
+
+    /* 
+     *   Check the "script quiet" mode - this indicates that we're reading
+     *   a script and not echoing output to the display.  If this mode is
+     *   on, and we're writing to the display, suppress this write.  If
+     *   the mode is off, or we're writing to another stream (such as the
+     *   log file), show the output as normal.  
+     */
+    if (!scrquiet || stream != &G_std_disp)
+    {
+        size_t i;
+        char buf[MAXWIDTH];
+        char *dst;
+        
+        /*
+         *   Check to see if we've reached the end of the screen, and if
+         *   so run the MORE prompt.  Note that we don't make this check
+         *   at all if USE_MORE is undefined, since this means that the OS
+         *   layer code is taking responsibility for pagination issues.
+         *   We also don't display a MORE prompt when reading from a
+         *   script file.
+         *   
+         *   Note that we suppress the MORE prompt if nl == 0, since this
+         *   is used to flush a partial line of text without starting a
+         *   new line (for example, when displaying a prompt where the
+         *   input will appear on the same line following the prompt).
+         *   
+         *   Skip the MORE prompt if this stream doesn't use it.  
+         */
+        if (stream->use_more_mode
+            && scrfp == 0
+            && G_os_moremode
+            && nl != 0 && nl != 4
+            && stream->linecnt++ >= G_os_pagelength)
+        {
+            /* display the MORE prompt */
+            out_more_prompt();
+        }
+
+        /*
+         *   Display the text.  Run through the text in pieces; each time the
+         *   attributes change, set attributes at the osifc level. 
+         */
+        for (i = 0, dst = buf ; txt[i] != '\0' ; ++i)
+        {
+            /* if the attribute is changing, notify osifc */
+            if (attr != 0 && attr[i] != stream->os_attr)
+            {
+                /* flush the preceding text */
+                if (dst != buf)
+                {
+                    *dst = '\0';
+                    stream_print(stream, buf);
+                }
+
+                /* set the new attribute */
+                os_set_text_attr(attr[i]);
+
+                /* remember this as the last OS attribute */
+                stream->os_attr = attr[i];
+
+                /* start with a fresh buffer */
+                dst = buf;
+            }
+
+            /* buffer this character */
+            *dst++ = txt[i];
+        }
+
+        /* flush the last chunk of text */
+        if (dst != buf)
+        {
+            *dst = '\0';
+            stream_print(stream, buf);
+        }
+    }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Flush the current line to the display.  The 'nl' argument specifies
+ *   what kind of flushing to do:
+ *   
+ *   0: flush the current line but do not start a new line; more text will
+ *   follow on the current line.  This is used, for example, to flush text
+ *   after displaying a prompt and before waiting for user input.
+ *   
+ *   1: flush the line and start a new line.
+ *   
+ *   2: flush the line as though starting a new line, but don't add an
+ *   actual newline character to the output, since the underlying OS
+ *   display code will handle this.  Instead, add a space after the line.
+ *   (This differs from mode 0 in that mode 0 shouldn't add anything at
+ *   all after the line.)
+ *   
+ *   3: "preview" mode.  Flush the line, but do not start a new line, and
+ *   retain the current text in the buffer.  This is used for systems that
+ *   handle the line wrapping in the underlying system code to flush a
+ *   partially filled line that will need to be flushed again later.
+ *   
+ *   4: same as mode 0, but used for internal buffer flushes only.  Do not
+ *   involve the underlying OS layer in this type of flush - simply flush
+ *   our buffers with no separation.  
+ */
+
+/* flush a given output stream */
+static void outflushn_stream(out_stream_info *stream, int nl)
+{
+    int i;
+
+    /* null-terminate the current output line buffer */
+    stream->linebuf[stream->linepos] = '\0';
+
+    /* note the position of the last character to display */
+    i = stream->linepos - 1;
+
+    /* if we're adding anything, remove trailing spaces */
+    if (nl != 0 && nl != 4)
+    {
+        /* look for last non-space character */
+        for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ;
+    }
+
+    /* check the output mode */
+    if (nl == 3)
+    {
+        /* 
+         *   this is the special "preview" mode -- only display the part
+         *   that we haven't already previewed for this same line
+         */
+        if (i + 1 > stream->preview)
+        {
+            /* write out the line */
+            t_outline(stream, 0, &stream->linebuf[stream->preview],
+                      &stream->attrbuf[stream->preview]);
+
+            /* skip past the part we wrote */
+            stream->preview += strlen(&stream->linebuf[stream->preview]);
+        }
+    }
+    else
+    {
+        char *suffix = nullptr; /* extra text to add after the flushed text */
+        int   countnl = 0;         /* true if line counts for [more] paging */
+
+        /* null-terminate the buffer at the current position */
+        stream->linebuf[++i] = '\0';
+
+        /* check the mode */
+        switch(nl)
+        {
+        case 0:
+        case 3:
+        case 4:
+            /* no newline - just flush out what we have with no suffix */
+            suffix = 0;
+            break;
+
+        case 1:
+            /* 
+             *   Add a newline.  If there's nothing in the current line,
+             *   or we just wrote out a newline, do not add an extra
+             *   newline.  Keep all newlines in PRE mode.
+             */
+            if (stream->linecol != 0 || !stream->just_did_nl
+                || stream->html_pre_level != 0)
+            {
+                /* add a newline after the text */
+                suffix = "\n";
+                
+                /* count the line in the page size */
+                countnl = 1;
+            }
+            else
+            {
+                /* don't add a newline */
+                suffix = 0;
+            }
+            break;
+
+        case 2:
+            /* 
+             *   we're going to depend on the underlying OS output layer
+             *   to do line breaking, so don't add a newline, but do add a
+             *   space, so that the underlying OS layer knows we have a
+             *   word break here 
+             */
+            suffix = " ";
+            break;
+        }
+
+        /* 
+         *   display the line, as long as we have something buffered to
+         *   display; even if we don't, display it if our column is
+         *   non-zero and we didn't just do a newline, since this must
+         *   mean that we've flushed a partial line and are just now doing
+         *   the newline 
+         */
+        if (stream->linebuf[stream->preview] != '\0'
+            || (stream->linecol != 0 && !stream->just_did_nl)
+            || stream->html_pre_level > 0)
+        {
+            /* write it out */
+            t_outline(stream, countnl, &stream->linebuf[stream->preview],
+                      &stream->attrbuf[stream->preview]);
+
+            /* write the suffix, if any */
+            if (suffix != 0)
+                t_outline(stream, 0, suffix, 0);
+        }
+
+        /* generate an HTML line break if necessary */
+        if (nl == 1 && stream->html_mode && stream->html_target)
+            t_outline(stream, 0, "<BR HEIGHT=0>", 0);
+
+        if (nl == 0)
+        {
+            /* we're not displaying a newline, so flush what we have */
+            os_flush();
+        }
+        else
+        {
+            /* we displayed a newline, so reset the column position */
+            stream->linecol = 0;
+        }
+
+        /* reset the line output buffer position */
+        stream->linepos = stream->preview = 0;
+
+        /* 
+         *   If we just output a newline, note it.  If we didn't just
+         *   output a newline, but we did write out anything else, note
+         *   that we're no longer at the start of a line on the underlying
+         *   output device.  
+         */
+        if (nl == 1)
+            stream->just_did_nl = TRUE;
+        else if (stream->linebuf[stream->preview] != '\0')
+            stream->just_did_nl = FALSE;
+    }
+
+    /* 
+     *   If the osifc-level attributes don't match the current attributes,
+     *   bring the osifc layer up to date.  This is necessary in cases where
+     *   we set attributes immediately before asking for input - we
+     *   essentially need to flush the attributes without flushing any text.
+     */
+    if (stream->cur_attr != stream->os_attr)
+    {
+        /* set the osifc attributes */
+        os_set_text_attr(stream->cur_attr);
+
+        /* remember the new attributes as the current osifc attributes */
+        stream->os_attr = stream->cur_attr;
+    }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Determine if we're showing output.  Returns true if output should be
+ *   displayed, false if it should be suppressed.  We'll note the output
+ *   for hidden display accounting as needed. 
+ */
+static int out_is_hidden()
+{
+    /* check the output flag */
+    if (!outflag)
+    {
+        /* trace the hidden output if desired */
+        if (dbghid && !hidout)
+            trchid();
+
+        /* note the hidden output */
+        hidout = 1;
+
+        /* 
+         *   unless we're showing hidden text in the debugger, we're
+         *   suppressing output, so return true 
+         */
+        if (!dbghid)
+            return TRUE;
+    }
+
+    /* we're not suppressing output */
+    return FALSE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Display a blank line to the given stream 
+ */
+static void outblank_stream(out_stream_info *stream)
+{
+    /* flush the stream */
+    outflushn_stream(stream, 1);
+
+    /* if generating for an HTML display target, add an HTML line break */
+    if (stream->html_mode && stream->html_target)
+        outstring_stream(stream, "<BR>");
+
+    /* write out the newline */
+    t_outline(stream, 1, "\n", 0);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Generate a tab for a "\t" sequence in the game text.
+ *   
+ *   Standard (non-HTML) version: we'll generate enough spaces to take us
+ *   to the next tab stop.
+ *   
+ *   HTML version: if we're in native HTML mode, we'll just generate a
+ *   <TAB MULTIPLE=4>; if we're not in HTML mode, we'll generate a hard
+ *   tab character, which the HTML formatter will interpret as a <TAB
+ *   MULTIPLE=4>.  
+ */
+static void outtab_stream(out_stream_info *stream)
+{
+    /* check to see what the underlying system is expecting */
+    if (stream->html_target)
+    {
+        /* the underlying system is HTML - check for HTML mode */
+        if (stream->html_mode)
+        {
+            /* we're in HTML mode, so use the HTML <TAB> tag */
+            outstring_stream(stream, "<TAB MULTIPLE=4>");
+        }
+        else
+        {
+            /* we're not in HTML mode, so generate a hard tab character */
+            outchar_noxlat_stream(stream, QTAB);
+        }
+    }
+    else
+    {
+        int maxcol;
+
+        /* 
+         *   We're not in HTML mode - expand the tab with spaces.  Figure
+         *   the maximum column: if we're doing our own line wrapping, never
+         *   go beyond the actual display width. 
+         */
+        maxcol = (stream->os_line_wrap ? OS_MAXWIDTH : G_os_linewidth);
+
+        /* add the spaces */
+        do
+        {
+            stream->attrbuf[stream->linepos] = stream->cur_attr;
+            stream->linebuf[stream->linepos++] = ' ';
+            ++(stream->linecol);
+        } while (((stream->linecol + 1) & 3) != 0
+                 && stream->linecol < maxcol);
+    }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Flush a line 
+ */
+static void out_flushline(out_stream_info *stream, int padding)
+{
+    /* 
+     *   check to see if we're using the underlying display layer's line
+     *   wrapping 
+     */
+    if (stream->os_line_wrap)
+    {
+        /*
+         *   In the HTML version, we don't need the normal *MORE*
+         *   processing, since the HTML layer will handle that.
+         *   Furthermore, we don't need to provide actual newline breaks
+         *   -- that happens after the HTML is parsed, so we don't have
+         *   enough information here to figure out actual line breaks.
+         *   So, we'll just flush out our buffer whenever it fills up, and
+         *   suppress newlines.
+         *   
+         *   Similarly, if we have OS-level MORE processing, don't try to
+         *   figure out where the line breaks go -- just flush our buffer
+         *   without a trailing newline whenever the buffer is full, and
+         *   let the OS layer worry about formatting lines and paragraphs.
+         *   
+         *   If we're using padding, use mode 2.  If we don't want padding
+         *   (which is the case if we completely fill up the buffer
+         *   without finding any word breaks), write out in mode 0, which
+         *   just flushes the buffer exactly like it is.  
+         */
+        outflushn_stream(stream, padding ? 2 : 4);
+    }
+    else
+    {
+        /*
+         *   Normal mode - we process the *MORE* prompt ourselves, and we
+         *   are responsible for figuring out where the actual line breaks
+         *   go.  Use outflush() to generate an actual newline whenever we
+         *   flush out our buffer.  
+         */
+        outflushn_stream(stream, 1);
+    }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Write a character to an output stream without translation
+ */
+static void outchar_noxlat_stream(out_stream_info *stream, char c)
+{
+    int  i;
+    int  qspace;
+    
+    /* check for the special quoted space character */
+    if (c == QSPACE)
+    {
+        /* it's a quoted space - note it and convert it to a regular space */
+        qspace = 1;
+        c = ' ';
+    }
+    else if (c == QTAB)
+    {
+        /* it's a hard tab - convert it to an ordinary tab */
+        c = '\t';
+        qspace = 0;
+    }
+    else
+    {
+        /* translate any whitespace character to a regular space character */
+        if (outissp(c))
+            c = ' ';
+        
+        /* it's not a quoted space */
+        qspace = 0;
+    }
+
+    /* check for the caps/nocaps flags */
+    if ((stream->capsflag || stream->allcapsflag) && outisal(c))
+    {
+        /* capsflag is set, so capitalize this character */
+        if (outislo(c))
+            c = toupper(c);
+
+        /* okay, we've capitalized something; clear flag */
+        stream->capsflag = 0;
+    }
+    else if (stream->nocapsflag && outisal(c))
+    {
+        /* nocapsflag is set, so minisculize this character */
+        if (outisup(c))
+            c = tolower(c);
+
+        /* clear the flag now that we've done the job */
+        stream->nocapsflag = 0;
+    }
+
+    /* if in capture mode, simply capture the character */
+    if (stream->capturing)
+    {
+        uchar *p;
+
+        /* if we have a valid capture object, copy to it */
+        if (stream->capture_obj != MCMONINV)
+        {
+            /* lock the object holding the captured text */
+            p = mcmlck(stream->capture_ctx, stream->capture_obj);
+            
+            /* make sure the capture object is big enough */
+            if (mcmobjsiz(stream->capture_ctx, stream->capture_obj)
+                <= stream->capture_ofs)
+            {
+                /* expand the object by another 256 bytes */
+                p = mcmrealo(stream->capture_ctx, stream->capture_obj,
+                             (ushort)(stream->capture_ofs + 256));
+            }
+            
+            /* add this character */
+            *(p + stream->capture_ofs++) = c;
+            
+            /* unlock the capture object */
+            mcmtch(stream->capture_ctx, stream->capture_obj);
+            mcmunlck(stream->capture_ctx, stream->capture_obj);
+        }
+
+        /*
+         *   we're done - we don't want to actually display the character
+         *   while capturing 
+         */
+        return;
+    }
+
+    /* add the character to out output buffer, flushing as needed */
+    if (stream->linecol + 1 < G_os_linewidth)
+    {
+        /* 
+         *   there's room for this character, so add it to the buffer 
+         */
+        
+        /* ignore non-quoted space at start of line outside of PRE */
+        if (outissp(c) && c != '\t' && stream->linecol == 0 && !qspace
+            && stream->html_pre_level == 0)
+            return;
+
+        /* is this a non-quoted space not at the start of the line? */
+        if (outissp(c) && c != '\t' && stream->linecol != 0 && !qspace
+            && stream->html_pre_level == 0)
+        {
+            int  pos1 = stream->linepos - 1;
+            char p = stream->linebuf[pos1];     /* check previous character */
+
+            /* ignore repeated spaces - collapse into a single space */
+            if (outissp(p))
+                return;
+
+            /*
+             *   Certain punctuation requires a double space: a period, a
+             *   question mark, an exclamation mark, or a colon; or any of
+             *   these characters followed by any number of single and/or
+             *   double quotes.  First, scan back to before any quotes, if
+             *   are on one now, then check the preceding character; if
+             *   it's one of the punctuation marks requiring a double
+             *   space, add this space a second time.  (In addition to
+             *   scanning back past quotes, scan past parentheses,
+             *   brackets, and braces.)  Don't double the spacing if we're
+             *   not in the normal doublespace mode; some people may
+             *   prefer single spacing after punctuation, so we make this
+             *   a run-time option.  
+             */
+            if (doublespace)
+            {
+                /* find the previous relevant punctuation character */
+                while (pos1 &&
+                       (p == '"' || p == '\'' || p == ')' || p == ']'
+                        || p == '}'))
+                {
+                    p = stream->linebuf[--pos1];
+                }
+                if ( p == '.' || p == '?' || p == '!' || p == ':' )
+                {
+                    /* a double-space is required after this character */
+                    stream->attrbuf[stream->linepos] = stream->cur_attr;
+                    stream->linebuf[stream->linepos++] = c;
+                    ++(stream->linecol);
+                }
+            }
+        }
+
+        /* add this character to the buffer */
+        stream->attrbuf[stream->linepos] = stream->cur_attr;
+        stream->linebuf[stream->linepos++] = c;
+
+        /* advance the output column position */
+        ++(stream->linecol);
+        return;
+    }
+
+    /*
+     *   The line would overflow if this character were added.  Find the
+     *   most recent word break, and output the line up to the previous
+     *   word.  Note that if we're trying to output a space, we'll just
+     *   add it to the line buffer.  If the last character of the line
+     *   buffer is already a space, we won't do anything right now.  
+     */
+    if (outissp(c) && c != '\t' && !qspace)
+    {
+        /* this is a space, so we're at a word break */
+        if (stream->linebuf[stream->linepos - 1] != ' ')
+        {
+            stream->attrbuf[stream->linepos] = stream->cur_attr;
+            stream->linebuf[stream->linepos++] = ' ';
+        }
+        return;
+    }
+    
+    /*
+     *   Find the most recent word break: look for a space or dash, starting
+     *   at the end of the line.  
+     *   
+     *   If we're about to write a hyphen, we want to skip all contiguous
+     *   hyphens, because we want to keep them together as a single
+     *   punctuation mark; then keep going in the normal manner, which will
+     *   keep the hyphens plus the word they're attached to together as a
+     *   single unit.  If spaces precede the sequence of hyphens, include
+     *   the prior word as well.  
+     */
+    i = stream->linepos - 1;
+    if (c == '-')
+    {
+        /* skip any contiguous hyphens at the end of the line */
+        for ( ; i >= 0 && stream->linebuf[i] == '-' ; --i) ;
+        
+        /* skip any spaces preceding the sequence of hyphens */
+        for ( ; i >= 0 && outissp(stream->linebuf[i]) ; --i) ;
+    }
+
+    /* 
+     *   Now find the preceding space.  If we're doing our own wrapping
+     *   (i.e., we're not using OS line wrapping), then look for the
+     *   nearest hyphen as well. 
+     */
+    for ( ; i >= 0 && !outissp(stream->linebuf[i])
+          && !(!stream->os_line_wrap && stream->linebuf[i] == '-') ; --i) ;
+
+    /* check to see if we found a good place to break */
+    if (i < 0)
+    {
+        /* 
+         *   we didn't find any good place to break - flush the entire
+         *   line as-is, breaking arbitrarily in the middle of a word 
+         */
+        out_flushline(stream, FALSE);
+
+        /* 
+         *   we've completely cleared out the line buffer, so reset all of
+         *   the line buffer counters 
+         */
+        stream->linepos = 0;
+        stream->linecol = 0;
+        stream->linebuf[0] = '\0';
+    }
+    else
+    {
+        char brkchar;
+        char tmpbuf[MAXWIDTH];
+        int tmpattr[MAXWIDTH];
+        size_t tmpcnt;
+
+        /* remember the word-break character */        
+        brkchar = stream->linebuf[i];
+
+        /* null-terminate the line buffer */        
+        stream->linebuf[stream->linepos] = '\0';
+
+        /* the next line starts after the break - save a copy */
+        tmpcnt = strlen(&stream->linebuf[i+1]);
+        memcpy(tmpbuf, &stream->linebuf[i+1], tmpcnt + 1);
+        memcpy(tmpattr, &stream->attrbuf[i+1], tmpcnt * sizeof(tmpattr[0]));
+
+        /* 
+         *   terminate the buffer at the space or after the hyphen,
+         *   depending on where we broke 
+         */
+        if (outissp(brkchar))
+            stream->linebuf[i] = '\0';
+        else
+            stream->linebuf[i+1] = '\0';
+
+        /* write out everything up to the word break */
+        out_flushline(stream, TRUE);
+
+        /* copy the next line into line buffer */
+        memcpy(stream->linebuf, tmpbuf, tmpcnt + 1);
+        memcpy(stream->attrbuf, tmpattr, tmpcnt * sizeof(tmpattr[0]));
+        stream->linepos = tmpcnt;
+
+        /* 
+         *   figure what column we're now in - count all of the printable
+         *   characters in the new line 
+         */
+        for (stream->linecol = 0, i = 0 ; i < stream->linepos ; ++i)
+        {
+            /* if it's printable, count it */
+            if (((unsigned char)stream->linebuf[i]) >= 26)
+                ++(stream->linecol);
+        }
+    }
+    
+    /* add the new character to buffer */
+    stream->attrbuf[stream->linepos] = stream->cur_attr;
+    stream->linebuf[stream->linepos++] = c;
+
+    /* advance the column counter */
+    ++(stream->linecol);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Write out a character, translating to the local system character set
+ *   from the game's internal character set. 
+ */
+static void outchar_stream(out_stream_info *stream, char c)
+{
+    outchar_noxlat_stream(stream, cmap_i2n(c));
+}
+
+/* 
+ *   write out a string, translating to the local system character set 
+ */
+static void outstring_stream(out_stream_info *stream, char *s)
+{
+    /* write out each character in the string */
+    for ( ; *s ; ++s)
+        outchar_stream(stream, *s);
+}
+
+/* 
+ *   write out a string without translation 
+ */
+static void outstring_noxlat_stream(out_stream_info *stream, char *s)
+{
+    for ( ; *s ; ++s)
+        outchar_noxlat_stream(stream, *s);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Write out an HTML character value, translating to the local character
+ *   set.  
+ */
+static void outchar_html_stream(out_stream_info *stream,
+                                unsigned int htmlchar)
+{
+    amp_tbl_t *ampptr;
+
+    /* 
+     *   search for a mapping entry for this entity, in case it's defined
+     *   in an external mapping file 
+     */
+    for (ampptr = amp_tbl ;
+         ampptr < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++ampptr)
+    {
+        /* if this is the one, stop looking */
+        if (ampptr->html_cval == htmlchar)
+            break;
+    }
+
+    /* 
+     *   If we found a mapping table entry, and the entry has an expansion
+     *   from the external character mapping table file, use the external
+     *   expansion; otherwise, use the default expansion.  
+     */
+    if (ampptr >= amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0])
+        || ampptr->expan == 0)
+    {
+        char xlat_buf[50];
+
+        /* 
+         *   there's no external mapping table file expansion -- use the
+         *   default OS mapping routine 
+         */
+        os_xlat_html4(htmlchar, xlat_buf, sizeof(xlat_buf));
+        outstring_noxlat_stream(stream, xlat_buf);
+    }
+    else
+    {
+        /* 
+         *   use the explicit mapping from the mapping table file 
+         */
+        outstring_noxlat_stream(stream, ampptr->expan);
+    }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Enter a recursion level.  Returns TRUE if the caller should proceed
+ *   with the operation, FALSE if not.
+ *   
+ *   If we're making a recursive call, thereby re-entering the formatter,
+ *   and this stream is not the same as the enclosing stream, we want to
+ *   ignore this call and suppress any output to this stream, so we'll
+ *   return FALSE.  
+ */
+static int out_push_stream(out_stream_info *stream)
+{
+    /* 
+     *   if we're already in the formatter, and the new stream doesn't
+     *   match the enclosing recursion level's stream, tell the caller to
+     *   abort the operation 
+     */
+    if (G_recurse != 0 && G_cur_stream != stream)
+        return FALSE;
+    
+    /* note the active stream */
+    G_cur_stream = stream;
+
+    /* count the entry */
+    ++G_recurse;
+
+    /* tell the caller to proceed */
+    return TRUE;
+}
+
+/*
+ *   Leave a recursion level 
+ */
+static void out_pop_stream()
+{
+    /* count the exit */
+    --G_recurse;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   nextout() returns the next character in a string, and updates the
+ *   string pointer and remaining length.  Returns zero if no more
+ *   characters are available in the string.  
+ */
+/* static char nextout(char **s, uint *len); */
+#define nextout(s, len) ((char)(*(len) == 0 ? 0 : (--(*(len)), *((*(s))++))))
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Get the next character, writing the previous character to the given
+ *   output stream if it's not null. 
+ */
+static char nextout_copy(char **s, size_t *slen,
+                         char prv, out_stream_info *stream)
+{
+    /* if there's a stream, write the previous character to the stream */
+    if (stream != 0)
+        outchar_stream(stream, prv);
+
+    /* return the next character */
+    return nextout(s, slen);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Read an HTML tag, for our primitive mini-parser.  If 'stream' is not
+ *   null, we'll copy each character we read to the output stream.  Returns
+ *   the next character after the tag name.
+ */
+static char read_tag(char *dst, size_t dstlen, int *is_end_tag,
+                     char **s, size_t *slen, out_stream_info *stream)
+{
+    char c;
+    
+    /* skip the opening '<' */
+    c = nextout_copy(s, slen, '<', stream);
+
+    /* skip spaces */
+    while (outissp(c))
+        c = nextout_copy(s, slen, c, stream);
+
+    /* note if this is a closing tag */
+    if (c == '/' || c == '\\')
+    {
+        /* it's an end tag - note it and skip the slash */
+        *is_end_tag = TRUE;
+        c = nextout_copy(s, slen, c, stream);
+
+        /* skip yet more spaces */
+        while (outissp(c))
+            c = nextout_copy(s, slen, c, stream);
+    }
+    else
+        *is_end_tag = FALSE;
+    
+    /* 
+     *   find the end of the tag name - the tag continues to the next space,
+     *   '>', or end of line 
+     */
+    for ( ; c != '\0' && !outissp(c) && c != '>' ;
+         c = nextout_copy(s, slen, c, stream))
+    {
+        /* add this to the tag buffer if it fits */
+        if (dstlen > 1)
+        {
+            *dst++ = c;
+            --dstlen;
+        }
+    }
+    
+    /* null-terminate the tag name */
+    if (dstlen > 0)
+        *dst = '\0';
+
+    /* return the next character */
+    return c;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   display a string of a given length to a given stream 
+ */
+static int outformatlen_stream(out_stream_info *stream,
+                               char *s, size_t slen)
+{
+    char     c;
+    int      done = 0;
+    char     fmsbuf[40];       /* space for constructing translation string */
+    uint     fmslen;
+    char    *f = 0;
+    char    *f1;
+    int      infmt = 0;
+
+    /* 
+     *   This routine can recurse because of format strings ("%xxx%"
+     *   sequences).  When we recurse, we want to ensure that the
+     *   recursion is directed to the original stream only.  So, note the
+     *   current stream statically in case we re-enter the formatter. 
+     */
+    if (!out_push_stream(stream))
+        return 0;
+
+    /* get the first character */
+    c = nextout(&s, &slen);
+
+    /* if we have anything to show, show it */
+    while (c != '\0')
+    {
+        /* check if we're collecting translation string */
+        if (infmt)
+        {
+            /*
+             *   if the string is too long for our buffer, or we've come
+             *   across a backslash (illegal in a format string), or we've
+             *   come across an HTML-significant character ('&' or '<') in
+             *   HTML mode, we must have a stray percent sign; dump the
+             *   whole string so far and act as though we have no format
+             *   string 
+             */
+            if (c == '\\'
+                || f == &fmsbuf[sizeof(fmsbuf)]
+                || (stream->html_mode && (c == '<' || c == '&')))
+            {
+                outchar_stream(stream, '%');
+                for (f1 = fmsbuf ; f1 < f ; ++f1)
+                    outchar_stream(stream, *f1);
+                infmt = 0;
+
+                /* process this character again */
+                continue;
+            }
+            else if (c == '%' && f == fmsbuf)       /* double percent sign? */
+            {
+                outchar_stream(stream, '%');       /* send out a single '%' */
+                infmt = 0;       /* no longer processing translation string */
+            }
+            else if (c == '%')   /* found end of string? translate it if so */
+            {
+                uchar *fms;
+                int    initcap = FALSE;
+                int    allcaps = FALSE;
+                char   fmsbuf_srch[sizeof(fmsbuf)];
+
+                /* null-terminate the string */
+                *f = '\0';
+
+                /* check for an init cap */
+                if (outisup(fmsbuf[0]))
+                {
+                    /* 
+                     *   note the initial capital, so that we follow the
+                     *   original capitalization in the substituted string 
+                     */
+                    initcap = TRUE;
+
+                    /* 
+                     *   if the second letter is capitalized as well,
+                     *   capitalize the entire substituted string 
+                     */
+                    if (fmsbuf[1] != '\0' && outisup(fmsbuf[1]))
+                    {
+                        /* use all caps */
+                        allcaps = TRUE;
+                    }
+                }
+
+                /* convert the entire string to lower case for searching */
+                strcpy(fmsbuf_srch, fmsbuf);
+                os_strlwr(fmsbuf_srch);
+                
+                /* find the string in the format string table */
+                fmslen = strlen(fmsbuf_srch);
+                for (fms = fmsbase ; fms < fmstop ; )
+                {
+                    uint propnum;
+                    uint len;
+
+                    /* get the information on this entry */
+                    propnum = osrp2(fms);
+                    len = osrp2(fms + 2) - 2;
+
+                    /* check for a match */
+                    if (len == fmslen &&
+                        !memcmp(fms + 4, fmsbuf_srch, (size_t)len))
+                    {
+                        int old_all_caps;
+
+                        /* note the current ALLCAPS mode */
+                        old_all_caps = stream->allcapsflag;
+                        
+                        /* 
+                         *   we have a match - set the appropriate
+                         *   capitalization mode 
+                         */
+                        if (allcaps)
+                            outallcaps_stream(stream, TRUE);
+                        else if (initcap)
+                            outcaps_stream(stream);
+
+                        /* 
+                         *   evaluate the associated property to generate
+                         *   the substitution text 
+                         */
+                        runppr(runctx, cmdActor, (prpnum)propnum, 0);
+
+                        /* turn off ALLCAPS mode */
+                        outallcaps_stream(stream, old_all_caps);
+
+                        /* no need to look any further */
+                        break;
+                    }
+
+                    /* move on to next formatstring if not yet found */
+                    fms += len + 4;
+                }
+
+                /* if we can't find it, dump the format string as-is */
+                if (fms == fmstop)
+                {
+                    outchar_stream(stream, '%');
+                    for (f1 = fmsbuf ; f1 < f ; ++f1)
+                        outchar_stream(stream, *f1);
+                    outchar_stream(stream, '%');
+                }
+
+                /* no longer reading format string */
+                infmt = 0;
+            }
+            else
+            {
+                /* copy this character of the format string */
+                *f++ = c;
+            }
+
+            /* move on to the next character and continue scanning */
+            c = nextout(&s, &slen);
+            continue;
+        }
+        
+        /*
+         *   If we're parsing HTML here, and we're inside a tag, skip
+         *   characters until we reach the end of the tag.  
+         */
+        if (stream->html_mode_flag != HTML_MODE_NORMAL)
+        {
+            switch(stream->html_mode_flag)
+            {
+            case HTML_MODE_TAG:
+                /* 
+                 *   keep skipping up to the closing '>', but note when we
+                 *   enter any quoted section 
+                 */
+                switch(c)
+                {
+                case '>':
+                    /* we've reached the end of the tag */
+                    stream->html_mode_flag = HTML_MODE_NORMAL;
+
+                    /* if we have a deferred <BR>, process it now */
+                    switch(stream->html_defer_br)
+                    {
+                    case HTML_DEFER_BR_NONE:
+                        /* no deferred <BR> */
+                        break;
+
+                    case HTML_DEFER_BR_FLUSH:
+                        outflushn_stream(stream, 1);
+                        break;
+
+                    case HTML_DEFER_BR_BLANK:
+                        outblank_stream(stream);
+                        break;
+                    }
+
+                    /* no more deferred <BR> pending */
+                    stream->html_defer_br = HTML_DEFER_BR_NONE;
+
+                    /* no more ALT attribute allowed */
+                    stream->html_allow_alt = FALSE;
+                    break;
+
+                case '"':
+                    /* enter a double-quoted string */
+                    stream->html_mode_flag = HTML_MODE_DQUOTE;
+                    break;
+
+                case '\'':
+                    /* enter a single-quoted string */
+                    stream->html_mode_flag = HTML_MODE_SQUOTE;
+                    break;
+
+                default:
+                    /* if it's alphabetic, note the attribute name */
+                    if (outisal(c))
+                    {
+                        char attrname[128];
+                        char attrval[256];
+                        char *dst;
+
+                        /* gather up the attribute name */
+                        for (dst = attrname ;
+                             dst + 1 < attrname + sizeof(attrname) ; )
+                        {
+                            /* store this character */
+                            *dst++ = c;
+
+                            /* get the next character */
+                            c = nextout(&s, &slen);
+
+                            /* if it's not alphanumeric, stop scanning */
+                            if (!outisal(c) && !outisdg(c))
+                                break;
+                        }
+
+                        /* null-terminate the result */
+                        *dst++ = '\0';
+
+                        /* gather the value if present */
+                        if (c == '=')
+                        {
+                            char qu;
+                            
+                            /* skip the '=' */
+                            c = nextout(&s, &slen);
+
+                            /* if we have a quote, so note */
+                            if (c == '"' || c == '\'')
+                            {
+                                /* remember the quote */
+                                qu = c;
+
+                                /* skip it */
+                                c = nextout(&s, &slen);
+                            }
+                            else
+                            {
+                                /* no quote */
+                                qu = 0;
+                            }
+
+                            /* read the value */
+                            for (dst = attrval ;
+                                 dst + 1 < attrval + sizeof(attrval) ; )
+                            {
+                                /* store this character */
+                                *dst++ = c;
+
+                                /* read the next one */
+                                c = nextout(&s, &slen);
+                                if (c == '\0')
+                                {
+                                    /* 
+                                     *   we've reached the end of the
+                                     *   string, and we're still inside
+                                     *   this attribute - abandon the
+                                     *   attribute but note that we're
+                                     *   inside a quoted string if
+                                     *   necessary 
+                                     */
+                                    if (qu == '"')
+                                        stream->html_mode_flag =
+                                            HTML_MODE_DQUOTE;
+                                    else if (qu == '\'')
+                                        stream->html_mode_flag =
+                                            HTML_MODE_SQUOTE;
+                                    else
+                                        stream->html_mode_flag
+                                            = HTML_MODE_TAG;
+
+                                    /* stop scanning the string */
+                                    break;
+                                }
+
+                                /* 
+                                 *   if we're looking for a quote, check
+                                 *   for the closing quote; otherwise,
+                                 *   check for alphanumerics 
+                                 */
+                                if (qu != 0)
+                                {
+                                    /* if this is our quote, stop scanning */
+                                    if (c == qu)
+                                        break;
+                                }
+                                else
+                                {
+                                    /* if it's non-alphanumeric, we're done */
+                                    if (!outisal(c) && !outisdg(c))
+                                        break;
+                                }
+                            }
+
+                            /* skip the closing quote, if necessary */
+                            if (qu != 0 && c == qu)
+                                c = nextout(&s, &slen);
+
+                            /* null-terminate the value string */
+                            *dst = '\0';
+                        }
+                        else
+                        {
+                            /* no value */
+                            attrval[0] = '\0';
+                        }
+
+                        /* 
+                         *   see if we recognize it, and it's meaningful
+                         *   in the context of the current tag 
+                         */
+                        if (!scumm_stricmp(attrname, "height")
+                            && stream->html_defer_br != HTML_DEFER_BR_NONE)
+                        {
+                            int ht;
+                                
+                            /*
+                             *   If the height is zero, always treat this
+                             *   as a non-blanking flush.  If it's one,
+                             *   treat it as we originally planned to.  If
+                             *   it's greater than one, add n blank lines.
+                             */
+                            ht = atoi(attrval);
+                            if (ht == 0)
+                            {
+                                /* always use non-blanking flush */
+                                stream->html_defer_br = HTML_DEFER_BR_FLUSH;
+                            }
+                            else if (ht == 1)
+                            {
+                                /* keep original setting */
+                            }
+                            else
+                            {
+                                for ( ; ht > 0 ; --ht)
+                                    outblank_stream(stream);
+                            }
+                        }
+                        else if (!scumm_stricmp(attrname, "alt")
+                                 && !stream->html_in_ignore
+                                 && stream->html_allow_alt)
+                        {
+                            /* write out the ALT string */
+                            outstring_stream(stream, attrval);
+                        }
+
+                        /* 
+                         *   since we already read the next character,
+                         *   simply loop back immediately 
+                         */
+                        continue;
+                    }
+                    break;
+                }
+                break;
+
+            case HTML_MODE_DQUOTE:
+                /* if we've reached the closing quote, return to tag state */
+                if (c == '"')
+                    stream->html_mode_flag = HTML_MODE_TAG;
+                break;
+
+            case HTML_MODE_SQUOTE:
+                /* if we've reached the closing quote, return to tag state */
+                if (c == '\'')
+                    stream->html_mode_flag = HTML_MODE_TAG;
+                break;
+            }
+
+            /* 
+             *   move on to the next character, and start over with the
+             *   new character 
+             */
+            c = nextout(&s, &slen);
+            continue;
+        }
+
+        /*
+         *   If we're in a title, and this isn't the start of a new tag,
+         *   skip the character - we suppress all regular text output
+         *   inside a <TITLE> ... </TITLE> sequence. 
+         */
+        if (stream->html_in_ignore && c != '<')
+        {
+            /* check for entities */
+            char cbuf[50];
+            if (c == '&')
+            {
+                /* translate the entity */
+                c = out_parse_entity(cbuf, sizeof(cbuf), &s, &slen);
+            }
+            else
+            {
+                /* it's an ordinary character - copy it out literally */
+                cbuf[0] = c;
+                cbuf[1] = '\0';
+
+                /* get the next character */
+                c = nextout(&s, &slen);
+            }
+
+            /* 
+             *   if we're gathering a title, and there's room in the title
+             *   buffer for more (always leaving room for a null
+             *   terminator), add this to the title buffer 
+             */
+            if (stream->html_in_title)
+            {
+                char *cbp;
+                for (cbp = cbuf ; *cbp != '\0' ; ++cbp)
+                {
+                    /* if there's room, add it */
+                    if (stream->html_title_ptr + 1 <
+                        stream->html_title_buf
+                        + sizeof(stream->html_title_buf))
+                        *stream->html_title_ptr++ = *cbp;
+                }
+            }
+
+            /* don't display anything in an ignore section */
+            continue;
+        }
+        
+        if ( c == '%' )                              /* translation string? */
+        {
+            infmt = 1;
+            f = fmsbuf;
+        }
+        else if ( c == '\\' )                       /* special escape code? */
+        {
+            c = nextout(&s, &slen);
+            
+            if (stream->capturing && c != '^' && c != 'v' && c != '\0')
+            {
+                outchar_stream(stream, '\\');
+                outchar_stream(stream, c);
+
+                /* keep the \- and also put out the next two chars */
+                if (c == '-')
+                {
+                    outchar_stream(stream, nextout(&s, &slen));
+                    outchar_stream(stream, nextout(&s, &slen));
+                }
+            }
+            else
+            {
+                switch(c)
+                {
+                case 'H':                                /* HTML mode entry */
+                    /* turn on HTML mode in the renderer */
+                    switch(c = nextout(&s, &slen))
+                    {
+                    case '-':
+                        /* if we have an HTML target, notify it */
+                        if (stream->html_target)
+                        {
+                            /* flush its stream */
+                            outflushn_stream(stream, 0);
+
+                            /* tell the OS layer to switch to normal mode */
+                            out_end_html(stream);
+                        }
+
+                        /* switch to normal mode */
+                        stream->html_mode = FALSE;
+                        break;
+
+                    case '+':
+                    default:
+                        /* if we have an HTML target, notify it */
+                        if (stream->html_target)
+                        {
+                            /* flush the underlying stream */
+                            outflushn_stream(stream, 0);
+
+                            /* tell the OS layer to switch to HTML mode */
+                            out_start_html(stream);
+                        }
+                        
+                        /* switch to HTML mode */
+                        stream->html_mode = TRUE;
+
+                        /* 
+                         *   if the character wasn't a "+", it's not part
+                         *   of the "\H" sequence, so display it normally 
+                         */
+                        if (c != '+' && c != 0)
+                            outchar_stream(stream, c);
+                        break;
+                    }
+
+                    /* this sequence doesn't result in any actual output */
+                    break;
+
+                case 'n':                                       /* newline? */
+                    outflushn_stream(stream, 1);        /* yes, output line */
+                    break;
+                    
+                case 't':                                           /* tab? */
+                    outtab_stream(stream);
+                    break;
+                    
+                case 'b':                                    /* blank line? */
+                    outblank_stream(stream);
+                    break;
+                    
+                case '\0':                               /* line ends here? */
+                    done = 1;
+                    break;
+
+                case ' ':                                   /* quoted space */
+                    if (stream->html_target && stream->html_mode)
+                    {
+                        /* 
+                         *   we're generating for an HTML target and we're
+                         *   in HTML mode - generate the HTML non-breaking
+                         *   space 
+                         */
+                        outstring_stream(stream, " ");
+                    }
+                    else
+                    {
+                        /* 
+                         *   we're not in HTML mode - generate our
+                         *   internal quoted space character 
+                         */
+                        outchar_stream(stream, QSPACE);
+                    }
+                    break;
+
+                case '^':                      /* capitalize next character */
+                    stream->capsflag = 1;
+                    stream->nocapsflag = 0;
+                    break;
+
+                case 'v':
+                    stream->nocapsflag = 1;
+                    stream->capsflag = 0;
+                    break;
+
+                case '(':
+                    /* generate HTML if in the appropriate mode */
+                    if (stream->html_mode && stream->html_target)
+                    {
+                        /* send HTML to the renderer */
+                        outstring_stream(stream, "<B>");
+                    }
+                    else
+                    {
+                        /* turn on the 'hilite' attribute */
+                        stream->cur_attr |= OS_ATTR_HILITE;
+                    }
+                    break;
+
+                case ')':
+                    /* generate HTML if in the appropriate mode */
+                    if (stream->html_mode && stream->html_target)
+                    {
+                        /* send HTML to the renderer */
+                        outstring_stream(stream, "</B>");
+                    }
+                    else
+                    {
+                        /* turn off the 'hilite' attribute */
+                        stream->cur_attr &= ~OS_ATTR_HILITE;
+                    }
+                    break;
+
+                case '-':
+                    outchar_stream(stream, nextout(&s, &slen));
+                    outchar_stream(stream, nextout(&s, &slen));
+                    break;
+                    
+                default:                 /* just pass invalid escapes as-is */
+                    outchar_stream(stream, c);
+                    break;
+                }
+            }
+        }
+        else if (!stream->html_target
+                 && stream->html_mode
+                 && (c == '<' || c == '&'))
+        {
+            /*
+             *   We're in HTML mode, but the underlying target does not
+             *   accept HTML sequences.  It appears we're at the start of
+             *   an "&" entity or a tag sequence, so parse it, remove it,
+             *   and replace it (if possible) with a text-only equivalent. 
+             */
+            if (c == '<')
+            {
+                /* read the tag */
+                char tagbuf[50];
+                int is_end_tag;
+                c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag,
+                             &s, &slen, 0);
+
+                /*
+                 *   Check to see if we recognize the tag.  We only
+                 *   recognize a few simple tags that map easily to
+                 *   character mode. 
+                 */
+                if (!scumm_stricmp(tagbuf, "br"))
+                {
+                    /* 
+                     *   line break - if there's anything buffered up,
+                     *   just flush the current line, otherwise write out
+                     *   a blank line 
+                     */
+                    if (stream->html_in_ignore)
+                        /* suppress breaks in ignore mode */;
+                    else if (stream->linepos != 0)
+                        stream->html_defer_br = HTML_DEFER_BR_FLUSH;
+                    else
+                        stream->html_defer_br = HTML_DEFER_BR_BLANK;
+                }
+                else if (!scumm_stricmp(tagbuf, "b")
+                         || !scumm_stricmp(tagbuf, "i")
+                         || !scumm_stricmp(tagbuf, "em")
+                         || !scumm_stricmp(tagbuf, "strong"))
+                {
+                    int attr;
+                    
+                    /* choose the attribute flag */
+                    switch (tagbuf[0])
+                    {
+                    case 'b':
+                    case 'B':
+                        attr = OS_ATTR_BOLD;
+                        break;
+
+                    case 'i':
+                    case 'I':
+                        attr = OS_ATTR_ITALIC;
+                        break;
+
+                    case 'e':
+                    case 'E':
+                        attr = OS_ATTR_EM;
+                        break;
+
+                    case 's':
+                    case 'S':
+                        attr = OS_ATTR_STRONG;
+                        break;
+                    }
+                    
+                    /* bold on/off - send out appropriate os-layer code */
+                    if (stream->html_in_ignore)
+                    {
+                        /* suppress any change in 'ignore' mode */
+                    }
+                    else if (!is_end_tag)
+                    {
+                        /* turn on the selected attribute */
+                        stream->cur_attr |= attr;
+                    }
+                    else
+                    {
+                        /* turn off the selected attribute */
+                        stream->cur_attr &= ~attr;
+                    }
+                }
+                else if (!scumm_stricmp(tagbuf, "p"))
+                {
+                    /* paragraph - send out a blank line */
+                    if (!stream->html_in_ignore)
+                        outblank_stream(stream);
+                }
+                else if (!scumm_stricmp(tagbuf, "tab"))
+                {
+                    /* tab - send out a \t */
+                    if (!stream->html_in_ignore)
+                        outtab_stream(stream);
+                }
+                else if (!scumm_stricmp(tagbuf, "img") || !scumm_stricmp(tagbuf, "sound"))
+                {
+                    /* IMG and SOUND - allow ALT attributes */
+                    stream->html_allow_alt = TRUE;
+                }
+                else if (!scumm_stricmp(tagbuf, "hr"))
+                {
+                    int rem;
+                    
+                    if (!stream->html_in_ignore)
+                    {
+                        /* start a new line */
+                        outflushn_stream(stream, 1);
+
+                        /* write out underscores to the display width */
+                        for (rem = G_os_linewidth - 1 ; rem > 0 ; )
+                        {
+                            char dashbuf[100];
+                            int cur;
+                            
+                            /* do as much as we can on this pass */
+                            cur = rem;
+                            if ((size_t)cur > sizeof(dashbuf) - 1)
+                                cur = sizeof(dashbuf) - 1;
+                            
+                            /* do a buffer-full of dashes */
+                            memset(dashbuf, '_', cur);
+                            dashbuf[cur] = '\0';
+                            outstring_stream(stream, dashbuf);
+                            
+                            /* deduct this from the total */
+                            rem -= cur;
+                        }
+
+                        /* put a blank line after the underscores */
+                        outblank_stream(stream);
+                    }
+                }
+                else if (!scumm_stricmp(tagbuf, "q"))
+                {
+                    unsigned int htmlchar;
+
+                    if (!stream->html_in_ignore)
+                    {
+                        /* if it's an open quote, increment the level */
+                        if (!is_end_tag)
+                            ++(stream->html_quote_level);
+                        
+                        /* add the open quote */
+                        htmlchar =
+                            (!is_end_tag
+                             ? ((stream->html_quote_level & 1) == 1
+                                ? 8220 : 8216)
+                             : ((stream->html_quote_level & 1) == 1
+                                ? 8221 : 8217));
+                        
+                        /* 
+                         *   write out the HTML character, translated to
+                         *   the local character set 
+                         */
+                        outchar_html_stream(stream, htmlchar);
+                        
+                        /* if it's a close quote, decrement the level */
+                        if (is_end_tag)
+                            --(stream->html_quote_level);
+                    }
+                }
+                else if (!scumm_stricmp(tagbuf, "title"))
+                {
+                    /* 
+                     *   Turn ignore mode on or off as appropriate, and
+                     *   turn on or off title mode as well.
+                     */
+                    if (is_end_tag)
+                    {
+                        /* 
+                         *   note that we're leaving an ignore section and
+                         *   a title section 
+                         */
+                        --(stream->html_in_ignore);
+                        --(stream->html_in_title);
+
+                        /* 
+                         *   if we're no longer in a title, call the OS
+                         *   layer to tell it the title string, in case it
+                         *   wants to change the window title or otherwise
+                         *   make use of the title 
+                         */
+                        if (stream->html_in_title == 0)
+                        {
+                            /* null-terminate the title string */
+                            *stream->html_title_ptr = '\0';
+                            
+                            /* tell the OS about the title */
+                            os_set_title(stream->html_title_buf);
+                        }
+                    }
+                    else
+                    {
+                        /* 
+                         *   if we aren't already in a title, set up to
+                         *   capture the title into the title buffer 
+                         */
+                        if (!stream->html_in_title)
+                            stream->html_title_ptr = stream->html_title_buf;
+
+                        /* 
+                         *   note that we're in a title and in an ignore
+                         *   section, since nothing within gets displayed 
+                         */
+                        ++(stream->html_in_ignore);
+                        ++(stream->html_in_title);
+                    }
+                }
+                else if (!scumm_stricmp(tagbuf, "aboutbox"))
+                {
+                    /* turn ignore mode on or off as appropriate */
+                    if (is_end_tag)
+                        --(stream->html_in_ignore);
+                    else
+                        ++(stream->html_in_ignore);
+                }
+                else if (!scumm_stricmp(tagbuf, "pre"))
+                {
+                    /* count the nesting level if starting PRE mode */
+                    if (!is_end_tag)
+                        stream->html_pre_level += 1;
+
+                    /* surround the PRE block with line breaks */
+                    outblank_stream(stream);
+
+                    /* count the nesting level if ending PRE mode */
+                    if (is_end_tag && stream->html_pre_level != 0)
+                        stream->html_pre_level -= 1;
+                }
+
+                /* suppress everything up to the next '>' */
+                stream->html_mode_flag = HTML_MODE_TAG;
+
+                /* 
+                 *   continue with the current character; since we're in
+                 *   html tag mode, we'll skip everything until we get to
+                 *   the closing '>' 
+                 */
+                continue;
+            }
+            else if (c == '&')
+            {
+                /* parse it */
+                char  xlat_buf[50];
+                c = out_parse_entity(xlat_buf, sizeof(xlat_buf), &s, &slen);
+
+                /* write it out (we've already translated it) */
+                outstring_noxlat_stream(stream, xlat_buf);
+                
+                /* proceed with the next character */
+                continue;
+            }
+        }
+        else if (stream->html_target && stream->html_mode && c == '<')
+        {
+            /*
+             *   We're in HTML mode, and we have an underlying HTML target.
+             *   We don't need to do much HTML interpretation at this level.
+             *   However, we do need to keep track of when we're in a PRE
+             *   block, so that we can pass whitespaces and newlines through
+             *   to the underlying HTML engine without filtering when we're
+             *   in preformatted text. 
+             */
+            char tagbuf[50];
+            int is_end_tag;
+            c = read_tag(tagbuf, sizeof(tagbuf), &is_end_tag,
+                         &s, &slen, stream);
+
+            /* check for special tags */
+            if (!scumm_stricmp(tagbuf, "pre"))
+            {
+                /* count the nesting level */
+                if (!is_end_tag)
+                    stream->html_pre_level += 1;
+                else if (is_end_tag && stream->html_pre_level != 0)
+                    stream->html_pre_level -= 1;
+            }
+
+            /* copy the last character after the tag to the stream */
+            outchar_stream(stream, c);
+        }
+        else
+        {
+            /* normal character */
+            outchar_stream(stream, c);
+        }
+
+        /* move on to the next character, unless we're finished */
+        if (done)
+            c = '\0';
+        else
+            c = nextout(&s, &slen);
+    }
+
+    /* if we ended up inside what looked like a format string, dump string */
+    if (infmt)
+    {
+        outchar_stream(stream, '%');
+        for (f1 = fmsbuf ; f1 < f ; ++f1)
+            outchar_stream(stream, *f1);
+    }
+
+    /* exit a recursion level */
+    out_pop_stream();
+
+    /* success */
+    return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Parse an HTML entity markup 
+ */
+static char out_parse_entity(char *outbuf, size_t outbuf_size,
+                             char **sp, size_t *slenp)
+{
+    char  ampbuf[10];
+    char *dst;
+    char *orig_s;
+    size_t orig_slen;
+    const amp_tbl_t *ampptr;
+    size_t lo, hi, cur;
+    char  c;
+
+    /* 
+     *   remember where the part after the '&' begins, so we can come back
+     *   here later if necessary 
+     */
+    orig_s = *sp;
+    orig_slen = *slenp;
+
+    /* get the character after the ampersand */
+    c = nextout(sp, slenp);
+
+    /* if it's numeric, parse the number */
+    if (c == '#')
+    {
+        uint val;
+        
+        /* skip the '#' */
+        c = nextout(sp, slenp);
+
+        /* check for hex */
+        if (c == 'x' || c == 'X')
+        {
+            /* skip the 'x' */
+            c = nextout(sp, slenp);
+
+            /* read the hex number */
+            for (val = 0 ; Common::isXDigit((uchar)c) ; c = nextout(sp, slenp))
+            {
+                /* accumulate the current digit into the value */
+                val *= 16;
+                if (outisdg(c))
+                    val += c - '0';
+                else if (c >= 'a' && c <= 'f')
+                    val += c - 'a' + 10;
+                else
+                    val += c - 'A' + 10;
+            }
+        }
+        else
+        {
+            /* read the number */
+            for (val = 0 ; outisdg(c) ; c = nextout(sp, slenp))
+            {
+                /* accumulate the current digit into the value */
+                val *= 10;
+                val += c - '0';
+            }
+        }
+        
+        /* if we found a ';' at the end, skip it */
+        if (c == ';')
+            c = nextout(sp, slenp);
+        
+        /* translate the character into the output buffer */
+        os_xlat_html4(val, outbuf, outbuf_size);
+        
+        /* we're done with this character */
+        return c;
+    }
+
+    /*
+     *   Parse the sequence after the '&'.  Parse up to the closing
+     *   semicolon, or any non-alphanumeric, or until we fill up the buffer.
+     */
+    for (dst = ampbuf ;
+         c != '\0' && (outisdg(c) || outisal(c))
+             && dst < ampbuf + sizeof(ampbuf) - 1 ;
+         *dst++ = c, c = nextout(sp, slenp)) ;
+    
+    /* null-terminate the name */
+    *dst = '\0';
+    
+    /* do a binary search for the name */
+    lo = 0;
+    hi = sizeof(amp_tbl)/sizeof(amp_tbl[0]) - 1;
+    for (;;)
+    {
+        int diff;
+        
+        /* if we've converged, look no further */
+        if (lo > hi || lo >= sizeof(amp_tbl)/sizeof(amp_tbl[0]))
+        {
+            ampptr = 0;
+            break;
+        }
+        
+        /* split the difference */
+        cur = lo + (hi - lo)/2;
+        ampptr = &amp_tbl[cur];
+        
+        /* see where we are relative to the target item */
+        diff = strcmp(ampptr->cname, ampbuf);
+        if (diff == 0)
+        {
+            /* this is it */
+            break;
+        }
+        else if (diff > 0)
+        {
+            /* make sure we don't go off the end */
+            if (cur == hi && cur == 0)
+            {
+                /* we've failed to find it */
+                ampptr = 0;
+                break;
+            }
+            
+            /* this one is too high - check the lower half */
+            hi = (cur == hi ? hi - 1 : cur);
+        }
+        else
+        {
+            /* this one is too low - check the upper half */
+            lo = (cur == lo ? lo + 1 : cur);
+        }
+    }
+    
+    /* skip to the appropriate next character */
+    if (c == ';')
+    {
+        /* name ended with semicolon - skip the semicolon */
+        c = nextout(sp, slenp);
+    }
+    else if (ampptr != 0)
+    {
+        int skipcnt;
+        
+        /* found the name - skip its exact length */
+        skipcnt = strlen(ampptr->cname);
+        for (*sp = orig_s, *slenp = orig_slen ; skipcnt != 0 ;
+             c = nextout(sp, slenp), --skipcnt) ;
+    }
+    
+    /* if we found the entry, write out the character */
+    if (ampptr != 0)
+    {
+        /* 
+         *   if this one has an external mapping table entry, use the mapping
+         *   table entry; otherwise, use the default OS routine mapping 
+         */
+        if (ampptr->expan != 0)
+        {
+            /* 
+             *   we have an explicit expansion from the mapping table file -
+             *   use it 
+             */
+            size_t copylen = strlen(ampptr->expan);
+            if (copylen > outbuf_size - 1)
+                copylen = outbuf_size - 1;
+
+            memcpy(outbuf, ampptr->expan, copylen);
+            outbuf[copylen] = '\0';
+        }
+        else
+        {
+            /* 
+             *   there's no mapping table expansion - use the default OS code
+             *   expansion 
+             */
+            os_xlat_html4(ampptr->html_cval, outbuf, outbuf_size);
+        }
+    }
+    else
+    {
+        /* 
+         *   didn't find it - output the '&' literally, then back up and
+         *   output the entire sequence following 
+         */
+        *sp = orig_s;
+        *slenp = orig_slen;
+        c = nextout(sp, slenp);
+
+        /* fill in the '&' return value */
+        outbuf[0] = '&';
+        outbuf[1] = '\0';
+    }
+
+    /* return the next character */
+    return c;
+}
+                      
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Initialize the output formatter 
+ */
+void out_init()
+{
+    /* not yet hiding output */
+    outflag = 1;
+    outcnt = 0;
+    hidout = 0;
+
+    /* initialize the standard display stream */
+    out_init_std(&G_std_disp);
+
+    /* initialize the log file stream */
+    out_init_log(&G_log_disp);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   initialize the property translation table 
+ */
+void tiosetfmt(tiocxdef *ctx, runcxdef *rctx, uchar *fbase, uint flen)
+{
+    VARUSED(ctx);
+    fmsbase = fbase;
+    fmstop = fbase + flen;
+    runctx = rctx;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Map an HTML entity to a local character value.  The character table
+ *   reader will call this routine during initialization if it finds HTML
+ *   entities in the mapping table file.  We'll remember these mappings
+ *   for use in translating HTML entities to the local character set.
+ *   
+ *   Note that the standard run-time can only display a single character
+ *   set, so every HTML entity that we display must be mapped to the
+ *   single active native character set.  
+ */
+void tio_set_html_expansion(unsigned int html_char_val,
+                            const char *expansion, size_t expansion_len)
+{
+    amp_tbl_t *p;
+
+    /* find the character value */
+    for (p = amp_tbl ;
+         p < amp_tbl + sizeof(amp_tbl)/sizeof(amp_tbl[0]) ; ++p)
+    {
+        /* if this is the one, store it */
+        if (p->html_cval == html_char_val)
+        {
+            /* allocate space for it */
+            p->expan = (char *)osmalloc(expansion_len + 1);
+
+            /* save it */
+            memcpy(p->expan, expansion, expansion_len);
+            p->expan[expansion_len] = '\0';
+
+            /* no need to look any further */
+            return;
+        }
+    }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Write out a c-style (null-terminated) string.
+ */
+int outformat(char *s)
+{
+    return outformatlen(s, strlen(s));
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   This routine sends out a string, one character at a time (via outchar).
+ *   Escape codes ('\n', and so forth) are handled here.
+ */
+int outformatlen(char *s, uint slen)
+{
+    char     c;
+    uint     orig_slen;
+    char    *orig_s;
+    int      ret;
+    int      called_filter;
+
+    /* presume we'll return success */
+    ret = 0;
+
+    /* presume we won't call the filter function */
+    called_filter = FALSE;
+
+    /* if there's a user filter function to invoke, call it */
+    if (G_user_filter != MCMONINV)
+    {
+        /* push the string */
+        runpstr(runctx, s, slen, 1);
+
+        /* call the filter */
+        runfn(runctx, G_user_filter, 1);
+
+        /* 
+         *   note that we called the filter, so that we'll remove the
+         *   result of the filter from the stack before we return 
+         */
+        called_filter = TRUE;
+
+        /* if the result is a string, use it in place of the original text */
+        if (runtostyp(runctx) == DAT_SSTRING)
+        {
+            runsdef val;
+            uchar *p;
+
+            /* pop the value */
+            runpop(runctx, &val);
+
+            /* 
+             *   get the text from the string, and use it as a replacement
+             *   for the original string 
+             */
+            p = val.runsv.runsvstr;
+            slen = osrp2(p) - 2;
+            s = (char *)(p + 2);
+
+            /* 
+             *   push the string back onto the stack - this will ensure
+             *   that the string stays referenced while we're working, so
+             *   that the garbage collector won't delete it 
+             */
+            runrepush(runctx, &val);
+        }
+    }
+
+    /* remember the original string, before we scan the first character */
+    orig_s = s;
+    orig_slen = slen;
+
+    /* get the first character to display */
+    c = nextout(&s, &slen);
+
+    /* if the string is non-empty, note that we've displayed something */
+    if (c != 0)
+        outcnt = 1;
+
+    /* check to see if we're hiding output */
+    if (out_is_hidden())
+        goto done;
+
+    /* if the debugger is showing watchpoints, suppress all output */
+    if (outwxflag)
+        goto done;
+
+    /* display the string */
+    ret = outformatlen_stream(&G_std_disp, orig_s, orig_slen);
+
+    /* if there's a log file, write to the log file as well */
+    if (logfp != 0)
+    {
+        outformatlen_stream(&G_log_disp, orig_s, orig_slen);
+        osfflush(logfp);
+    }
+
+done:
+    /* if we called the filter, remove the result from the stack */
+    if (called_filter)
+        rundisc(runctx);
+
+    /* return the result from displaying to the screen */
+    return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Display a blank line 
+ */
+void outblank()
+{
+    /* note that we've displayed something */
+    outcnt = 1;
+
+    /* check to see if we're hiding output */
+    if (out_is_hidden())
+        return;
+
+    /* generate the newline to the standard display */
+    outblank_stream(&G_std_disp);
+
+    /* if we're logging, generate the newline to the log file as well */
+    if (logfp != 0)
+    {
+        outblank_stream(&G_log_disp);
+        osfflush(logfp);
+    }
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   outcaps() - sets an internal flag which makes the next letter output
+ *   a capital, whether it came in that way or not.  Set the same state in
+ *   both formatters (standard and log).  
+ */
+void outcaps(void)
+{
+    outcaps_stream(&G_std_disp);
+    outcaps_stream(&G_log_disp);
+}
+
+/*
+ *   outnocaps() - sets the next letter to a miniscule, whether it came in
+ *   that way or not.  
+ */
+void outnocaps(void)
+{
+    outnocaps_stream(&G_std_disp);
+    outnocaps_stream(&G_log_disp);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Open a log file 
+ */
+int tiologopn(tiocxdef *ctx, char *fn)
+{
+    /* if there's an old log file, close it */
+    if (tiologcls(ctx))
+        return 1;
+
+    /* save the filename for later */
+    strcpy(logfname, fn);
+
+    /* open the new file */
+    logfp = osfopwt(fn, OSFTLOG);
+
+    /* 
+     *   Reset the log file's output formatter state, since we're opening
+     *   a new file.  
+     */
+    out_init_log(&G_log_disp);
+
+    /* 
+     *   Set the log file's HTML source mode flag to the same value as is
+     *   currently being used in the main display stream, so that it will
+     *   interpret source markups the same way that the display stream is
+     *   going to.  
+     */
+    G_log_disp.html_mode = G_std_disp.html_mode;
+
+    /* return 0 on success, non-zero on failure */
+    return (logfp == 0);
+}
+
+/*
+ *   Close the log file 
+ */
+int tiologcls(tiocxdef *ctx)
+{
+    /* if we have a file, close it */
+    if (logfp != 0)
+    {
+        /* close the handle */
+        osfcls(logfp);
+
+        /* set the system file type to "log file" */
+        os_settype(logfname, OSFTLOG);
+
+        /* forget about our log file handle */
+        logfp = 0;
+    }
+
+    /* success */
+    return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Write text explicitly to the log file.  This can be used to add
+ *   special text (such as prompt text) that would normally be suppressed
+ *   from the log file.  When more mode is turned off, we don't
+ *   automatically copy text to the log file; any text that the caller
+ *   knows should be in the log file during times when more mode is turned
+ *   off can be explicitly added with this function.
+ *   
+ *   If nl is true, we'll add a newline at the end of this text.  The
+ *   caller should not include any newlines in the text being displayed
+ *   here.  
+ */
+void out_logfile_print(char *txt, int nl)
+{
+    /* if there's no log file, there's nothing to do */
+    if (logfp == 0)
+        return;
+
+    /* add the text */
+    os_fprintz(logfp, txt);
+
+    /* add a newline if desired */
+    if (nl)
+    {
+        /* add a normal newline */
+        os_fprintz(logfp, "\n");
+
+        /* if the logfile is an html target, write an HTML line break */
+        if (G_log_disp.html_target && G_log_disp.html_mode)
+            os_fprintz(logfp, "<BR HEIGHT=0>\n");
+    }
+
+    /* flush the output */
+    osfflush(logfp);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Set the current MORE mode 
+ */
+int setmore(int state)
+{
+    int oldstate = G_os_moremode;
+    
+    G_os_moremode = state;
+    return oldstate;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Run the MORE prompt.  If the output layer takes responsibility for
+ *   pagination issues (i.e., USE_MORE is defined), we'll simply display
+ *   the prompt and wait for input.  Otherwise, the OS layer controls the
+ *   MORE prompt, so we'll call the OS-layer function to display the
+ *   prompt.  
+ */
+void out_more_prompt()
+{
+#ifdef USE_MORE
+    /*
+     *   USE_MORE defined - we take responsibility for pagination.  Show
+     *   our default MORE prompt and wait for a keystroke.  
+     */
+
+    int done;
+	int next_page = FALSE;
+
+    /* display the "MORE" prompt */
+    os_printz("[More]");
+    os_flush();
+
+    /* wait for an acceptable keystroke */
+    for (done = FALSE ; !done ; )
+    {
+        os_event_info_t evt;
+        
+        /* get an event */
+        switch(os_get_event(0, FALSE, &evt))
+        {
+        case OS_EVT_KEY:
+            switch(evt.key[0])
+            {
+            case ' ':
+                /* stop waiting, show one page */
+                done = TRUE;
+                next_page = TRUE;
+                break;
+                
+            case '\r':
+            case '\n':
+                /* stop waiting, show one line */
+                done = TRUE;
+                next_page = FALSE;
+                break;
+
+            default:
+                /* ignore any other keystrokes */
+                break;
+            }
+            break;
+
+        case OS_EVT_EOF:
+            /* end of file - there's nothing to wait for now */
+            done = TRUE;
+            next_page = TRUE;
+
+            /* don't use more prompts any more, as the user can't respond */
+            G_os_moremode = FALSE;
+            break;
+
+        default:
+            /* ignore other events */
+            break;
+        }
+    }
+
+    /* 
+     *   Remove the prompt from the screen by backing up and overwriting
+     *   it with spaces.  (Note that this assumes that we're running in
+     *   some kind of terminal or character mode with a fixed-pitch font;
+     *   if that's not the case, the OS layer should be taking
+     *   responsibility for pagination anyway, so this code shouldn't be
+     *   in use in the first place.)  
+     */
+    os_printz("\r      \r");
+
+    /* 
+     *   if they pressed the space key, it means that we should show an
+     *   entire new page, so reset the line count to zero; otherwise,
+     *   we'll want to display another MORE prompt at the very next line,
+     *   so leave the line count alone 
+     */
+    if (next_page)
+        G_std_disp.linecnt = 0;
+
+#else /* USE_MORE */
+
+    /*
+     *   USE_MORE is undefined - this means that the OS layer is taking
+     *   all responsibility for pagination.  We must ask the OS layer to
+     *   display the MORE prompt, because we can't make any assumptions
+     *   about what the prompt looks like.  
+     */
+
+    os_more_prompt();
+
+#endif /* USE_MORE */
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   reset output 
+ */
+void outreset(void)
+{
+    G_std_disp.linecnt = 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Determine if HTML mode is active.  Returns true if so, false if not.
+ *   Note that this merely indicates whether an "\H+" sequence is
+ *   currently active -- this will return true after an "\H+" sequence,
+ *   even on text-only interpreters.  
+ */
+int tio_is_html_mode()
+{
+    /* return the current HTML mode flag for the standard display stream */
+    return G_std_disp.html_mode;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Capture routines.  Capture affects only the standard display output
+ *   stream; there's no need to capture information redundantly in the log
+ *   file stream.  
+ */
+
+/*
+ *   Begin/end capturing 
+ */
+void tiocapture(tiocxdef *tioctx, mcmcxdef *memctx, int flag)
+{
+    if (flag)
+    {
+        /* create a new object if necessary */
+        if (G_std_disp.capture_obj == MCMONINV)
+        {
+            mcmalo(memctx, 256, &G_std_disp.capture_obj);
+            mcmunlck(memctx, G_std_disp.capture_obj);
+        }
+
+        /* remember the memory context */
+        G_std_disp.capture_ctx = memctx;
+    }
+
+    /* 
+     *   remember capture status in the standard output stream as well as
+     *   the log stream 
+     */
+    G_std_disp.capturing = flag;
+    G_log_disp.capturing = flag;
+}
+
+/* clear all captured output */
+void tioclrcapture(tiocxdef *tioctx)
+{
+    G_std_disp.capture_ofs = 0;
+}
+
+/* clear captured output back to a given size */
+void tiopopcapture(tiocxdef *tioctx, uint orig_size)
+{
+    G_std_disp.capture_ofs = orig_size;
+}
+
+/* get the object handle of the captured output */
+mcmon tiogetcapture(tiocxdef *ctx)
+{
+    return G_std_disp.capture_obj;
+}
+
+/* get the amount of text captured */
+uint tiocapturesize(tiocxdef *ctx)
+{
+    return G_std_disp.capture_ofs;
+}
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   set the current actor 
+ */
+void tiosetactor(tiocxdef *ctx, objnum actor)
+{
+    VARUSED(ctx);
+    cmdActor = actor;
+}
+
+/*
+ *   get the current actor 
+ */
+objnum tiogetactor(tiocxdef *ctx)
+{
+    VARUSED(ctx);
+    return cmdActor;
+}
+          
+/* ------------------------------------------------------------------------ */
+/*
+ *   Flush the output line.  We'll write to both the standard display and
+ *   the log file, as needed.  
+ */
+void outflushn(int nl)
+{
+    /* flush the display stream */
+    outflushn_stream(&G_std_disp, nl);
+
+    /* flush the log stream, if we have an open log file */
+    if (logfp != 0)
+    {
+        outflushn_stream(&G_log_disp, nl);
+        osfflush(logfp);
+    }
+}
+
+/*
+ *   flush the current line, and start a new line 
+ */
+void outflush(void)
+{
+    /* use the common flushing routine in mode 1 (regular newline) */
+    outflushn(1);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Hidden text routines
+ */
+
+/* 
+ *   outhide - hide output in the standard display stream 
+ */
+void outhide(void)
+{
+    outflag = 0;
+    outcnt = 0;
+    hidout = 0;
+}
+
+/*
+ *   Check output status.  Indicate whether output is currently hidden,
+ *   and whether any hidden output has occurred.  
+ */
+void outstat(int *hidden, int *output_occurred)
+{
+    *hidden = !outflag;
+    *output_occurred = outcnt;
+}
+
+/* set the flag to indicate that output has occurred */
+void outsethidden(void)
+{
+    outcnt = 1;
+    hidout = 1;
+}
+
+/*
+ *   outshow() - turns output back on, and returns TRUE (1) if any output
+ *   has occurred since the last outshow(), FALSE (0) otherwise.
+ */
+int outshow(void)
+{
+    /* turn output back on */
+    outflag = 1;
+
+    /* if we're debugging, note the end of hidden output */
+    if (dbghid && hidout)
+    {
+        hidout = 0;
+        trcsho();
+    }
+
+    /* return the flag indicating whether hidden output occurred */
+    return outcnt;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   start/end watchpoint evaluation - suppress all dstring output 
+ */
+void outwx(int flag)
+{
+    outwxflag = flag;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Set the user filter function.  Setting this to MCMONINV clears the
+ *   filter. 
+ */
+void out_set_filter(objnum filter_fn)
+{
+    /* remember the filter function */
+    G_user_filter = filter_fn;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Set the double-space mode 
+ */
+void out_set_doublespace(int dbl)
+{
+    /* remember the new setting */
+    doublespace = dbl;
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp
index 5123d65..5a9ede0 100644
--- a/engines/glk/tads/tads2/run.cpp
+++ b/engines/glk/tads/tads2/run.cpp
@@ -22,30 +22,2410 @@
 
 #include "glk/tads/tads2/run.h"
 #include "glk/tads/tads2/data.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/post_compilation.h"
 #include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-int runsdef::runsiz() const {
-	switch (runstyp) {
+/* forward declarations */
+struct bifcxdef;
+
+/*
+*   Create a new object
+*/
+static void run_new(runcxdef *ctx, uchar *noreg *codepp,
+	objnum callobj, prpnum callprop)
+{
+	objnum   sc = 0;
+	objnum   objn;
+	objdef  *objp;
+	int      sccnt;
+	vocidef *voci;
+
+	/* get the superclass (nil means no superclass) */
+	if (runtostyp(ctx) == DAT_NIL)
+		sccnt = 0;
+	else
+	{
+		/* get the superclass */
+		sc = runpopobj(ctx);
+		sccnt = 1;
+
+		/* make sure it's not a dynamically-allocated object */
+		voci = vocinh(ctx->runcxvoc, sc);
+		if (voci->vociflg & VOCIFNEW)
+			runsig(ctx, ERR_BADNEWSC);
+	}
+
+	/* create a new object and set its superclass */
+	objp = objnew(ctx->runcxmem, sccnt, 64, &objn, FALSE);
+	if (sccnt) oswp2(objsc(objp), sc);
+
+	/* save undo for the object creation */
+	vocdusave_newobj(ctx->runcxvoc, objn);
+
+	/* touch and unlock the object */
+	mcmtch(ctx->runcxmem, (mcmon)objn);
+	mcmunlck(ctx->runcxmem, (mcmon)objn);
+
+	/* add a vocabulary inheritance record for the new object */
+	vociadd(ctx->runcxvoc, objn, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC);
+
+	/* set up its vocabulary, inheriting from the class */
+	if (sccnt)
+		supivoc1((struct supcxdef *)0, ctx->runcxvoc,
+			vocinh(ctx->runcxvoc, objn), objn, TRUE, VOCFNEW);
+
+	/* run the constructor */
+	runpprop(ctx, codepp, callobj, callprop, objn, PRP_CONSTRUCT,
+		FALSE, 0, objn);
+#ifdef NEVER
+	/*
+	*   add it to its location's contents list by calling
+	*   newobj.moveInto(newobj.location)
+	*/
+	runppr(ctx, objn, PRP_LOCATION, 0);
+	if (runtostyp(ctx) == DAT_OBJECT)
+		runppr(ctx, objn, PRP_MOVEINTO, 1);
+	else
+		rundisc(ctx);
+#endif
+
+	/* return the new object */
+	runpobj(ctx, objn);
+}
+
+/*
+*   Delete an object
+*/
+static void run_delete(runcxdef *ctx, uchar *noreg *codepp,
+	objnum callobj, prpnum callprop)
+{
+	objnum    objn;
+	vocidef  *voci;
+	int       i;
+	voccxdef *vctx = ctx->runcxvoc;
+
+	/* get the object to be deleted */
+	objn = runpopobj(ctx);
+
+	/* make sure it was allocated with "new" */
+	voci = vocinh(vctx, objn);
+	if (voci == 0 || !(voci->vociflg & VOCIFNEW))
+		runsig(ctx, ERR_BADDEL);
+
+	/* run the destructor */
+	runpprop(ctx, codepp, callobj, callprop, objn, PRP_DESTRUCT,
+		FALSE, 0, objn);
+#ifdef NEVER
+	/* remove it from its location, if any, by using moveInto(nil) */
+	runpnil(ctx);
+	runppr(ctx, objn, PRP_MOVEINTO, 1);
+#endif
+
+	/* save undo for the object deletion */
+	vocdusave_delobj(vctx, objn);
+
+	/* delete the object's inheritance and vocabulary records */
+	vocdel(vctx, objn);
+	vocidel(vctx, objn);
+
+	/* forget 'it' if the deleted object is 'it' (or 'them', etc) */
+	if (vctx->voccxit == objn) vctx->voccxit = MCMONINV;
+	if (vctx->voccxhim == objn) vctx->voccxhim = MCMONINV;
+	if (vctx->voccxher == objn) vctx->voccxher = MCMONINV;
+	for (i = 0; i < vctx->voccxthc; ++i)
+	{
+		if (vctx->voccxthm[i] == objn)
+		{
+			/* forget the entire 'them' list when deleting from it */
+			vctx->voccxthc = 0;
+			break;
+		}
+	}
+
+	/* forget the 'again' statistics if necessary */
+	if (vctx->voccxlsd.vocolobj == objn
+		|| vctx->voccxlsi.vocolobj == objn
+		|| vctx->voccxlsa == objn
+		|| vctx->voccxlsv == objn
+		|| vctx->voccxlsp == objn)
+	{
+		/* forget the verb */
+		vctx->voccxlsv = MCMONINV;
+
+		/*
+		*   note in the flags why we lost the "again" verb, for better
+		*   error reporting if the player tries to type "again"
+		*/
+		vctx->voccxflg |= VOCCXAGAINDEL;
+	}
+
+	/* delete the memory manager object */
+	mcmfre(ctx->runcxmem, (mcmon)objn);
+}
+
+
+/*
+*   invoke a function
+*/
+void runfn(runcxdef *ctx, noreg objnum  objn, int argc)
+{
+	uchar *fn;
+	int    err;
+
+	NOREG((&objn))
+
+		/* get a lock on the object */
+		fn = mcmlck(ctx->runcxmem, objn);
+
+	/* catch any errors, so we can unlock the object */
+	ERRBEGIN(ctx->runcxerr)
+
+		/* execute the object */
+		runexe(ctx, fn, MCMONINV, objn, (prpnum)0, argc);
+
+	/* in case of error, unlock the object and resignal the error */
+	ERRCATCH(ctx->runcxerr, err)
+		mcmunlck(ctx->runcxmem, objn);    /* release the lock on the object */
+	if (err < ERR_RUNEXIT || err > ERR_RUNEXITOBJ)
+		dbgdump(ctx->runcxdbg);                       /* dump the stack */
+	errrse(ctx->runcxerr);
+	ERREND(ctx->runcxerr)
+
+		/* we're done with the object, so unlock it */
+		mcmunlck(ctx->runcxmem, objn);
+}
+
+/*
+*   compress the heap - remove unreferenced items
+*/
+void runhcmp(runcxdef *ctx, uint siz, uint below,
+	runsdef *val1, runsdef *val2, runsdef *val3)
+{
+	uchar   *hp = ctx->runcxheap;
+	uchar   *htop = ctx->runcxhp;
+	runsdef *stop = ctx->runcxsp + below;
+	runsdef *stk = ctx->runcxstk;
+	runsdef *sp;
+	uchar   *dst = hp;
+	uchar   *hnxt;
+	int      ref;
+
+	/* go through heap, finding references on stack */
+	for (; hp < htop; hp = hnxt)
+	{
+		hnxt = hp + osrp2(hp);                /* remember next heap element */
+
+		for (ref = FALSE, sp = stk; sp < stop; ++sp)
+		{
+			switch (sp->runstyp)
+			{
+			case DAT_SSTRING:
+			case DAT_LIST:
+				if (sp->runsv.runsvstr == hp)    /* reference to this item? */
+				{
+					ref = TRUE;             /* this heap item is referenced */
+					sp->runsv.runsvstr = dst;      /* reflect imminent move */
+				}
+				break;
+
+			default:                /* other types do not refer to the heap */
+				break;
+			}
+		}
+
+		/* check the explicitly referenced value pointers as well */
+#define CHECK_VAL(val) \
+        if (val && val->runsv.runsvstr == hp) \
+            ref = TRUE, val->runsv.runsvstr = dst;
+		CHECK_VAL(val1);
+		CHECK_VAL(val2);
+		CHECK_VAL(val3);
+#undef CHECK_VAL
+
+		/* if referenced, copy it to dst and advance dst */
+		if (ref)
+		{
+			if (hp != dst) memmove(dst, hp, (size_t)osrp2(hp));
+			dst += osrp2(dst);
+		}
+	}
+
+	/* set heap pointer based on shuffled heap */
+	ctx->runcxhp = dst;
+
+	/* check for space requested, and signal error if not available */
+	if ((uint)(ctx->runcxhtop - ctx->runcxhp) < siz)
+		runsig(ctx, ERR_HPOVF);
+}
+
+/*
+*   push a value onto the stack that's already been allocated in heap
+*/
+void runrepush(runcxdef *ctx, runsdef *val)
+{
+	/* check for stack overflow */
+	runstkovf(ctx);
+
+	OSCPYSTRUCT(*(ctx->runcxsp), *val);
+
+	/* increment stack pointer */
+	++(ctx->runcxsp);
+}
+
+/* push a counted-length string onto the stack */
+void runpstr(runcxdef *ctx, char *str, int len, int sav)
+{
+	runsdef val;
+
+	/* allocate space and set up new string */
+	runhres(ctx, len + 2, sav);
+	oswp2(ctx->runcxhp, len + 2);
+	memcpy(ctx->runcxhp + 2, str, (size_t)len);
+
+	/* push return value */
+	val.runsv.runsvstr = ctx->runcxhp;
+	val.runstyp = DAT_SSTRING;
+	ctx->runcxhp += len + 2;
+	runrepush(ctx, &val);
+}
+
+/* push a C-style string, converting escape codes */
+void runpushcstr(runcxdef *ctx, char *str, size_t len, int sav)
+{
+	char    *p;
+	char    *dst;
+	size_t   need;
+	runsdef  val;
+
+	/* determine how much space we'll need after converting escapes */
+	for (p = str, need = len; p < str + len; ++p)
+	{
+		switch (*p)
+		{
+		case '\\':
+		case '\n':
+		case '\r':
+		case '\t':
+			/* these characters need to be escaped */
+			++need;
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	/* reserve space */
+	runhres(ctx, need + 2, sav);
+
+	/* set up the length prefix */
+	oswp2(ctx->runcxhp, need + 2);
+
+	/* copy the string, expanding escapes */
+	for (p = str, dst = (char *)ctx->runcxhp + 2; p < str + len; ++p)
+	{
+		switch (*p)
+		{
+		case '\\':
+			*dst++ = '\\';
+			*dst++ = '\\';
+			break;
+
+		case '\n':
+		case '\r':
+			*dst++ = '\\';
+			*dst++ = 'n';
+			break;
+
+		case '\t':
+			*dst++ = '\\';
+			*dst++ = '\t';
+			break;
+
+		default:
+			*dst++ = *p;
+			break;
+		}
+	}
+
+	/* push the return value */
+	val.runsv.runsvstr = ctx->runcxhp;
+	val.runstyp = DAT_SSTRING;
+	ctx->runcxhp += need + 2;
+	runrepush(ctx, &val);
+}
+
+/* push a value onto the stack */
+void runpush(runcxdef *ctx, dattyp typ, runsdef *val)
+{
+	int len;
+
+	/* check for stack overflow */
+	runstkovf(ctx);
+
+	OSCPYSTRUCT(*(ctx->runcxsp), *val);
+	ctx->runcxsp->runstyp = typ;
+
+	/* variable-length data must be copied into the heap */
+	if (typ == DAT_SSTRING || typ == DAT_LIST)
+	{
+		len = osrp2(val->runsv.runsvstr);
+		runhres(ctx, len, 0);                      /* reserve space in heap */
+		memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)len);
+		ctx->runcxsp->runsv.runsvstr = ctx->runcxhp;
+		ctx->runcxhp += len;
+	}
+
+	/* increment stack pointer */
+	++(ctx->runcxsp);
+}
+
+/* push a number onto the stack */
+void runpnum(runcxdef *ctx, long num)
+{
+	runsdef val;
+
+	val.runsv.runsvnum = num;
+	runpush(ctx, DAT_NUMBER, &val);
+}
+
+/* push an object onto the stack (or nil if obj is MCMONINV) */
+void runpobj(runcxdef *ctx, objnum obj)
+{
+	runsdef val;
+
+	if (obj == MCMONINV)
+		runpnil(ctx);
+	else
+	{
+		val.runsv.runsvobj = obj;
+		runpush(ctx, DAT_OBJECT, &val);
+	}
+}
+
+/* push nil */
+void runpnil(runcxdef *ctx)
+{
+	runsdef val;
+	runpush(ctx, DAT_NIL, &val);
+}
+
+/* copy datatype + value from a runsdef into a buffer (such as list) */
+static void runputbuf(uchar *dstp, runsdef *val)
+{
+	*dstp++ = val->runstyp;
+	switch (val->runstyp)
+	{
+	case DAT_LIST:
+	case DAT_SSTRING:
+		memcpy(dstp, val->runsv.runsvstr, (size_t)osrp2(val->runsv.runsvstr));
+		break;
+
 	case DAT_NUMBER:
-		return 4;
+		oswp4s(dstp, val->runsv.runsvnum);
+		break;
+
+	case DAT_PROPNUM:
+		oswp2(dstp, val->runsv.runsvprp);
+		break;
+
+	case DAT_OBJECT:
+	case DAT_FNADDR:
+		oswp2(dstp, val->runsv.runsvobj);
+		break;
+	}
+}
+
+/* push a value from a buffer (list, property, etc) onto stack */
+void runpbuf(runcxdef *ctx, int typ, void *valp)
+{
+	runsdef val;
+
+	switch (typ)
+	{
+	case DAT_NUMBER:
+		val.runsv.runsvnum = osrp4s(valp);
+		break;
+
+	case DAT_OBJECT:
+	case DAT_FNADDR:
+		val.runsv.runsvobj = osrp2(valp);
+		break;
+
+	case DAT_PROPNUM:
+		val.runsv.runsvprp = osrp2(valp);
+		break;
+
+	case DAT_SSTRING:
+	case DAT_LIST:
+		val.runsv.runsvstr = (uchar *)valp;
+		break;
+
+	case DAT_NIL:
+	case DAT_TRUE:
+		break;
+	}
+	runpush(ctx, typ, &val);
+}
+
+/* compare items at top of stack for equality; TRUE->equal, FALSE->unequal */
+int runeq(runcxdef *ctx)
+{
+	runsdef val1, val2;
+
+	/* get values, and see if they have identical type; not equal if not */
+	runpop(ctx, &val1);
+	runpop(ctx, &val2);
+	if (val1.runstyp != val2.runstyp) return(FALSE);
+
+	/* types match, so check values */
+	switch (val1.runstyp)
+	{
+	case DAT_NUMBER:
+		return(val1.runsv.runsvnum == val2.runsv.runsvnum);
+
 	case DAT_SSTRING:
 	case DAT_LIST:
-		return osrp2(runsv.runsvstr);
+		return(osrp2(val1.runsv.runsvstr) == osrp2(val2.runsv.runsvstr)
+			&& !memcmp(val1.runsv.runsvstr, val2.runsv.runsvstr,
+			(size_t)osrp2(val1.runsv.runsvstr)));
+
 	case DAT_PROPNUM:
+		return(val1.runsv.runsvprp == val2.runsv.runsvprp);
+
 	case DAT_OBJECT:
 	case DAT_FNADDR:
-		return 2;
+		return(val1.runsv.runsvobj == val2.runsv.runsvobj);
+
+	default:
+		return(TRUE);
+	}
+}
+
+/* compare magnitudes of numbers/strings at top of stack; strcmp-like value */
+int runmcmp(runcxdef *ctx)
+{
+	if (runtostyp(ctx) == DAT_NUMBER)
+	{
+		long num2 = runpopnum(ctx);
+		long num1 = runpopnum(ctx);
+
+		if (num1 > num2) return(1);
+		else if (num1 < num2) return(-1);
+		else return(0);
+	}
+	else if (runtostyp(ctx) == DAT_SSTRING)
+	{
+		uchar *str2 = runpopstr(ctx);
+		uchar *str1 = runpopstr(ctx);
+		uint   len1 = osrp2(str1) - 2;
+		uint   len2 = osrp2(str2) - 2;
+
+		str1 += 2;
+		str2 += 2;
+		while (len1 && len2)
+		{
+			if (*str1 < *str2) return(-1);   /* character from 1 is greater */
+			else if (*str1 > *str2) return(1);       /* char from 1 is less */
+
+			++str1;
+			++str2;
+			--len1;
+			--len2;
+		}
+		if (len1) return(1);    /* match up to len2, but string 1 is longer */
+		else if (len2) return(-1);  /* match up to len1, but str2 is longer */
+		else return(0);                            /* strings are identical */
+	}
+	else
+	{
+		runsig(ctx, ERR_INVCMP);
+	}
+	return 0;
+}
+
+/* determine size of a runsdef item */
+int runsiz(runsdef *item) {
+	switch (item->runstyp) {
+	case DAT_NUMBER:
+		return(4);
+	case DAT_SSTRING:
+	case DAT_LIST:
+		return(osrp2(item->runsv.runsvstr));
+	case DAT_PROPNUM:
+	case DAT_OBJECT:
+	case DAT_FNADDR:
+		return(2);
+	default:
+		return(0);
+	}
+}
+
+/* find a sublist within a list */
+uchar *runfind(uchar *lst, runsdef *item)
+{
+	uint len;
+	uint curlen;
+
+	for (len = osrp2(lst) - 2, lst += 2; len; lst += curlen, len -= curlen)
+	{
+		if (*lst == item->runstyp)
+		{
+			switch (*lst)
+			{
+			case DAT_LIST:
+			case DAT_SSTRING:
+				if (osrp2(lst + 1) == osrp2(item->runsv.runsvstr) &&
+					!memcmp(lst + 1, item->runsv.runsvstr, (size_t)osrp2(lst + 1)))
+					return(lst);
+				break;
+			case DAT_NUMBER:
+				if (osrp4s(lst + 1) == item->runsv.runsvnum)
+					return(lst);
+				break;
+
+			case DAT_TRUE:
+			case DAT_NIL:
+				return(lst);
+
+			case DAT_OBJECT:
+			case DAT_FNADDR:
+				if (osrp2(lst + 1) == item->runsv.runsvobj)
+					return(lst);
+				break;
+
+			case DAT_PROPNUM:
+				if (osrp2(lst + 1) == item->runsv.runsvprp)
+					return(lst);
+				break;
+			}
+		}
+		curlen = datsiz(*lst, lst + 1) + 1;
+	}
+	return((uchar *)0);
+}
+
+/* add values */
+void runadd(runcxdef *ctx, runsdef *val, runsdef *val2, uint below)
+{
+	if (val->runstyp == DAT_LIST)
+	{
+		int     len1 = osrp2(val->runsv.runsvstr);
+		int     len2 = runsiz(val2);
+		int     newlen;
+
+		/* if concatenating a list, take out length + datatype from 2nd */
+		if (val2->runstyp == DAT_LIST)
+			newlen = len1 + len2 - 2;          /* leave out second list len */
+		else
+			newlen = len1 + len2 + 1;             /* add in datatype header */
+
+												  /* get space in heap, copy first list, and set new length */
+		runhres2(ctx, newlen, below, val, val2);
+		memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)len1);
+		oswp2(ctx->runcxhp, newlen);
+
+		/* append the new element or list of elements */
+		if (val2->runstyp == DAT_LIST)
+			memcpy(ctx->runcxhp + len1, val2->runsv.runsvstr + 2,
+			(size_t)(len2 - 2));
+		else
+			runputbuf(ctx->runcxhp + len1, val2);
+
+		/* set up return value and update heap pointer */
+		val->runsv.runsvstr = ctx->runcxhp;
+		ctx->runcxhp += newlen;
+	}
+	else if (val->runstyp == DAT_SSTRING && val2->runstyp == DAT_SSTRING)
+	{
+		int len1 = osrp2(val->runsv.runsvstr);
+		int len2 = osrp2(val2->runsv.runsvstr);
+
+		/* reserve space, and concatenate the two strings */
+		runhres2(ctx, len1 + len2 - 2, below, val, val2);
+		memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)len1);
+		memcpy(ctx->runcxhp + len1, val2->runsv.runsvstr + 2,
+			(size_t)len2 - 2);
+
+		/* set length to sum of two lengths, minus 2nd length word */
+		oswp2(ctx->runcxhp, len1 + len2 - 2);
+		val->runsv.runsvstr = ctx->runcxhp;
+		ctx->runcxhp += len1 + len2 - 2;
+	}
+	else if (val->runstyp == DAT_NUMBER && val2->runstyp == DAT_NUMBER)
+		val->runsv.runsvnum += val2->runsv.runsvnum;
+	else
+		runsig(ctx, ERR_INVADD);
+}
+
+/* returns TRUE if value changed */
+int runsub(runcxdef *ctx, runsdef *val, runsdef *val2, uint below)
+{
+	if (val->runstyp == DAT_LIST)
+	{
+		uchar *sublist;
+		int    subsize;
+		int    listsize;
+		int    part1sz;
+
+		if (val2->runstyp == DAT_LIST)
+		{
+			uchar *p1;
+			uchar *p2;
+			uint   rem1;
+			uint   rem2;
+			uchar *dst;
+
+			/* reserve space for another copy of first list */
+			listsize = runsiz(val);
+			runhres2(ctx, listsize, below, val, val2);
+			dst = ctx->runcxhp + 2;
+
+			/* get pointer to first list */
+			p1 = val->runsv.runsvstr;
+			rem1 = osrp2(p1) - 2;
+			p1 += 2;
+
+			/*
+			*   loop through left list, copying elements to output if
+			*   not in the right list
+			*/
+			for (; rem1; lstadv(&p1, &rem1))
+			{
+				int found = FALSE;
+
+				/* find current element of first list in second list */
+				p2 = val2->runsv.runsvstr;
+				rem2 = osrp2(p2) - 2;
+				p2 += 2;
+				for (; rem2; lstadv(&p2, &rem2))
+				{
+					if (*p1 == *p2)
+					{
+						int siz1 = datsiz(*p1, p1 + 1);
+						int siz2 = datsiz(*p2, p2 + 1);
+
+						if (siz1 == siz2 &&
+							(siz1 == 0 || !memcmp(p1 + 1, p2 + 1, (size_t)siz1)))
+						{
+							found = TRUE;
+							break;
+						}
+					}
+				}
+
+				/* if this element wasn't found, copy to output list */
+				if (!found)
+				{
+					uint siz;
+
+					*dst++ = *p1;
+					if ((siz = datsiz(*p1, p1 + 1)) != 0)
+					{
+						memcpy(dst, p1 + 1, siz);
+						dst += siz;
+					}
+				}
+			}
+
+			/* we've built the list; write size and we're done */
+			oswp2(ctx->runcxhp, dst - ctx->runcxhp);
+			val->runsv.runsvstr = ctx->runcxhp;
+			ctx->runcxhp = dst;
+		}
+		else if ((sublist = runfind(val->runsv.runsvstr, val2)) != 0)
+		{
+			subsize = datsiz(*sublist, sublist + 1) + 1;
+			listsize = runsiz(val);
+			part1sz = sublist - (uchar *)val->runsv.runsvstr;
+
+			runhres2(ctx, listsize - subsize, below, val, val2);
+			memcpy(ctx->runcxhp, val->runsv.runsvstr, (size_t)part1sz);
+			memcpy(ctx->runcxhp + part1sz, sublist + subsize,
+				(size_t)(listsize - subsize - part1sz));
+			oswp2(ctx->runcxhp, listsize - subsize);
+			val->runsv.runsvstr = ctx->runcxhp;
+			ctx->runcxhp += listsize - subsize;
+		}
+		else
+		{
+			return(FALSE);            /* no change - value can be re-pushed */
+		}
+	}
+	else if (val->runstyp == DAT_NUMBER && val2->runstyp == DAT_NUMBER)
+		val->runsv.runsvnum -= val2->runsv.runsvnum;
+	else
+		runsig(ctx, ERR_INVSUB);
+
+	return(TRUE);                 /* value has changed; must be pushed anew */
+}
+
+/* return code pointer offset */
+static uint runcpsav(runcxdef *ctx, uchar *noreg *cp, objnum obj, prpnum prop)
+{
+	uint ofs;
+
+	VARUSED(prop);
+
+	/* get offset from start of object */
+	ofs = *cp - mcmobjptr(ctx->runcxmem, (mcmon)obj);
+
+	/* clear the pointer so the caller knows the object is unlocked */
+	*cp = 0;
+
+	/* unlock the object, and return the derived offset */
+	mcmunlck(ctx->runcxmem, (mcmon)obj);
+	return(ofs);
+}
+
+/* restore code pointer based on object.property */
+uchar *runcprst(runcxdef *ctx, uint ofs, objnum obj, prpnum prop)
+{
+	uchar *ptr;
+
+	VARUSED(prop);
+
+	/* lock object, and get pointer based on offset */
+	ptr = mcmlck(ctx->runcxmem, (mcmon)obj) + ofs;
+
+	return(ptr);
+}
+
+/* get offset of an element within a list */
+static uint runindofs(runcxdef *ctx, uint indx, uchar *lstp)
+{
+	uint   lstsiz;
+	uchar *orgp = lstp;
+
+	/* verify that index is in range */
+	if (indx <= 0) runsig(ctx, ERR_LOWINX);
+
+	/* get list's size, and point to its data string */
+	lstsiz = osrp2(lstp) - 2;
+	lstp += 2;
+
+	/* skip the first indx-1 elements */
+	for (--indx; indx && lstsiz; --indx) lstadv(&lstp, &lstsiz);
+
+	/* if we ran out of list, the index is out of range */
+	if (!lstsiz) runsig(ctx, ERR_HIGHINX);
+
+	/* return the offset */
+	return((uint)(lstp - orgp));
+}
+
+/* push an indexed element of a list; index is tos, list is next on stack */
+static void runpind(runcxdef *ctx, uint indx, uchar *lstp)
+{
+	uchar   *ele;
+	runsdef  val;
+
+	/* find the element we want to push */
+	ele = lstp + runindofs(ctx, indx, lstp);
+
+	/* reserve space first, in case lstp gets moved around */
+	val.runstyp = DAT_LIST;
+	val.runsv.runsvstr = lstp;
+	runhres1(ctx, datsiz(*ele, ele + 1), 0, &val);
+	if (val.runsv.runsvstr != lstp)
+		ele = val.runsv.runsvstr + runindofs(ctx, indx, val.runsv.runsvstr);
+
+	/* push the operand */
+	runpbuf(ctx, *ele, ele + 1);
+}
+
+/*
+*   Check a property to ensure that it's a data property.  Throws an
+*   error if the property contains a method.  This is used for debugger
+*   speculative evaluation to ensure that we don't call any methods from
+*   within speculative expressions.
+*/
+static void runcheckpropdata(runcxdef *ctx, objnum obj, prpnum prop)
+{
+	uint    pofs;
+	objnum  target;
+	objdef *objptr;
+	prpdef *prpptr;
+	int     typ;
+
+	/* if the object is invalid, it's an error */
+	if (obj == MCMONINV)
+		errsig(ctx->runcxerr, ERR_REQVOB);
+
+	/* get the property */
+	pofs = objgetap(ctx->runcxmem, obj, prop, &target, FALSE);
+
+	/* if there's no property, it's okay - it will just return nil */
+	if (pofs == 0)
+		return;
+
+	/* get the object */
+	objptr = mcmlck(ctx->runcxmem, target);
+
+	/* get the property */
+	prpptr = (prpdef *)(((uchar *)objptr) + pofs);
+	typ = prptype(prpptr);
+
+	/* we're done with the object's memory now */
+	mcmunlck(ctx->runcxmem, target);
+
+	/* check the type */
+	switch (typ)
+	{
+	case DAT_CODE:
+	case DAT_DSTRING:
+		/*
+		*   we can't call code or evaluate (i.e., print) double-quoted
+		*   strings during speculative evaluation
+		*/
+		errsig(ctx->runcxerr, ERR_RTBADSPECEXPR);
+
+	default:
+		/* other types do not involve method calls, so they're okay */
+		break;
+	}
+}
+
+/* push an object's property */
+void runpprop(runcxdef *ctx, uchar *noreg *codepp,
+	objnum callobj, prpnum callprop,
+	noreg objnum obj, prpnum prop, int inh, int argc, objnum self)
+{
+	uint     pofs;
+	uint     saveofs = 0;
+	objdef  *objptr;
+	prpdef  *prpptr;
+	uchar   *val;
+	int      typ;
+	runsdef  sval;
+	objnum   target;
+	int      times_through = 0;
+	int      err;
+	objnum   otherobj = 0;
+
+	NOREG((&obj, &codepp));
+
+	if (obj == MCMONINV) runsig(ctx, ERR_RUNNOBJ);
+
+startover:
+	pofs = objgetap(ctx->runcxmem, obj, prop, &target, inh);
+
+	/* if nothing was found, push nil */
+	if (!pofs)
+	{
+		runpush(ctx, DAT_NIL, &sval);
+		return;
+	}
+
+	/* found a property; get the prpdef, and the value and type of data */
+	objptr = mcmlck(ctx->runcxmem, target);
+	ERRBEGIN(ctx->runcxerr)         /* catch errors so we can unlock object */
+
+		prpptr = (prpdef *)(((uchar *)objptr) + pofs);
+	val = prpvalp(prpptr);
+	typ = prptype(prpptr);
+
+	/* determine what to do based on property type */
+	switch (typ)
+	{
+	case DAT_CODE:
+		/* save caller's code offset - caller's object may move */
+		if (codepp)
+			saveofs = runcpsav(ctx, codepp, callobj, callprop);
+
+		/* execute the code */
+		runexe(ctx, val, self, target, prop, argc);
+
+		/* restore caller's code pointer in case object moved */
+		if (codepp)
+			*codepp = runcprst(ctx, saveofs, callobj, callprop);
+		break;
+
+	case DAT_REDIR:
+		otherobj = osrp2(val);
+		break;
+
+	case DAT_DSTRING:
+		outfmt(ctx->runcxtio, val);
+		break;
+
+	case DAT_DEMAND:
+		break;
+
 	default:
-		return 0;
+		runpbuf(ctx, typ, val);
+		break;
+	}
+
+	/* we're done - unlock the object */
+	mcmunlck(ctx->runcxmem, target);
+
+	/* if it's redirected, redirect it now */
+	if (typ == DAT_REDIR)
+	{
+		runpprop(ctx, codepp, callobj, callprop, otherobj, prop,
+			FALSE, argc, otherobj);
 	}
+
+	/* if an error occurs, unlock the object, and resignal the error */
+	ERRCATCH(ctx->runcxerr, err)
+		mcmunlck(ctx->runcxmem, target);
+	if (err < ERR_RUNEXIT || err > ERR_RUNEXITOBJ)
+		dbgdump(ctx->runcxdbg);                       /* dump the stack */
+	errrse(ctx->runcxerr);
+	ERREND(ctx->runcxerr)
+
+		/* apply special handling for set-on-first-use data */
+		if (typ == DAT_DEMAND)
+		{
+			/*
+			*   if we've already done this, the property isn't being set by
+			*   the callback, so we'll never get out of this loop - abort if
+			*   so
+			*/
+			if (++times_through != 1)
+				runsig(ctx, ERR_DMDLOOP);
+
+			/* save caller's code offset - caller's object may move */
+			if (codepp)
+				saveofs = runcpsav(ctx, codepp, callobj, callprop);
+
+			/* invoke the callback to set the property on demand */
+			(*ctx->runcxdmd)(ctx->runcxdmc, obj, prop);
+
+			/* restore caller's code pointer */
+			if (codepp)
+				*codepp = runcprst(ctx, saveofs, callobj, callprop);
+
+			/* try again now that it's been set up */
+			goto startover;
+		}
 }
 
-/*--------------------------------------------------------------------------*/
+/* ======================================================================== */
+/*
+*   user exit callbacks
+*/
 
+/* External fnctions are now obsolete */
+#if 0
+static int runuftyp(runuxdef *ctx)
+{
+	return(runtostyp(ctx->runuxctx));
+}
+
+static long runufnpo(runuxdef *ctx)
+{
+	return(runpopnum(ctx->runuxctx));
+}
+
+static uchar *runufspo(runuxdef *ctx)
+{
+	return(runpopstr(ctx->runuxctx));
+}
+
+static void runufdsc(runuxdef *ctx)
+{
+	rundisc(ctx->runuxctx);
+}
+
+static void runufnpu(runuxdef *ctx, long num)
+{
+	runpnum(ctx->runuxctx, num);
+}
+
+static void runufspu(runuxdef *ctx, uchar *str)
+{
+	runsdef val;
+
+	val.runstyp = DAT_SSTRING;
+	val.runsv.runsvstr = str - 2;
+	runrepush(ctx->runuxctx, &val);
+}
+
+static void runufcspu(runuxdef *ctx, char *str)
+{
+	runpstr(ctx->runuxctx, str, (int)strlen(str), ctx->runuxargc);
+}
+
+static uchar *runufsal(runuxdef *ctx, int len)
+{
+	uchar *ret;
+
+	len += 2;
+	runhres(ctx->runuxctx, len, ctx->runuxargc);
+	ret = ctx->runuxctx->runcxhp;
+	oswp2(ret, len);
+	ret += 2;
+
+	ctx->runuxctx->runcxhp += len;
+	return(ret);
+}
+
+static void runuflpu(runuxdef *ctx, int typ)
+{
+	runsdef val;
+
+	val.runstyp = typ;
+	runrepush(ctx->runuxctx, &val);
+}
+
+#endif
+
+/* convert an osrp2 value to a signed short value */
+#define runrp2s(p) ((short)(ushort)osrp2(p))
+
+
+/* ======================================================================== */
+/*
+*   execute p-code
+*/
+void runexe(runcxdef *ctx, uchar *p0, objnum self, objnum target,
+	prpnum targprop, int argc)
+{
+	uchar    *noreg p = p0;
+	uchar     opc;                     /* opcode we're currently working on */
+	runsdef   val;                           /* stack element (for pushing) */
+	runsdef   val2;     /* another one (for popping in two-op instructions) */
+	uint      ofs;                   /* offset in code of current execution */
+	prpnum    prop;                         /* property number, when needed */
+	objnum    obj;                            /* object number, when needed */
+	runsdef  *noreg rstsp;        /* sp to reset to on DISCARD instructions */
+	uchar    *lstp;                                         /* list pointer */
+	int       nargc;                   /* argument count of called function */
+	runsdef  *valp;
+	runsdef  *stkval;
+	int       i;
+	int       brkchk;
+
+#ifndef DBG_OFF
+	int       err;
+#endif
+
+	NOREG((&rstp, &p));
+
+	/* save entry SP - this is reset point until ENTER */
+	rstsp = ctx->runcxsp;
+
+#ifndef DBG_OFF
+	/*
+	*   For the debugger's sake, set up an error frame so that we catch
+	*   any errors thrown during p-code execution within this function.
+	*   If an error occurs, and the debugger is present, we'll set the
+	*   instruction pointer back to the start of the line that caused the
+	*   error and enter the debugger with the error indication.  If the
+	*   debugger isn't present, we'll simply re-throw the error.  This
+	*   entire block can be compiled out of the execution engine when
+	*   linking a stand-alone (non-debug) version of the run-time.
+	*/
+resume_from_error:
+	ERRBEGIN(ctx->runcxerr)
+#endif /* DBG_OFF */
+
+		for (brkchk = 0;; ++brkchk)
+		{
+			/* check for break - signal if user has hit break */
+			if (brkchk == 1000)
+			{
+				brkchk = 0;
+				if (os_break()) runsig(ctx, ERR_USRINT);
+			}
+
+			opc = *p++;
+
+			switch (opc)
+			{
+			case OPCPUSHNUM:
+				val.runsv.runsvnum = osrp4s(p);
+				runpush(ctx, DAT_NUMBER, &val);
+				p += 4;
+				break;
+
+			case OPCPUSHOBJ:
+				val.runsv.runsvobj = osrp2(p);
+				runpush(ctx, DAT_OBJECT, &val);
+				p += 2;
+				break;
+
+			case OPCPUSHSELF:
+				val.runsv.runsvobj = self;
+				runpush(ctx, DAT_OBJECT, &val);
+				break;
+
+			case OPCPUSHSTR:
+				val.runsv.runsvstr = p;
+				runpush(ctx, DAT_SSTRING, &val);
+				p += osrp2(p);                              /* skip past string */
+				break;
+
+			case OPCPUSHLST:
+				val.runsv.runsvstr = p;
+				runpush(ctx, DAT_LIST, &val);
+				p += osrp2(p);                                /* skip past list */
+				break;
+
+			case OPCPUSHNIL:
+				runpush(ctx, DAT_NIL, &val);
+				break;
+
+			case OPCPUSHTRUE:
+				runpush(ctx, DAT_TRUE, &val);
+				break;
+
+			case OPCPUSHFN:
+				val.runsv.runsvobj = osrp2(p);
+				runpush(ctx, DAT_FNADDR, &val);
+				p += 2;
+				break;
+
+			case OPCPUSHPN:
+				val.runsv.runsvprp = osrp2(p);
+				runpush(ctx, DAT_PROPNUM, &val);
+				p += 2;
+				break;
+
+			case OPCNEG:
+				val.runstyp = DAT_NUMBER;
+				val.runsv.runsvnum = -runpopnum(ctx);
+				runrepush(ctx, &val);
+				break;
+
+			case OPCBNOT:
+				val.runstyp = DAT_NUMBER;
+				val.runsv.runsvnum = ~runpopnum(ctx);
+				runrepush(ctx, &val);
+				break;
+
+			case OPCNOT:
+				if (runtoslog(ctx))
+					runpush(ctx, runclog(!runpoplog(ctx)), &val);
+				else
+					runpush(ctx, runclog(runpopnum(ctx)), &val);
+				break;
+
+			case OPCADD:
+				runpop(ctx, &val2);    /* right op is pushed last -> popped 1st */
+				runpop(ctx, &val);
+				runadd(ctx, &val, &val2, 2);
+				runrepush(ctx, &val);
+				break;
+
+			case OPCSUB:
+				runpop(ctx, &val2);    /* right op is pushed last -> popped 1st */
+				runpop(ctx, &val);
+				(void)runsub(ctx, &val, &val2, 2);
+				runrepush(ctx, &val);
+				break;
+
+			case OPCMUL:
+				val.runstyp = DAT_NUMBER;
+				val.runsv.runsvnum = runpopnum(ctx);
+				val.runsv.runsvnum *= runpopnum(ctx);
+				runrepush(ctx, &val);
+				break;
+
+			case OPCBAND:
+				val.runstyp = DAT_NUMBER;
+				val.runsv.runsvnum = runpopnum(ctx);
+				val.runsv.runsvnum &= runpopnum(ctx);
+				runrepush(ctx, &val);
+				break;
+
+			case OPCBOR:
+				val.runstyp = DAT_NUMBER;
+				val.runsv.runsvnum = runpopnum(ctx);
+				val.runsv.runsvnum |= runpopnum(ctx);
+				runrepush(ctx, &val);
+				break;
+
+			case OPCSHL:
+				val.runstyp = DAT_NUMBER;
+				val.runsv.runsvnum = runpopnum(ctx);
+				val.runsv.runsvnum = runpopnum(ctx) << val.runsv.runsvnum;
+				runrepush(ctx, &val);
+				break;
+
+			case OPCSHR:
+				val.runstyp = DAT_NUMBER;
+				val.runsv.runsvnum = runpopnum(ctx);
+				val.runsv.runsvnum = runpopnum(ctx) >> val.runsv.runsvnum;
+				runrepush(ctx, &val);
+				break;
+
+			case OPCXOR:
+				/* allow logical ^ logical or number ^ number */
+				if (runtoslog(ctx))
+				{
+					int a, b;
+
+					/* logicals - return a logical value */
+					a = runpoplog(ctx);
+					b = runpoplog(ctx);
+					val.runstyp = runclog(a ^ b);
+				}
+				else
+				{
+					/* numeric value - return binary xor */
+					val.runstyp = DAT_NUMBER;
+					val.runsv.runsvnum = runpopnum(ctx);
+					val.runsv.runsvnum ^= runpopnum(ctx);
+				}
+				runrepush(ctx, &val);
+				break;
+
+			case OPCDIV:
+				val.runsv.runsvnum = runpopnum(ctx);
+				if (val.runsv.runsvnum == 0)
+					runsig(ctx, ERR_DIVZERO);
+				val.runsv.runsvnum = runpopnum(ctx) / val.runsv.runsvnum;
+				val.runstyp = DAT_NUMBER;
+				runrepush(ctx, &val);
+				break;
+
+			case OPCMOD:
+				val.runsv.runsvnum = runpopnum(ctx);
+				if (val.runsv.runsvnum == 0)
+					runsig(ctx, ERR_DIVZERO);
+				val.runsv.runsvnum = runpopnum(ctx) % val.runsv.runsvnum;
+				val.runstyp = DAT_NUMBER;
+				runrepush(ctx, &val);
+				break;
+
+#ifdef NEVER
+			case OPCAND:
+				if (runtostyp(ctx) == DAT_LIST)
+					runlstisect(ctx);
+				else
+					runpush(ctx, runclog(runpoplog(ctx) && runpoplog(ctx)), &val);
+				break;
+
+			case OPCOR:
+				runpush(ctx, runclog(runpoplog(ctx) || runpoplog(ctx)), &val);
+				break;
+#endif /* NEVER */
+
+			case OPCEQ:
+				runpush(ctx, runclog(runeq(ctx)), &val);
+				break;
+
+			case OPCNE:
+				runpush(ctx, runclog(!runeq(ctx)), &val);
+				break;
+
+			case OPCLT:
+				runpush(ctx, runclog(runmcmp(ctx) < 0), &val);
+				break;
+
+			case OPCLE:
+				runpush(ctx, runclog(runmcmp(ctx) <= 0), &val);
+				break;
+
+			case OPCGT:
+				runpush(ctx, runclog(runmcmp(ctx) > 0), &val);
+				break;
+
+			case OPCGE:
+				runpush(ctx, runclog(runmcmp(ctx) >= 0), &val);
+				break;
+
+			case OPCCALL:
+			{
+				objnum o;
+
+				/* get the argument count */
+				nargc = *p++;
+
+				/* ensure we have enough values to pass as arguments */
+				runcheckargc(ctx, &nargc);
+
+				/* object could move--save offset to restore 'p' after call */
+				o = osrp2(p);
+				ofs = runcpsav(ctx, &p, target, targprop);
+
+				/* execute the function */
+				runfn(ctx, o, nargc);
+
+				/* restore code pointer in case target object moved */
+				p = runcprst(ctx, ofs, target, targprop) + 2;
+				break;
+			}
+
+			case OPCGETP:
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				prop = osrp2(p);
+				p += 2;
+				obj = runpopobj(ctx);
+				runpprop(ctx, &p, target, targprop, obj, prop, FALSE, nargc,
+					obj);
+				break;
+
+			case OPCGETPDATA:
+				prop = osrp2(p);
+				p += 2;
+				obj = runpopobj(ctx);
+				runcheckpropdata(ctx, obj, prop);
+				runpprop(ctx, &p, target, targprop, obj, prop, FALSE, 0, obj);
+				break;
+
+			case OPCGETDBLCL:
+#ifdef DBG_OFF
+				/* non-debug mode - this will always throw an error */
+				dbgfrfind(ctx->runcxdbg, 0, 0);
+#else
+				/* debug mode - look up the local in the stack frame */
+				{
+					objnum   frobj;
+					uint     frofs;
+					runsdef *otherbp;
+
+					frobj = osrp2(p);
+					frofs = osrp2(p + 2);
+					otherbp = dbgfrfind(ctx->runcxdbg, frobj, frofs);
+					runrepush(ctx, otherbp + runrp2s(p + 4) - 1);
+					p += 6;
+				}
+				break;
+#endif
+
+			case OPCGETLCL:
+				runrepush(ctx, ctx->runcxbp + runrp2s(p) - 1);
+				p += 2;
+				break;
+
+			case OPCRETURN:
+				runleave(ctx, argc /* was: osrp2(p) */);
+				dbgleave(ctx->runcxdbg, DBGEXRET);
+				goto done;
+
+			case OPCRETVAL:
+				/* if there's nothing on the stack, return nil */
+				if (runtostyp(ctx) != DAT_BASEPTR)
+					runpop(ctx, &val);
+				else
+					val.runstyp = DAT_NIL;
+
+				runleave(ctx, argc /* was: osrp2(p) */);
+				runrepush(ctx, &val);
+				dbgleave(ctx->runcxdbg, DBGEXVAL);
+				goto done;
+
+			case OPCENTER:
+				/* push old base pointer and set up new one */
+				ctx->runcxsp = rstsp;
+				val.runsv.runsvstr = (uchar *)ctx->runcxbp;
+				runpush(ctx, DAT_BASEPTR, &val);
+				ctx->runcxbp = ctx->runcxsp;
+
+				/* add a trace record */
+				dbgenter(ctx->runcxdbg, ctx->runcxbp, self, target, targprop,
+					0, argc);
+
+				/* initialize locals to nil */
+				for (i = osrp2(p); i; --i) runpush(ctx, DAT_NIL, &val);
+				p += 2;                         /* skip the local count operand */
+
+												/* save stack pointer - reset sp to this value on DISCARD */
+				rstsp = ctx->runcxsp;
+				break;
+
+			case OPCDISCARD:
+				ctx->runcxsp = rstsp;
+				break;
+
+			case OPCSWITCH:
+			{
+				int      tostyp;
+				int      match, typmatch;
+
+				runpop(ctx, &val);
+				tostyp = val.runstyp;
+				switch (tostyp)
+				{
+				case DAT_SSTRING:
+					tostyp = OPCPUSHSTR;
+					break;
+				case DAT_LIST:
+					tostyp = OPCPUSHLST;
+					break;
+				case DAT_PROPNUM:
+					tostyp = OPCPUSHPN;
+					break;
+				case DAT_FNADDR:
+					tostyp = OPCPUSHFN;
+					break;
+				case DAT_TRUE:
+					tostyp = OPCPUSHTRUE;
+					break;
+				case DAT_NIL:
+					tostyp = OPCPUSHNIL;
+					break;
+				}
+
+				p += osrp2(p);                         /* find the switch table */
+				i = osrp2(p);                            /* get number of cases */
+
+														 /* look for a matching case */
+				for (match = FALSE; i && !match; --i)
+				{
+					p += 2;                     /* skip previous jump/size word */
+					typmatch = (*p == tostyp);
+					switch (*p++)
+					{
+					case OPCPUSHNUM:
+						match = (typmatch
+							&& val.runsv.runsvnum == osrp4s(p));
+						p += 4;
+						break;
+
+					case OPCPUSHLST:
+					case OPCPUSHSTR:
+						match = (typmatch
+							&& osrp2(val.runsv.runsvstr) == osrp2(p)
+							&& !memcmp(val.runsv.runsvstr,
+								p, (size_t)osrp2(p)));
+						p += runrp2s(p);
+						break;
+
+					case OPCPUSHPN:
+						match = (typmatch
+							&& val.runsv.runsvprp == osrp2(p));
+						p += 2;
+						break;
+
+					case OPCPUSHOBJ:
+					case OPCPUSHFN:
+						match = (typmatch
+							&& val.runsv.runsvobj == osrp2(p));
+						p += 2;
+						break;
+
+					case OPCPUSHSELF:
+						match = (typmatch && val.runsv.runsvobj == self);
+						break;
+
+					case OPCPUSHTRUE:
+					case OPCPUSHNIL:
+						match = typmatch;
+						break;
+					}
+				}
+
+				if (!match) p += 2;         /* if default, skip to default case */
+				p += runrp2s(p);      /* wherever we left off, p points to jump */
+				break;
+			}
+
+			case OPCJMP:
+				p += runrp2s(p);
+				break;
+
+			case OPCJT:
+				if (runtoslog(ctx))
+					p += (runpoplog(ctx) ? runrp2s(p) : 2);
+				else
+					p += (runpopnum(ctx) != 0 ? runrp2s(p) : 2);
+				break;
+
+			case OPCJF:
+				if (runtoslog(ctx))
+					p += ((!runpoplog(ctx)) ? runrp2s(p) : 2);
+				else if (runtostyp(ctx) == DAT_NUMBER)
+					p += ((runpopnum(ctx) == 0) ? runrp2s(p) : 2);
+				else                      /* consider any other type to be true */
+				{
+					rundisc(ctx);  /* throw away the item considered to be true */
+					p += 2;
+				}
+				break;
+
+			case OPCSAY:
+				outfmt(ctx->runcxtio, p);
+				p += osrp2(p);                              /* skip past string */
+				break;
+
+			case OPCBUILTIN:
+			{
+				int      binum;
+				runsdef *stkp;
+
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				binum = osrp2(p);
+				ofs = runcpsav(ctx, &p, target, targprop);
+				stkp = ctx->runcxsp - nargc;
+
+				dbgenter(ctx->runcxdbg, ctx->runcxsp + 1, MCMONINV, MCMONINV,
+					(prpnum)0, binum, nargc);
+				(*ctx->runcxbi[binum])((struct bifcxdef *)ctx->runcxbcx,
+					nargc);
+				dbgleave(ctx->runcxdbg,
+					ctx->runcxsp != stkp ? DBGEXVAL : DBGEXRET);
+
+				p = runcprst(ctx, ofs, target, targprop);
+				p += 2;
+				break;
+			}
+
+			case OPCPTRCALL:
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				ofs = runcpsav(ctx, &p, target, targprop);
+				runfn(ctx, runpopfn(ctx), nargc);
+				p = runcprst(ctx, ofs, target, targprop);
+				break;
+
+			case OPCINHERIT:
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				prop = osrp2(p);
+				p += 2;
+				runpprop(ctx, &p, target, targprop, target, prop, TRUE, nargc,
+					self);
+				break;
+
+			case OPCPTRINH:
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				prop = runpopprp(ctx);
+				runpprop(ctx, &p, target, targprop, target, prop, TRUE, nargc,
+					self);
+				break;
+
+			case OPCPTRGETP:
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				prop = runpopprp(ctx);
+				obj = runpopobj(ctx);
+				runpprop(ctx, &p, target, targprop, obj, prop, FALSE, nargc,
+					obj);
+				break;
+
+			case OPCPTRGETPDATA:
+				prop = runpopprp(ctx);
+				obj = runpopobj(ctx);
+				runcheckpropdata(ctx, obj, prop);
+				runpprop(ctx, &p, target, targprop, obj, prop, FALSE, 0, obj);
+				break;
+
+			case OPCEXPINH:
+				/* inheritance from explicit superclass */
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				prop = osrp2(p);
+				obj = osrp2(p + 2);
+				p += 4;
+
+				/*
+				*   Evaluate the property of the given object, but keeping
+				*   the same 'self' as is currently in effect.  Note that the
+				*   'inherit' flag is FALSE in this call, even though we're
+				*   inheriting, because the opcode explicitly specifies the
+				*   object we want to inherit from.
+				*/
+				runpprop(ctx, &p, target, targprop, obj, prop, FALSE,
+					nargc, self);
+				break;
+
+			case OPCEXPINHPTR:
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				prop = runpopprp(ctx);
+				obj = osrp2(p);
+				p += 2;
+				runpprop(ctx, &p, target, targprop, obj, prop, FALSE,
+					nargc, self);
+				break;
+
+			case OPCPASS:
+				prop = osrp2(p);
+				runleave(ctx, 0);
+				dbgleave(ctx->runcxdbg, DBGEXPASS);
+				runpprop(ctx, &p, target, targprop, target, prop, TRUE, argc,
+					self);
+				goto done;
+
+			case OPCEXIT:
+				errsig(ctx->runcxerr, ERR_RUNEXIT);
+				/* NOTREACHED */
+
+			case OPCABORT:
+				errsig(ctx->runcxerr, ERR_RUNABRT);
+				/* NOTREACHED */
+
+			case OPCASKDO:
+				errsig(ctx->runcxerr, ERR_RUNASKD);
+				/* NOTREACHED */
+
+			case OPCASKIO:
+				errsig1(ctx->runcxerr, ERR_RUNASKI, ERRTINT, osrp2(p));
+				/* NOTREACHED */
+
+			case OPCJE:
+				p += (runeq(ctx) ? runrp2s(p) : 2);
+				break;
+
+			case OPCJNE:
+				p += (!runeq(ctx) ? runrp2s(p) : 2);
+				break;
+
+			case OPCJGT:
+				p += (runmcmp(ctx) > 0 ? runrp2s(p) : 2);
+				break;
+
+			case OPCJGE:
+				p += (runmcmp(ctx) >= 0 ? runrp2s(p) : 2);
+				break;
+
+			case OPCJLT:
+				p += (runmcmp(ctx) < 0 ? runrp2s(p) : 2);
+				break;
+
+			case OPCJLE:
+				p += (runmcmp(ctx) <= 0 ? runrp2s(p) : 2);
+				break;
+
+			case OPCJNAND:
+				p += (!(runpoplog(ctx) && runpoplog(ctx)) ? runrp2s(p) : 2);
+				break;
+
+			case OPCJNOR:
+				p += (!(runpoplog(ctx) || runpoplog(ctx)) ? runrp2s(p) : 2);
+				break;
+
+			case OPCGETPSELF:
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				prop = osrp2(p);
+				p += 2;
+				runpprop(ctx, &p, target, targprop, self, prop, FALSE, nargc,
+					self);
+				break;
+
+			case OPCGETPSELFDATA:
+				prop = osrp2(p);
+				p += 2;
+				runcheckpropdata(ctx, self, prop);
+				runpprop(ctx, &p, target, targprop, self, prop, FALSE, 0, self);
+				break;
+
+			case OPCGETPPTRSELF:
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				prop = runpopprp(ctx);
+				runpprop(ctx, &p, target, targprop, self, prop, FALSE, nargc,
+					self);
+				break;
+
+			case OPCGETPOBJ:
+				nargc = *p++;
+				runcheckargc(ctx, &nargc);
+				obj = osrp2(p);
+				prop = osrp2(p + 2);
+				p += 4;
+				runpprop(ctx, &p, target, targprop, obj, prop, FALSE, nargc,
+					obj);
+				break;
+
+			case OPCINDEX:
+				i = runpopnum(ctx);                                /* get index */
+				lstp = runpoplst(ctx);                          /* get the list */
+				runpind(ctx, i, lstp);
+				break;
+
+			case OPCJST:
+				if (runtostyp(ctx) == DAT_TRUE)
+					p += runrp2s(p);
+				else
+				{
+					(void)runpoplog(ctx);
+					p += 2;
+				}
+				break;
+
+			case OPCJSF:
+				if (runtostyp(ctx) == DAT_NIL ||
+					(runtostyp(ctx) == DAT_NUMBER &&
+					(ctx->runcxsp - 1)->runsv.runsvnum == 0))
+					p += runrp2s(p);
+				else
+				{
+					runpop(ctx, &val);
+					p += 2;
+				}
+				break;
+
+			case OPCCALLEXT:
+			{
+#if 0 // external functions are now obsolete
+				static runufdef uf =
+				{
+					runuftyp,  runufnpo,  runufspo,  runufdsc,
+					runufnpu,  runufspu,  runufcspu, runufsal,
+					runuflpu
+				};
+				int        fn;
+				runxdef   *ex;
+
+				runuxdef   ux;
+
+				/* set up callback context */
+				ux.runuxctx = ctx;
+				ux.runuxvec = &uf;
+				ux.runuxargc = *p++;
+
+				fn = osrp2(p);
+				p += 2;
+				ex = &ctx->runcxext[fn];
+
+				if (!ex->runxptr)
+				{
+					if ((ex->runxptr = os_exfil(ex->runxnam)) == 0)
+						runsig1(ctx, ERR_EXTLOAD, ERRTSTR, ex->runxnam);
+				}
+				if (os_excall(ex->runxptr, &ux))
+					runsig1(ctx, ERR_EXTRUN, ERRTSTR, ex->runxnam);
+#else
+				/* external functions are obsolete - throw an error */
+				runxdef *ex;
+				p += 1;
+				ex = &ctx->runcxext[osrp2(p)];
+				p += 2;
+				runsig1(ctx, ERR_EXTRUN, ERRTSTR, ex->runxnam);
+#endif
+			}
+			break;
+
+			case OPCDBGRET:
+				goto done;
+
+			case OPCCONS:
+			{
+				uint    totsiz;
+				uint    oldsiz;
+				uint    tot;
+				uint    cursiz;
+				runsdef lstend;
+
+				tot = i = osrp2(p);    /* get # of items to build into list */
+				p += 2;
+
+				/* reserve space for initial list (w/length word only) */
+				runhres(ctx, 2, 0);
+
+				/*
+				*   Set up value to point to output list, making room
+				*   for length prefix.  Remember size-so-far separately.
+				*/
+				lstend.runstyp = DAT_LIST;
+				lstend.runsv.runsvstr = ctx->runcxhp;
+				ctx->runcxhp += 2;
+				totsiz = 2;
+
+				while (i--)
+				{
+					runpop(ctx, &val);          /* get next value off stack */
+					cursiz = runsiz(&val);
+
+					/*
+					*   Set up to allocate space.  Before doing so, make
+					*   sure the list under construction is valid, to
+					*   ensure that it stays around after garbage
+					*   collection.
+					*/
+					oldsiz = totsiz;
+					totsiz += cursiz + 1;
+					oswp2(lstend.runsv.runsvstr, oldsiz);
+					ctx->runcxhp = lstend.runsv.runsvstr + oldsiz;
+					runhres2(ctx, cursiz + 1, tot - i, &val, &lstend);
+
+					/* write this item to the list */
+					runputbuf(lstend.runsv.runsvstr + oldsiz, &val);
+				}
+				oswp2(lstend.runsv.runsvstr, totsiz);
+				ctx->runcxhp = lstend.runsv.runsvstr + totsiz;
+				runrepush(ctx, &lstend);
+			}
+			break;
+
+			case OPCARGC:
+				val.runsv.runsvnum = argc;
+				runpush(ctx, DAT_NUMBER, &val);
+				break;
+
+			case OPCCHKARGC:
+				if ((*p & 0x80) ? argc < (*p & 0x7f) : argc != *p)
+				{
+					char namebuf[128];
+					size_t namelen;
+
+					/*
+					*   debugger is present - look up the name of the current
+					*   function or method, so that we can report it in the
+					*   error message
+					*/
+					if (targprop == 0)
+					{
+						/* we're in a function */
+						namelen = dbgnam(ctx->runcxdbg, namebuf, TOKSTFUNC,
+							target);
+					}
+					else
+					{
+						/* we're in an object.method */
+						namelen = dbgnam(ctx->runcxdbg, namebuf, TOKSTOBJ,
+							target);
+						namebuf[namelen++] = '.';
+						namelen += dbgnam(ctx->runcxdbg, namebuf + namelen,
+							TOKSTPROP, targprop);
+					}
+					namebuf[namelen] = '\0';
+					runsig1(ctx, ERR_ARGC, ERRTSTR, namebuf);
+				}
+				++p;
+				break;
+
+			case OPCLINE:
+			case OPCBP:
+			{
+				uchar *ptr = mcmobjptr(ctx->runcxmem, (mcmon)target);
+				uchar  instr;
+
+				/* set up the debugger frame record for this line */
+				dbgframe(ctx->runcxdbg, osrp2(p + 1), p - ptr);
+
+				/* remember the instruction */
+				instr = *(p - 1);
+
+				/* remember the offset of the line record */
+				ctx->runcxlofs = ofs = (p + 2 - ptr);
+
+				/* skip to the next instruction */
+				p += *p;
+
+				/* let the debugger take over, if it wants to */
+				dbgssi(ctx->runcxdbg, ofs, instr, 0, &p);
+				break;
+			}
+
+			case OPCFRAME:
+				/* this is a frame record - just jump past it */
+				p += osrp2(p);
+				break;
+
+			case OPCASI_MASK | OPCASIDIR | OPCASILCL:
+				runpop(ctx, &val);
+				OSCPYSTRUCT(*(ctx->runcxbp + runrp2s(p) - 1), val);
+				stkval = &val;
+				p += 2;
+				goto no_assign;
+
+			case OPCASI_MASK | OPCASIDIR | OPCASIPRP:
+				obj = runpopobj(ctx);
+				prop = osrp2(p);
+				p += 2;
+				runpop(ctx, &val);
+				stkval = valp = &val;
+				goto assign_property;
+
+			case OPCASI_MASK | OPCASIDIR | OPCASIPRPPTR:
+				prop = runpopprp(ctx);
+				obj = runpopobj(ctx);
+				runpop(ctx, &val);
+				stkval = valp = &val;
+				goto assign_property;
+
+			case OPCNEW:
+				run_new(ctx, &p, target, targprop);
+				break;
+
+			case OPCDELETE:
+				run_delete(ctx, &p, target, targprop);
+				break;
+
+			default:
+				if ((opc & OPCASI_MASK) == OPCASI_MASK)
+				{
+					runsdef  val3;
+					int      asityp;
+					int      asiext;
+					int      lclnum;
+
+					valp = &val;
+					stkval = &val;
+
+					asityp = (opc & OPCASITYP_MASK);
+					if (asityp == OPCASIEXT)
+						asiext = *p++;
+
+					/* get list element/property number if needed */
+					switch (opc & OPCASIDEST_MASK)
+					{
+					case OPCASIPRP:
+						obj = runpopobj(ctx);
+						prop = osrp2(p);
+						p += 2;
+						break;
+
+					case OPCASIPRPPTR:
+						prop = runpopprp(ctx);
+						obj = runpopobj(ctx);
+						break;
+
+					case OPCASIIND:
+						i = runpopnum(ctx);
+						lstp = runpoplst(ctx);
+						break;
+
+					case OPCASILCL:
+						lclnum = runrp2s(p);
+						p += 2;
+						break;
+					}
+
+					if (asityp != OPCASIDIR)
+					{
+						/* we have an <op>= operator - get lval, modify, & set */
+						switch (opc & OPCASIDEST_MASK)
+						{
+						case OPCASILCL:
+							OSCPYSTRUCT(val, *(ctx->runcxbp + lclnum - 1));
+							break;
+
+						case OPCASIPRP:
+						case OPCASIPRPPTR:
+							runpprop(ctx, &p, target, targprop, obj, prop,
+								FALSE, 0, obj);
+							runpop(ctx, &val);
+							break;
+
+						case OPCASIIND:
+							runpind(ctx, i, lstp);
+							runpop(ctx, &val);
+							break;
+						}
+
+						/* if saving pre-inc/dec value, get the value now */
+						if ((opc & OPCASIPRE_MASK) == OPCASIPOST)
+						{
+							OSCPYSTRUCT(val3, val);
+							stkval = &val3;
+						}
+					}
+
+					/* get rvalue, except for inc/dec operations */
+					if (asityp != OPCASIINC && asityp != OPCASIDEC)
+						runpop(ctx, &val2);
+
+					/* now apply operation to lvalue using rvalue */
+					switch (asityp)
+					{
+					case OPCASIADD:
+						if ((opc & OPCASIIND) != 0)
+						{
+							runsdef val4;
+
+							/*
+							*   we're adding to an indexed value out of a list -
+							*   we need to make sure the list is protected from
+							*   garbage collection, so push it back on the stack
+							*   while we're working
+							*/
+							val4.runstyp = DAT_LIST;
+							val4.runsv.runsvstr = lstp;
+							runrepush(ctx, &val4);
+
+							/* carry out the addition */
+							runadd(ctx, &val, &val2, 2);
+
+							/*
+							*   in case the list got moved during garbage
+							*   collection, retrieve it from the stack
+							*/
+							lstp = runpoplst(ctx);
+						}
+						else
+						{
+							/* no list indexing - just carry out the addition */
+							runadd(ctx, &val, &val2, 2);
+						}
+						break;
+
+					case OPCASISUB:
+						if ((opc & OPCASIIND) != 0)
+						{
+							runsdef val4;
+							int result;
+
+							/* as with adding, protect the list from GC */
+							val4.runstyp = DAT_LIST;
+							val4.runsv.runsvstr = lstp;
+							runrepush(ctx, &val4);
+
+							/* carry out the subtraction and note the result */
+							result = runsub(ctx, &val, &val2, 2);
+
+							/* recover the list pointer */
+							lstp = runpoplst(ctx);
+
+							/* check to see if we have an assignment */
+							if (!result)
+								goto no_assign;
+						}
+						else
+						{
+							/* no list indexing - just do the subtraction */
+							if (!runsub(ctx, &val, &val2, 2))
+								goto no_assign;
+						}
+						break;
+
+					case OPCASIMUL:
+						if (val.runstyp != DAT_NUMBER
+							|| val2.runstyp != DAT_NUMBER)
+							runsig(ctx, ERR_REQNUM);
+						val.runsv.runsvnum *= val2.runsv.runsvnum;
+						break;
+
+					case OPCASIDIV:
+						if (val.runstyp != DAT_NUMBER
+							|| val2.runstyp != DAT_NUMBER)
+							runsig(ctx, ERR_REQNUM);
+						if (val2.runsv.runsvnum == 0)
+							runsig(ctx, ERR_DIVZERO);
+						val.runsv.runsvnum /= val2.runsv.runsvnum;
+						break;
+
+					case OPCASIINC:
+						if (val.runstyp != DAT_NUMBER)
+							runsig(ctx, ERR_REQNUM);
+						++(val.runsv.runsvnum);
+						break;
+
+					case OPCASIDEC:
+						if (val.runstyp != DAT_NUMBER)
+							runsig(ctx, ERR_REQNUM);
+						--(val.runsv.runsvnum);
+						break;
+
+					case OPCASIDIR:
+						valp = stkval = &val2;
+						break;
+
+					case OPCASIEXT:
+						switch (asiext)
+						{
+						case OPCASIMOD:
+							if (val.runstyp != DAT_NUMBER
+								|| val2.runstyp != DAT_NUMBER)
+								runsig(ctx, ERR_REQNUM);
+							if (val2.runsv.runsvnum == 0)
+								runsig(ctx, ERR_DIVZERO);
+							val.runsv.runsvnum %= val2.runsv.runsvnum;
+							break;
+
+						case OPCASIBAND:
+							if ((val.runstyp == DAT_TRUE
+								|| val.runstyp == DAT_NIL)
+								&& (val2.runstyp == DAT_TRUE
+									|| val2.runstyp == DAT_NIL))
+							{
+								int a, b;
+
+								a = (val.runstyp == DAT_TRUE ? 1 : 0);
+								b = (val2.runstyp == DAT_TRUE ? 1 : 0);
+								val.runstyp = runclog(a && b);
+							}
+							else if (val.runstyp == DAT_NUMBER
+								&& val2.runstyp == DAT_NUMBER)
+								val.runsv.runsvnum &= val2.runsv.runsvnum;
+							else
+								runsig(ctx, ERR_REQNUM);
+							break;
+
+						case OPCASIBOR:
+							if ((val.runstyp == DAT_TRUE
+								|| val.runstyp == DAT_NIL)
+								&& (val2.runstyp == DAT_TRUE
+									|| val2.runstyp == DAT_NIL))
+							{
+								int a, b;
+
+								a = (val.runstyp == DAT_TRUE ? 1 : 0);
+								b = (val2.runstyp == DAT_TRUE ? 1 : 0);
+								val.runstyp = runclog(a || b);
+							}
+							else if (val.runstyp == DAT_NUMBER
+								&& val2.runstyp == DAT_NUMBER)
+								val.runsv.runsvnum |= val2.runsv.runsvnum;
+							else
+								runsig(ctx, ERR_REQNUM);
+							break;
+
+						case OPCASIXOR:
+							if ((val.runstyp == DAT_TRUE || val.runstyp == DAT_NIL)
+								&& (val2.runstyp == DAT_TRUE
+									|| val2.runstyp == DAT_NIL))
+							{
+								int a, b;
+
+								a = (val.runstyp == DAT_TRUE ? 1 : 0);
+								b = (val2.runstyp == DAT_TRUE ? 1 : 0);
+								val.runstyp = runclog(a ^ b);
+							}
+							else if (val.runstyp == DAT_NUMBER
+								&& val2.runstyp == DAT_NUMBER)
+								val.runsv.runsvnum ^= val2.runsv.runsvnum;
+							else
+								runsig(ctx, ERR_REQNUM);
+							break;
+
+						case OPCASISHL:
+							if (val.runstyp != DAT_NUMBER
+								|| val2.runstyp != DAT_NUMBER)
+								runsig(ctx, ERR_REQNUM);
+							val.runsv.runsvnum <<= val2.runsv.runsvnum;
+							break;
+
+						case OPCASISHR:
+							if (val.runstyp != DAT_NUMBER
+								|| val2.runstyp != DAT_NUMBER)
+								runsig(ctx, ERR_REQNUM);
+							val.runsv.runsvnum >>= val2.runsv.runsvnum;
+							break;
+
+						default:
+							runsig(ctx, ERR_INVOPC);
+						}
+						break;
+
+					default:
+						runsig(ctx, ERR_INVOPC);
+					}
+
+					/* write the rvalue at *valp to the lvalue */
+					switch (opc & OPCASIDEST_MASK)
+					{
+					case OPCASILCL:
+						OSCPYSTRUCT(*(ctx->runcxbp + lclnum - 1), *valp);
+						break;
+
+					case OPCASIPRP:
+					case OPCASIPRPPTR:
+					assign_property:
+					{
+						void    *valbuf;
+						uchar    outbuf[4];
+
+						switch (valp->runstyp)
+						{
+						case DAT_LIST:
+						case DAT_SSTRING:
+							valbuf = valp->runsv.runsvstr;
+							break;
+
+						case DAT_NUMBER:
+							valbuf = outbuf;
+							oswp4s(outbuf, valp->runsv.runsvnum);
+							break;
+
+						case DAT_OBJECT:
+						case DAT_FNADDR:
+							valbuf = outbuf;
+							oswp2(outbuf, valp->runsv.runsvobj);
+							break;
+
+						case DAT_PROPNUM:
+							valbuf = outbuf;
+							oswp2(outbuf, valp->runsv.runsvprp);
+							break;
+
+						default:
+							valbuf = &valp->runsv;
+							break;
+						}
+
+						ofs = runcpsav(ctx, &p, target, targprop);
+						objsetp(ctx->runcxmem, obj, prop, valp->runstyp,
+							valbuf, ctx->runcxundo);
+						p = runcprst(ctx, ofs, target, targprop);
+						break;
+					}
+
+					case OPCASIIND:
+					{
+						uint   newtot;
+						uint   newsiz;
+						uint   remsiz;
+						uint   delsiz;
+						uchar *delp;
+						uchar *remp;
+
+						/* compute sizes and pointers to various parts */
+						ofs = runindofs(ctx, i, lstp);
+						delp = lstp + ofs;        /* ptr to item to replace */
+						delsiz = datsiz(*delp, delp + 1);  /* size of *delp */
+						remsiz = osrp2(lstp) - ofs - delsiz - 1;
+						newsiz = runsiz(valp);          /* size of new item */
+						newtot = osrp2(lstp) + newsiz - delsiz;  /* new tot */
+
+						/* reserve space for the new list & copy first part */
+						{
+							runsdef rval3;
+
+							/* make sure lstp stays valid before and after */
+							rval3.runstyp = DAT_LIST;
+							rval3.runsv.runsvstr = lstp;
+							runhres3(ctx, newtot, 3, &val, &val2, &rval3);
+
+							/* update all of the pointers within lstp */
+							lstp = rval3.runsv.runsvstr;
+							delp = lstp + ofs;
+							remp = lstp + ofs + delsiz + 1;
+						}
+						memcpy(ctx->runcxhp + 2, lstp + 2, (size_t)(ofs - 2));
+
+						/* set size of new list */
+						oswp2(ctx->runcxhp, newtot);
+
+						/* copy new item into buffer */
+						runputbuf(ctx->runcxhp + ofs, valp);
+
+						/* copy remainder and update heap pointer */
+						memcpy(ctx->runcxhp + ofs + newsiz + 1, remp,
+							(size_t)remsiz);
+						val.runstyp = DAT_LIST;
+						val.runsv.runsvstr = ctx->runcxhp;
+						stkval = &val;
+						ctx->runcxhp += newtot;
+						break;
+					}
+					}
+
+				no_assign:   /* skip assignment - operation didn't change value */
+					if (*p == OPCDISCARD)
+					{
+						/* next assignment is DISCARD - deal with it now */
+						++p;
+						ctx->runcxsp = rstsp;
+					}
+					else
+						runrepush(ctx, stkval);
+				}
+				else
+					errsig(ctx->runcxerr, ERR_INVOPC);
+			}
+		}
+
+	/*
+	*   come here to return - don't use 'return' directly, since that
+	*   would not properly exit the error frame
+	*/
+done:;
+
+#ifndef DBG_OFF
+	/*
+	*   Come here to catch any errors that occur during execution of this
+	*   p-code
+	*/
+	ERRCATCH(ctx->runcxerr, err)
+	{
+		/*
+		*   if the debugger isn't present, or we're already in the
+		*   debugger, or if the debugger can't resume from errors, or if
+		*   we're not in user code (in which case the debugger can't
+		*   resume from this error even if it normally could resume from
+		*   an error), simply re-signal the error
+		*/
+		if (!dbgpresent()
+			|| ctx->runcxdbg->dbgcxfcn == 0
+			|| !dbgu_err_resume(ctx->runcxdbg)
+			|| (ctx->runcxdbg->dbgcxflg & DBGCXFIND) != 0)
+			errrse(ctx->runcxerr);
+
+		/* check the error code */
+		switch (err)
+		{
+		case ERR_RUNEXIT:
+		case ERR_RUNABRT:
+		case ERR_RUNASKD:
+		case ERR_RUNASKI:
+		case ERR_RUNQUIT:
+		case ERR_RUNRESTART:
+		case ERR_RUNEXITOBJ:
+			/* don't trap these errors - resignal it immediately */
+			errrse(ctx->runcxerr);
+
+		default:
+			/* trap other errors to the debugger */
+			break;
+		}
+
+		/* if the object was unlocked, re-lock it */
+		if (p == 0)
+			mcmlck(ctx->runcxmem, target);
+
+		/* set up after the last OPCLINE instruction */
+		p = mcmobjptr(ctx->runcxmem, (mcmon)target) + ctx->runcxlofs - 2;
+		p += *p;
+
+		/*
+		*   Keep the current error's arguments around for handling
+		*   outside of this handler, since we'll need them in dbgssi.
+		*/
+		errkeepargs(ctx->runcxerr);
+
+		/* enter the debugger with the error code */
+		dbgssi(ctx->runcxdbg, ctx->runcxlofs, OPCLINE, err, &p);
+
+		/* check the error again */
+		switch (err)
+		{
+		case ERR_ARGC:
+			/* we can't continue from this - simply return */
+			break;
+
+		default:
+			/* resume execution */
+			goto resume_from_error;
+		}
+	}
+	ERREND(ctx->runcxerr);
+#endif /* DBG_OFF */
+}
+
+/*
+*   Signal a run-time error.  This function first calls the debugger
+*   single-step function to allow the debugger to trap the error, then
+*   signals the error as usual when the debugger returns.
+*/
+void runsign(runcxdef *ctx, int err)
+{
+	/*
+	*   If the debugger isn't capable of resuming from a run-time error,
+	*   trap to the debugger now so that the user can see what happened.
+	*   Do not trap to the debugger here if the debugger can resume from
+	*   an error; instead, we'll trap in the p-code loop, since we'll be
+	*   able to resume execution from the point of the error.
+	*
+	*   Note that we can't resume from an error when there's no stack
+	*   frame, so we'll trap to the debugger here in that case.
+	*/
+	if (ctx->runcxdbg->dbgcxfcn == 0
+		|| !dbgu_err_resume(ctx->runcxdbg))
+		dbgssi(ctx->runcxdbg, ctx->runcxlofs, OPCLINE, err, 0);
+
+	/* signal the error */
+	errsign(ctx->runcxerr, err, "TADS");
+}
 
 } // End of namespace TADS2
 } // End of namespace TADS
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index b6694bf..71b8d78 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -46,14 +46,10 @@ namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-/* Forward declarations */
-struct runcxdef;
-struct runufdef;
-struct voccxdef;
+/* forward declarations */
+struct bifcxdef;
 
-/**
- * Stack element - the stack is an array of these structures
- */
+/* stack element - the stack is an array of these structures */
 struct runsdef {
     uchar  runstyp;                                      /* type of element */
     union {
@@ -61,50 +57,36 @@ struct runsdef {
         objnum  runsvobj;                                   /* object value */
         prpnum  runsvprp;                          /* property number value */
         uchar  *runsvstr;                              /* string/list value */
-    }      runsv;
-
-
-	/**
-	 * Determine size of a data item
-	 */
-	int runsiz() const;
+    } runsv;
 };
 
-/**
- * External function control structure
- */
+/* external function control structure */
 struct runxdef {
     char    runxnam[TOKNAMMAX + 1];            /* name of external function */
     int   (*runxptr)(void *);          /* pointer to memory containing code */
 };
 
-/**
- * External function context structure - passed to user exits
- */
+/* external function context structure - passed to user exits */
 struct runuxdef {
-    runcxdef *runuxctx;		/* run-time context */
-    runufdef *runuxvec;		/* vector of functions */
-    int runuxargc;					/* count of arguments to function */
+    struct runcxdef osfar_t *runuxctx;                  /* run-time context */
+    struct runufdef osfar_t *runuxvec;               /* vector of functions */
+    int                      runuxargc;   /* count of arguments to function */
 };
 
-/**
- * External function callback vector
- */
+/* external function callback vector */
 struct runufdef {
-    int    (*runuftyp)(runuxdef *);         /* type of top of stack */
-    long   (*runufnpo)(runuxdef *);                 /* pop a number */
-    uchar *(*runufspo)(runuxdef *);                 /* pop a string */
-    void   (*runufdsc)(runuxdef *); /* discard item at top of stack */
-    void   (*runufnpu)(runuxdef *, long);          /* push a number */
-    void   (*runufspu)(runuxdef *, uchar *); /* push alloc'd string */
-    void   (*runufcspu)(runuxdef *, char *);     /* push a C-string */
-    uchar *(*runufsal)(runuxdef *, int);   /* allocate a new string */
-    void   (*runuflpu)(runuxdef *, int);/* push DAT_TRUE or DAT_NIL */
+    int    (osfar_t *runuftyp)(runuxdef *);         /* type of top of stack */
+    long   (osfar_t *runufnpo)(runuxdef *);                 /* pop a number */
+    uchar *(osfar_t *runufspo)(runuxdef *);                 /* pop a string */
+    void   (osfar_t *runufdsc)(runuxdef *); /* discard item at top of stack */
+    void   (osfar_t *runufnpu)(runuxdef *, long);          /* push a number */
+    void   (osfar_t *runufspu)(runuxdef *, uchar *); /* push alloc'd string */
+    void   (osfar_t *runufcspu)(runuxdef *, char *);     /* push a C-string */
+    uchar *(osfar_t *runufsal)(runuxdef *, int);   /* allocate a new string */
+    void   (osfar_t *runuflpu)(runuxdef *, int);/* push DAT_TRUE or DAT_NIL */
 };
 
-/**
- * Execution context
- */
+/* execution context */
 struct runcxdef {
     errcxdef   *runcxerr;                       /* error management context */
     mcmcxdef   *runcxmem;    /* cache manager context for object references */
@@ -120,8 +102,8 @@ struct runcxdef {
     void       *runcxbcx;        /* context for built-in callback functions */
     void     (**runcxbi)(struct bifcxdef *ctx, int argc);
                                                       /* built-in functions */
-    dbgcxdef *runcxdbg;                          /* debugger context */
-    voccxdef *runcxvoc;             /* player command parser context */
+    struct dbgcxdef *runcxdbg;                          /* debugger context */
+    struct voccxdef *runcxvoc;             /* player command parser context */
     void      (*runcxdmd)(void *ctx, objnum obj, prpnum prp);
                                          /* demand-loader callback function */
     void       *runcxdmc;                 /* demand-loader callback context */
@@ -130,120 +112,66 @@ struct runcxdef {
     uint        runcxlofs;        /* offset of last line record encountered */
     char       *runcxgamename;                     /* name of the .GAM file */
     char       *runcxgamepath;      /* absolute directory path of .GAM file */
-
-	/**
-	 * Execute a function, given the function object number
-	 */
-	void runfn(noreg objnum objn, int argc);
-
-	/**
-	 * Execute p-code given a pointer to the code.  p is the actual pointer
-	 * to the first byte of code to be executed. self is the object to be
-	 * used for the special 'self' pseudo-object, and target is the object
-	 * whose data are actually being executed.  targprop is the property being
-	 * executed; 0 is used for functions.
-	 */
-	void runexe(uchar *p, objnum self, objnum target,
-		prpnum targprop, int argc);
-
-	/**
-	 * Push a value onto the stack
-	 */
-	void runpush(dattyp typ, runsdef *val);
-
-	/**
-	 * Push a value onto the stack that's already in the heap
-	 */
-	void runrepush(runsdef *val);
-
-	/**
-	 * Push a number onto the stack
-	 */
-	void runpnum(long val);
-
-	/**
-	 * Push an object onto the stack
-	 */
-	void runpobj(objnum obj);
-
-	/**
-	 * push nil
-	 */
-	void runpnil(runcxdef *ctx);
-
-	/**
-	 * push a value onto the stack from a buffer (propdef, list)
-	 */
-	void runpbuf(int typ, void *val);
-
-	/**
-	 * Push a counted-length string onto the stack
-	 */
-	void runpstr(char *str, int len, int sav);
-
-	/**
-	 * Push a C-style string onto the stack, converting escape codes.  If
-	 * the character contains backslashes, newline, or tab characters, we'll
-	 * convert these characters to their escaped equivalent.
-	 */
-	void runpushcstr(char *str, size_t len, int sav);
-
-	/**
-	 * Push a property onto the stack.  codepp is a pointer to the caller's
-	 * code pointer, which will be updated if necessary; callobj and
-	 * callofsp are the object and starting offset within the object of the
-	 * code being executed by the caller, which are needed to update
-	 * *codepp.  Property 0 is used if a function is being executed.  obj
-	 * and prop are the object and property number whose value is to be
-	 * pushed.  If 'inh' is TRUE, it means that only a property inherited
-	 * by 'obj' is to be considered; this is used for "pass"/"inherited"
-	 * operations, with the current target object given as 'obj'.
-	 */
-	void runpprop(uchar *noreg *codepp, objnum callobj,
-		prpnum callprop, noreg objnum obj, prpnum prop, int inh,
-		int argc, objnum self);
-
-	/**
-	 * compare magnitudes of numbers/strings on top of stack; strcmp-like value
-	 */
-	int runmcmp(runcxdef *ctx);
-
-	/**
-	 * True if items at top of stack are equal, FALSE otherwise
-	 */
-	int runeq(runcxdef *ctx);
-
-	/**
-	 * Garbage collect heap, making sure 'siz' bytes are available afterwards
-	 */
-	void runhcmp(uint siz, uint below,
-		runsdef *val1, runsdef *val2, runsdef *val3);
-
-	/**
-	 * Find a sublist within a list, returning pointer to sublist or null
-	 */
-	uchar *runfind(uchar *list, runsdef *item);
-
-	/**
-	 * Restore code pointer from object.property + offset
-	 */
-	uchar *runcprst(uint ofs, objnum obj, prpnum prop);
-
-	/**
-	 * Add two runsdef values, returning result in *val
-	 */
-	void runadd(runsdef *val2, uint below);
-
-	/**
-	 * Subtract val2 from val, returning result in *val; return TRUE if
-	 * value changed, FALSE otherwise (this is returned when subtracting
-	 * something from a list that isn't in the list)
-	 */
-	int runsub(runsdef *val2, uint below);
 };
 
+/* execute a function, given the function object number */
+void runfn(runcxdef *ctx, noreg objnum objn, int argc);
+
+/*
+ *   Execute p-code given a pointer to the code.  p is the actual pointer
+ *   to the first byte of code to be executed. self is the object to be
+ *   used for the special 'self' pseudo-object, and target is the object
+ *   whose data are actually being executed.  targprop is the property being
+ *   executed; 0 is used for functions. 
+ */
+void runexe(runcxdef *ctx, uchar *p, objnum self, objnum target,
+            prpnum targprop, int argc);
+
+/* push a value onto the stack */
+void runpush(runcxdef *ctx, dattyp typ, runsdef *val);
+
+/* push a value onto the stack that's already in the heap */
+void runrepush(runcxdef *ctx, runsdef *val);
+
+/* push a number onto the stack */
+void runpnum(runcxdef *ctx, long val);
+
+/* push an object onto the stack */
+void runpobj(runcxdef *ctx, objnum obj);
+
+/* push nil */
+void runpnil(runcxdef *ctx);
+
+/* push a value onto the stack from a buffer (propdef, list) */
+void runpbuf(runcxdef *ctx, int typ, void *val);
+
+/* push a counted-length string onto the stack */
+void runpstr(runcxdef *ctx, char *str, int len, int sav);
+
+/*
+ *   Push a C-style string onto the stack, converting escape codes.  If
+ *   the character contains backslashes, newline, or tab characters, we'll
+ *   convert these characters to their escaped equivalent.
+ */
+void runpushcstr(runcxdef *ctx, char *str, size_t len, int sav);
+
+/*
+ *   Push a property onto the stack.  codepp is a pointer to the caller's
+ *   code pointer, which will be updated if necessary; callobj and
+ *   callofsp are the object and starting offset within the object of the
+ *   code being executed by the caller, which are needed to update
+ *   *codepp.  Property 0 is used if a function is being executed.  obj
+ *   and prop are the object and property number whose value is to be
+ *   pushed.  If 'inh' is TRUE, it means that only a property inherited
+ *   by 'obj' is to be considered; this is used for "pass"/"inherited"
+ *   operations, with the current target object given as 'obj'.
+ */
+void runpprop(runcxdef *ctx, uchar *noreg *codepp, objnum callobj,
+              prpnum callprop, noreg objnum obj, prpnum prop, int inh,
+              int argc, objnum self);
+
 /* top level runpprop, when caller is not executing in an object */
-/* void runppr(objnum obj, prpnum prp, int argc); */
+/* void runppr(runcxdef *ctx, objnum obj, prpnum prp, int argc); */
 #define runppr(ctx, obj, prp, argc) \
  runpprop(ctx, (uchar **)0, (objnum)0, (prpnum)0, obj, prp, FALSE, argc, obj)
 
@@ -252,7 +180,7 @@ struct runcxdef {
 #define rundisc(ctx) (runstkund(ctx), (--((ctx)->runcxsp)))
 
 /* pop the top element on the stack */
-/* void runpop(runsdef *val); */
+/* void runpop(runcxdef *ctx, runsdef *val); */
 #define runpop(ctx, v) \
  (runstkund(ctx), memcpy(v, (--((ctx)->runcxsp)), (size_t)sizeof(runsdef)))
 
@@ -327,6 +255,17 @@ struct runcxdef {
 /* int runclog(int log); */
 #define runclog(l) ((l) ? DAT_TRUE : DAT_NIL)
 
+/* compare magnitudes of numbers/strings on top of stack; strcmp-like value */
+int runmcmp(runcxdef *ctx);
+
+/* TRUE if items at top of stack are equal, FALSE otherwise */
+int runeq(runcxdef *ctx);
+
+/* check for stack underflow */
+/* void runstkund(runcxdef *ctx); */
+
+/* check for stack overflow */
+/* void runstkovf(runcxdef *ctx); */
 
 /* 
  *   Check to ensure we have enough arguments to pass to a function or method
@@ -352,7 +291,7 @@ struct runcxdef {
 #endif /* RUNFAST */
 
 /* reserve space in heap, collecting garbage if necessary */
-/* void runhres(uint siz, uint below); */
+/* void runhres(runcxdef *ctx, uint siz, uint below); */
 #define runhres(ctx, siz, below) \
  ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \
  (runhcmp(ctx, siz, below, (runsdef *)0, (runsdef *)0, (runsdef *)0),\
@@ -371,9 +310,31 @@ struct runcxdef {
  ((uint)((ctx)->runcxhtop - (ctx)->runcxhp) > (uint)(siz) ? DISCARD 0 : \
  (runhcmp(ctx, siz, below, val1, val2, val3), DISCARD 0))
 
+/* garbage collect heap, making sure 'siz' bytes are available afterwards */
+void runhcmp(runcxdef *ctx, uint siz, uint below,
+             runsdef *val1, runsdef *val2, runsdef *val3);
+
+/* determine size of a data item */
+int runsiz(runsdef *item);
+
+/* find a sublist within a list, returning pointer to sublist or NULL */
+uchar *runfind(uchar *list, runsdef *item);
+
+/* add two runsdef values, returning result in *val */
+void runadd(runcxdef *ctx, runsdef *val, runsdef *val2, uint below);
+
+/*
+ *   subtract val2 from val, returning result in *val; return TRUE if
+ *   value changed, FALSE otherwise (this is returned when subtracting
+ *   something from a list that isn't in the list) 
+ */
+int runsub(runcxdef *ctx, runsdef *val, runsdef *val2, uint below);
+
+/* restore code pointer from object.property + offset */
+uchar *runcprst(runcxdef *ctx, uint ofs, objnum obj, prpnum prop);
 
 /* leave a stack frame, removing arguments */
-/* void runleave(uint parms); */
+/* void runleave(runcxdef *ctx, uint parms); */
 #define runleave(ctx, parms) \
  (((ctx)->runcxsp = (ctx)->runcxbp), \
   ((ctx)->runcxbp = (runsdef *)((--((ctx)->runcxsp))->runsv.runsvstr)), \
@@ -385,6 +346,27 @@ struct runcxdef {
                      ((ctx)->runcxhp = (ctx)->runcxheap), \
                      dbgrst(ctx->runcxdbg))
 
+/* set up runtime status line display */
+void runistat(struct voccxdef *vctx, struct runcxdef *rctx,
+                  struct tiocxdef *tctx);
+
+/* signal a run-time error - allows debugger trapping */
+void runsign(runcxdef *ctx, int err);
+
+/* sign a run-time error with zero arguments */
+#define runsig(ctx, err) (errargc((ctx)->runcxerr,0),runsign(ctx,err))
+
+/* signal a run-time error with one argument */
+#define runsig1(ctx, err, typ, arg) \
+  (errargv((ctx)->runcxerr,0,typ,arg),errargc((ctx)->runcxerr,1),\
+   runsign(ctx,err))
+
+/* draw status line */
+void runstat(void);
+
+/* initialize output status */
+void runistat(struct voccxdef *vctx, struct runcxdef *rctx,
+              struct tiocxdef *tctx);
 
 } // End of namespace TADS2
 } // End of namespace TADS
diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp
index 96bd2f1..3f9dc6e 100644
--- a/engines/glk/tads/tads2/tokenizer.cpp
+++ b/engines/glk/tads/tads2/tokenizer.cpp
@@ -24,6 +24,7 @@
 #include "glk/tads/tads2/error.h"
 #include "glk/tads/tads2/memory_cache_heap.h"
 #include "glk/tads/tads2/os.h"
+#include "glk/tads/os_glk.h"
 
 namespace Glk {
 namespace TADS {
diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp
index 516e9df..8c4fead 100644
--- a/engines/glk/tads/tads2/vocabulary.cpp
+++ b/engines/glk/tads/tads2/vocabulary.cpp
@@ -24,6 +24,7 @@
 #include "glk/tads/tads2/vocabulary.h"
 #include "glk/tads/tads2/error.h"
 #include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/os_glk.h"
 
 namespace Glk {
 namespace TADS {


Commit: f607792fa4e1f024dd8265034ac84425bde4aee7
    https://github.com/scummvm/scummvm/commit/f607792fa4e1f024dd8265034ac84425bde4aee7
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS2: More code files implemented

Changed paths:
  A engines/glk/tads/tads2/opcode.h
  A engines/glk/tads/tads2/post_compilation.cpp
  R engines/glk/tads/tads2/memory_cache_loader.cpp
  R engines/glk/tads/tads2/opcode_defs.h
    engines/glk/module.mk
    engines/glk/tads/osfrobtads.cpp
    engines/glk/tads/osfrobtads.h
    engines/glk/tads/tads2/file_io.cpp
    engines/glk/tads/tads2/line_source_file.cpp
    engines/glk/tads/tads2/memory_cache.cpp
    engines/glk/tads/tads2/memory_cache_swap.cpp
    engines/glk/tads/tads2/object.cpp
    engines/glk/tads/tads2/run.h


diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 93eff12..ef5dcb3 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -103,10 +103,11 @@ MODULE_OBJS := \
 	tads/tads2/list.o \
 	tads/tads2/memory_cache.o \
 	tads/tads2/memory_cache_heap.o \
-	tads/tads2/memory_cache_loader.o \
 	tads/tads2/memory_cache_swap.o \
+	tads/tads2/object.o \
 	tads/tads2/os.o \
 	tads/tads2/output.o \
+	tads/tads2/post_compilation.o \
 	tads/tads2/regex.o \
 	tads/tads2/run.o \
 	tads/tads2/tads2.o \
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index df265be..eabd297 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -46,7 +46,7 @@ int osfrb(osfildef *fp, void *buf, size_t count) {
 	return dynamic_cast<Common::ReadStream *>(fp)->read(buf, count);
 }
 
-bool osfwb(osfildef *fp, void *buf, size_t count) {
+bool osfwb(osfildef *fp, const void *buf, size_t count) {
 	return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count;
 }
 
@@ -58,5 +58,39 @@ osfildef *osfopwt(const char *fname, os_filetype_t typ) {
 	return osfoprwtb(fname, typ);
 }
 
+int osfseek(osfildef *fp, int ofs, int origin) {
+	return dynamic_cast<Common::SeekableReadStream *>(fp)->seek(ofs, origin);
+}
+
+int osfpos(osfildef *fp) {
+	return dynamic_cast<Common::SeekableReadStream *>(fp)->pos();
+}
+
+char *osfgets(char *buf, size_t count, osfildef *fp) {
+	Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(fp);
+	char *ptr = buf;
+	char c;
+	while (!rs->eos() && --count > 0) {
+		c = rs->readByte();
+		if (c == '\n' || c == '\0')
+			break;
+		*ptr++ = c;
+	}
+
+	*ptr++ = '\0';
+	return buf;
+}
+
+bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) {
+	Common::String name = !flen ? Common::String(fname) : Common::String(fname, fname + flen);
+
+	if (!Common::File::exists(fname))
+		return false;
+
+	strncpy(buf, name.c_str(), bufsiz - 1);
+	buf[bufsiz - 1] = '\0';
+	return true;
+}
+
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index ce1624d..5c9bcd8 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -181,14 +181,14 @@ osfildef *osfoprwt(const char *fname, os_filetype_t typ);
 /* Open text file for reading/writing.  If the file already exists,
  * truncate the existing contents.  Create a new file if it doesn't
  * already exist. */
-#define osfoprwtt(fname,typ) (fopen((fname),"w+"))
+#define osfoprwtt(fname,typ) osfoprwt
 
 /* Open binary file for writing. */
-#define osfopwb(fname,typ) (fopen((fname),"wb"))
+#define osfopwb(fname,typ) osfoprwtb(fname, typ)
 
 /* Open source file for reading - use the appropriate text or binary
  * mode. */
-#define osfoprs osfoprt
+#define osfoprs osfoprb
 
 /* Open binary file for reading. */
 inline osfildef *osfoprb(const char *fname, os_filetype_t typ);
@@ -205,13 +205,13 @@ osfoprwb(const char *fname, os_filetype_t typ);
 inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
 
 /* Get a line of text from a text file. */
-#define osfgets fgets
+char *osfgets(char *buf, size_t count, osfildef *fp);
 
 /* Write a line of text to a text file. */
 #define osfputs fputs
 
 /* Write bytes to file. */
-inline bool osfwb(osfildef *fp, void *buf, size_t count);
+inline bool osfwb(osfildef *fp, const void *buf, size_t count);
 
 /* Flush buffered writes to a file. */
 inline void osfflush(osfildef *fp);
@@ -220,13 +220,13 @@ inline void osfflush(osfildef *fp);
 int osfrb(osfildef *fp, void *buf, size_t count);
 
 /* Read bytes from file and return the number of bytes read. */
-#define osfrbc(fp,buf,bufl) (fread((buf),1,(bufl),(fp)))
+#define osfrbc(fp,buf,bufl) osfrb(fp,buf,bufl)
 
 /* Get the current seek location in the file. */
-#define osfpos ftell
+inline int osfpos(osfildef *fp);
 
 /* Seek to a location in the file. */
-#define osfseek fseek
+inline int osfseek(osfildef *fp, int ofs, int origin);
 
 /* Close a file. */
 #define osfcls delete
@@ -264,6 +264,8 @@ int os_file_stat( const char* fname, int follow_links,
  */
 #define os_progress(fname,linenum)
 
+bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz);
+
 } // End of namespace TADS
 } // End of namespace Glk
 
diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp
index 581601f..d82f67a 100644
--- a/engines/glk/tads/tads2/file_io.cpp
+++ b/engines/glk/tads/tads2/file_io.cpp
@@ -21,12 +21,1754 @@
  */
 
 #include "glk/tads/tads2/file_io.h"
+#include "glk/tads/tads2/appctx.h"
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
 
+/* compare a resource string */
+/* int fioisrsc(uchar *filbuf, char *refnam); */
+#define fioisrsc(filbuf, refnam) \
+  (((filbuf)[0] == strlen(refnam)) && \
+   !memcmp(filbuf+1, refnam, (size_t)((filbuf)[0])))
+
+/* callback to load an object on demand */
+void OS_LOADDS fioldobj(void *ctx0, mclhd handle, uchar *ptr, ushort siz)
+{
+    fiolcxdef  *ctx = (fiolcxdef *)ctx0;
+    ulong       seekpos = (ulong)handle;
+    osfildef   *fp = ctx->fiolcxfp;
+    char        buf[7];
+    errcxdef   *ec = ctx->fiolcxerr;
+    uint        rdsiz;
+    
+    /* figure out what type of object is to be loaded */
+    osfseek(fp, seekpos + ctx->fiolcxst, OSFSK_SET);
+    if (osfrb(fp, buf, 7)) errsig(ec, ERR_LDGAM);
+    switch(buf[0])
+    {
+    case TOKSTFUNC:
+        rdsiz = osrp2(buf + 3);
+        break;
+
+    case TOKSTOBJ:
+        rdsiz = osrp2(buf + 5);
+        break;
+        
+    case TOKSTFWDOBJ:
+    case TOKSTFWDFN:
+    default:
+        errsig(ec, ERR_UNKOTYP);
+    }
+    
+    if (siz < rdsiz) errsig(ec, ERR_LDBIG);
+    if (osfrb(fp, ptr, rdsiz)) errsig(ec, ERR_LDGAM);
+    if (ctx->fiolcxflg & FIOFCRYPT)
+        fioxor(ptr, rdsiz, ctx->fiolcxseed, ctx->fiolcxinc);
+}
+
+/* shut down load-on-demand subsystem (close load file) */
+void fiorcls(fiolcxdef *ctx)
+{
+    if (ctx != 0 && ctx->fiolcxfp != 0)
+    {
+        /* close the file */
+        osfcls(ctx->fiolcxfp);
+
+        /* forget the file object */
+        ctx->fiolcxfp = 0;
+    }
+}
+
+/*
+ *   Read an HTMLRES resource map 
+ */
+static void fiordhtml(errcxdef *ec, osfildef *fp, appctxdef *appctx,
+                      int resfileno, const char *resfilename)
+{
+    uchar buf[256];
+
+    /* 
+     *   resource map - if the host system is interested, tell it about it 
+     */
+    if (appctx != 0)
+    {
+        ulong entry_cnt;
+        ulong i;
+        
+        /* read the index table header */
+        if (osfrb(fp, buf, 8))
+            errsig1(ec, ERR_RDRSC, ERRTSTR,
+                    errstr(ec, resfilename, strlen(resfilename)));
+
+        /* get the number of entries in the table */
+        entry_cnt = osrp4(buf);
+        
+        /* read the index entries */
+        for (i = 0 ; i < entry_cnt ; ++i)
+        {
+            ulong res_ofs;
+            ulong res_siz;
+            ushort res_namsiz;
+            
+            /* read this entry */
+            if (osfrb(fp, buf, 10))
+                errsig1(ec, ERR_RDRSC, ERRTSTR,
+                        errstr(ec, resfilename, strlen(resfilename)));
+
+            /* get the entry header */
+            res_ofs = osrp4(buf);
+            res_siz = osrp4(buf + 4);
+            res_namsiz = osrp2(buf + 8);
+            
+            /* read this entry's name */
+            if (osfrb(fp, buf, res_namsiz))
+                errsig1(ec, ERR_RDRSC, ERRTSTR,
+                        errstr(ec, resfilename, strlen(resfilename)));
+            
+            /* tell the host system about this entry */
+            if (appctx->add_resource)
+                (*appctx->add_resource)(appctx->add_resource_ctx,
+                                        res_ofs, res_siz,
+                                        (char *)buf,
+                                        (size_t)res_namsiz,
+                                        resfileno);
+        }
+        
+        /* tell the host system where the resources start */
+        if (appctx->set_resmap_seek != 0)
+        {
+            long pos = osfpos(fp);
+            (*appctx->set_resmap_seek)(appctx->set_resmap_seek_ctx,
+                                       pos, resfileno);
+        }
+    }
+}
+
+/*
+ *   Read an external resource file.  This is a limited version of the
+ *   general file reader that can only read resource files, not full game
+ *   files.  
+ */
+static void fiordrscext(errcxdef *ec, osfildef *fp, appctxdef *appctx,
+                        int resfileno, char *resfilename)
+{
+    uchar buf[TOKNAMMAX + 50];
+    unsigned long endpos;
+    unsigned long startofs;
+
+    /* note the starting offset */
+    startofs = osfpos(fp);
+    
+    /* check file and version headers, and get flags and timestamp */
+    if (osfrb(fp, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2)))
+        errsig1(ec, ERR_RDRSC, ERRTSTR,
+                errstr(ec, resfilename, strlen(resfilename)));
+    if (memcmp(buf, FIOFILHDRRSC, (size_t)sizeof(FIOFILHDRRSC)))
+        errsig1(ec, ERR_BADHDRRSC, ERRTSTR,
+                errstr(ec, resfilename, strlen(resfilename)));
+    if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR,
+               (size_t)sizeof(FIOVSNHDR))
+        && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2,
+                  (size_t)sizeof(FIOVSNHDR2))
+        && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3,
+                  (size_t)sizeof(FIOVSNHDR3)))
+        errsig(ec, ERR_BADVSN);
+    if (osfrb(fp, buf, (size_t)26))
+        errsig1(ec, ERR_RDRSC, ERRTSTR,
+                errstr(ec, resfilename, strlen(resfilename)));
+
+    /* now read resources from the file */
+    for (;;)
+    {
+        /* read resource type and next-resource pointer */
+        if (osfrb(fp, buf, 1)
+            || osfrb(fp, buf + 1, (int)(buf[0] + 4)))
+            errsig1(ec, ERR_RDRSC, ERRTSTR,
+                    errstr(ec, resfilename, strlen(resfilename)));
+        endpos = osrp4(buf + 1 + buf[0]);
+
+        /* check the resource type */
+        if (fioisrsc(buf, "HTMLRES"))
+        {
+            /* read the HTML resource map */
+            fiordhtml(ec, fp, appctx, resfileno, resfilename);
+
+            /* 
+             *   skip the resources - they're entirely for the host
+             *   application's use 
+             */
+            osfseek(fp, endpos + startofs, OSFSK_SET);
+        }
+        else if (fioisrsc(buf, "$EOF"))
+        {
+            /* we're done reading the file */
+            break;
+        }
+        else
+            errsig(ec, ERR_UNKRSC);
+    }
+}
+
+/*
+ *   read a game from a binary file
+ *
+ *   flags:
+ *      &1 ==> run preinit
+ *      &2 ==> preload objects
+ */
+static void fiord1(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx,
+                   osfildef *fp, const char *fname,
+                   fiolcxdef *setupctx, ulong startofs,
+                   objnum *preinit, uint *flagp, tokpdef *path,
+                   uchar **fmtsp, uint *fmtlp, uint *pcntptr, int flags,
+                   appctxdef *appctx, char *argv0)
+{
+    int         i;
+    int         siz;
+    uchar       buf[TOKNAMMAX + 50];
+    errcxdef   *ec = vctx->voccxerr;
+    ulong       endpos;
+    int         obj;
+    ulong       curpos;
+    runxdef    *ex;
+    ulong       eof_reset = 0;             /* reset here at EOF if non-zero */
+#if 0  // XFCNs are obsolete
+    int         xfcns_done = FALSE;                 /* already loaded XFCNs */
+#endif
+    ulong       xfcn_pos = 0;          /* location of XFCN's if preloadable */
+    uint        xor_seed = 17;                     /* seed value for fioxor */
+    uint        xor_inc = 29;                 /* increment value for fioxor */
+
+    /* set up loader callback context */
+    setupctx->fiolcxfp = fp;
+    setupctx->fiolcxerr = ec;
+    setupctx->fiolcxst = startofs;
+    setupctx->fiolcxseed = xor_seed;
+    setupctx->fiolcxinc = xor_inc;
+
+    /* check file and version headers, and get flags and timestamp */
+    if (osfrb(fp, buf, (int)(sizeof(FIOFILHDR) + sizeof(FIOVSNHDR) + 2)))
+        errsig(ec, ERR_RDGAM);
+    if (memcmp(buf, FIOFILHDR, (size_t)sizeof(FIOFILHDR)))
+         errsig(ec, ERR_BADHDR);
+    if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR,
+               (size_t)sizeof(FIOVSNHDR))
+        && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2,
+                  (size_t)sizeof(FIOVSNHDR2))
+        && memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3,
+                  (size_t)sizeof(FIOVSNHDR3)))
+        errsig(ec, ERR_BADVSN);
+    if (osfrb(fp, vctx->voccxtim, (size_t)26)) errsig(ec, ERR_RDGAM);
+
+    /* 
+     *   if the game wasn't compiled with 2.2 or later, make a note,
+     *   because we need to ignore certain property flags (due to a bug in
+     *   the old compiler) 
+     */
+    if (memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR2,
+               (size_t)sizeof(FIOVSNHDR2)) == 0
+        || memcmp(buf + sizeof(FIOFILHDR), FIOVSNHDR3,
+                  (size_t)sizeof(FIOVSNHDR3)) == 0)
+        mctx->mcmcxflg |= MCMCXF_NO_PRP_DEL;
+
+    setupctx->fiolcxflg =
+        *flagp = osrp2(buf + sizeof(FIOFILHDR) + sizeof(FIOVSNHDR));
+    
+    /* now read resources from the file */
+    for (;;)
+    {
+        /* read resource type and next-resource pointer */
+        if (osfrb(fp, buf, 1)
+            || osfrb(fp, buf + 1, (int)(buf[0] + 4)))
+            errsig(ec, ERR_RDGAM);
+        endpos = osrp4(buf + 1 + buf[0]);
+        
+        if (fioisrsc(buf, "OBJ"))
+        {
+            /* skip regular objects if fast-load records are included */
+            if (*flagp & FIOFFAST)
+            {
+                osfseek(fp, endpos + startofs, OSFSK_SET);
+                continue;
+            }
+
+            curpos = osfpos(fp) - startofs;
+            while (curpos != endpos)
+            {
+                /* read type and object number */
+                if (osfrb(fp, buf, 3)) errsig(ec, ERR_RDGAM);
+                obj = osrp2(buf+1);
+
+                switch(buf[0])
+                {
+                case TOKSTFUNC:
+                case TOKSTOBJ:
+                    if (osfrb(fp, buf + 3, 4)) errsig(ec, ERR_RDGAM);
+                    mcmrsrv(mctx, (ushort)osrp2(buf + 3), (mcmon)obj,
+                            (mclhd)curpos);
+                    curpos += osrp2(buf + 5) + 7;
+                    
+                    /* load object if preloading */
+                    if (flags & 2)
+                    {
+                        (void)mcmlck(mctx, (mcmon)obj);
+                        mcmunlck(mctx, (mcmon)obj);
+                    }
+
+                    /* seek past this object */
+                    osfseek(fp, curpos + startofs, OSFSK_SET);
+                    break;
+                    
+                case TOKSTFWDOBJ:
+                case TOKSTFWDFN:
+                {
+                    ushort  bsiz;
+                    uchar  *p;
+                    
+                    if (osfrb(fp, buf+3, 2)) errsig(ec, ERR_RDGAM);
+                    bsiz = osrp2(buf+3);
+                    p = mcmalonum(mctx, bsiz, (mcmon)obj);
+                    if (osfrb(fp, p, bsiz)) errsig(ec, ERR_RDGAM);
+                    mcmunlck(mctx, (mcmon)obj);
+                    curpos += 5 + bsiz;
+                    break;
+                }
+                    
+                case TOKSTEXTERN:
+                    if (!vctx->voccxrun->runcxext)
+                        errsig(ec, ERR_UNXEXT);
+                    ex = &vctx->voccxrun->runcxext[obj];
+
+                    if (osfrb(fp, buf + 3, 1)
+                        || osfrb(fp, ex->runxnam, (int)buf[3]))
+                        errsig(ec, ERR_RDGAM);
+                    ex->runxnam[buf[3]] = '\0';
+                    curpos += buf[3] + 4;
+                    break;
+                    
+                default:
+                    errsig(ec, ERR_UNKOTYP);
+                }
+            }
+        }
+        else if (fioisrsc(buf, "FST"))
+        {
+            uchar *p;
+            uchar *bufp;
+            ulong  bsiz;
+            
+            if (!(*flagp & FIOFFAST))
+            {
+                osfseek(fp, endpos + startofs, OSFSK_SET);
+                continue;
+            }
+            
+            curpos = osfpos(fp) - startofs;
+            bsiz = endpos - curpos;
+            if (bsiz && bsiz < OSMALMAX
+                && (bufp = p = (uchar *)osmalloc((size_t)bsiz)) != 0)
+            {
+                uchar *p1;
+                ulong  siz2;
+                uint   sizcur;
+
+                for (p1 = p, siz2 = bsiz ; siz2 ; siz2 -= sizcur, p1 += sizcur)
+                {
+                    sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2);
+                    if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM);
+                }
+
+                while (bsiz)
+                {
+                    obj = osrp2(p + 1);
+                    switch(*p)
+                    {
+                    case TOKSTFUNC:
+                    case TOKSTOBJ:
+                        mcmrsrv(mctx, (ushort)osrp2(p + 3), (mcmon)obj,
+                                (mclhd)osrp4(p + 7));
+                        p += 11;
+                        bsiz -= 11;
+                        
+                        /* preload object if desired */
+                        if (flags & 2)
+                        {
+                            (void)mcmlck(mctx, (mcmon)obj);
+                            mcmunlck(mctx, (mcmon)obj);
+                        }
+                        break;
+                        
+                    case TOKSTEXTERN:
+                        if (!vctx->voccxrun->runcxext)
+                            errsig(ec, ERR_UNXEXT);
+                        ex = &vctx->voccxrun->runcxext[obj];
+                        
+                        memcpy(ex->runxnam, p + 4, (size_t)p[3]);
+                        ex->runxnam[p[3]] = '\0';
+                        bsiz -= p[3] + 4;
+                        p += p[3] + 4;
+                        break;
+                        
+                    default:
+                        errsig(ec, ERR_UNKOTYP);
+                    }
+                }
+                
+                /* done with temporary block; free it */
+                osfree(bufp);
+                osfseek(fp, endpos + startofs, OSFSK_SET);
+            }
+            else
+            {
+                while (curpos != endpos)
+                {
+                    if (osfrb(fp, buf, 3)) errsig(ec, ERR_RDGAM);
+                    obj = osrp2(buf + 1);
+                    switch(buf[0])
+                    {
+                    case TOKSTFUNC:
+                    case TOKSTOBJ:
+                        if (osfrb(fp, buf + 3, 8)) errsig(ec, ERR_RDGAM);
+                        mcmrsrv(mctx, (ushort)osrp2(buf + 3), (mcmon)obj,
+                                (mclhd)osrp4(buf + 7));
+                        curpos += 11;
+                        
+                        /* preload object if desired */
+                        if (flags & 2)
+                        {
+                            (void)mcmlck(mctx, (mcmon)obj);
+                            mcmunlck(mctx, (mcmon)obj);
+                            osfseek(fp, curpos + startofs, OSFSK_SET);
+                        }
+                        break;
+                        
+                    case TOKSTEXTERN:
+                        if (!vctx->voccxrun->runcxext)
+                            errsig(ec, ERR_UNXEXT);
+                        ex = &vctx->voccxrun->runcxext[obj];
+                        
+                        if (osfrb(fp, buf + 3, 1)
+                            || osfrb(fp, ex->runxnam, (int)buf[3]))
+                            errsig(ec, ERR_RDGAM);
+                        ex->runxnam[buf[3]] = '\0';
+                        curpos += buf[3] + 4;
+                        break;
+                        
+                    default:
+                        errsig(ec, ERR_UNKOTYP);
+                    }
+                }
+            }
+
+            /* if we can preload xfcn's, do so now */
+            if (xfcn_pos)
+            {
+                eof_reset = endpos;    /* remember to return here when done */
+                osfseek(fp, xfcn_pos, OSFSK_SET);           /* go to xfcn's */
+            }
+        }
+        else if (fioisrsc(buf, "XFCN"))
+        {
+            if (!vctx->voccxrun->runcxext) errsig(ec, ERR_UNXEXT);
+
+            /* read length and name of resource */
+            if (osfrb(fp, buf, 3) || osfrb(fp, buf + 3, (int)buf[2]))
+                errsig(ec, ERR_RDGAM);
+            siz = osrp2(buf);
+
+#if 0
+/* 
+ *   external functions are now obsolete - do not load
+ */            
+
+            /* look for an external function with the same name */
+            for (i = vctx->voccxrun->runcxexc,  ex = vctx->voccxrun->runcxext
+                 ; i ; ++ex, --i)
+            {
+                j = strlen(ex->runxnam);
+                if (j == buf[2] && !memcmp(buf + 3, ex->runxnam, (size_t)j))
+                    break;
+            }
+
+            /* if we found an external function of this name, load it */
+            if (i && !xfcns_done)
+            {
+                /* load the function */
+                ex->runxptr = os_exfld(fp, (unsigned)siz);
+            }
+            else
+            {
+                /* this XFCN isn't used; don't bother loading it */
+                osfseek(fp, endpos + startofs, OSFSK_SET);
+            }
+#else
+            /* external functions are obsolete; simply skip the data */
+            osfseek(fp, endpos + startofs, OSFSK_SET);
+#endif
+        }
+        else if (fioisrsc(buf, "HTMLRES"))
+        {
+            /* read the resources */
+            fiordhtml(ec, fp, appctx, 0, fname);
+
+            /* 
+             *   skip the resources - they're entirely for the host
+             *   application's use 
+             */
+            osfseek(fp, endpos + startofs, OSFSK_SET);
+        }
+        else if (fioisrsc(buf, "INH"))
+        {
+            uchar *p;
+            uchar *bufp;
+            ulong  bsiz;
+            
+            /* do it in a single file read, if we can, for speed */
+            curpos = osfpos(fp) - startofs;
+            bsiz = endpos - curpos;
+            if (bsiz && bsiz < OSMALMAX
+                && (bufp = p = (uchar *)osmalloc((size_t)bsiz)) != 0)
+            {
+                uchar *p1;
+                ulong  siz2;
+                uint   sizcur;
+
+                for (p1 = p, siz2 = bsiz ; siz2 ; siz2 -= sizcur, p1 += sizcur)
+                {
+                    sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2);
+                    if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM);
+                }
+
+                while (bsiz)
+                {
+                    i = osrp2(p + 7);
+                    obj = osrp2(p + 1);
+                    
+                    vociadd(vctx, (objnum)obj, (objnum)osrp2(p+3), i,
+                            (objnum *)(p + 9), p[0] | VOCIFXLAT);
+                    vocinh(vctx, obj)->vociilc = osrp2(p + 5);
+                    
+                    p += 9 + (2 * i);
+                    bsiz -= 9 + (2 * i);
+                }
+                
+                /* done with temporary block; free it */
+                osfree(bufp);
+            }
+            else
+            {
+                while (curpos != endpos)
+                {
+                    if (osfrb(fp, buf, 9)) errsig(ec, ERR_RDGAM);
+                    i = osrp2(buf + 7);       /* get number of superclasses */
+                    obj = osrp2(buf + 1);              /* get object number */
+                    if (i && osfrb(fp, buf + 9, 2 * i)) errsig(ec, ERR_RDGAM);
+                    
+                    vociadd(vctx, (objnum)obj, (objnum)osrp2(buf+3),
+                            i, (objnum *)(buf + 9), buf[0] | VOCIFXLAT);
+                    vocinh(vctx, obj)->vociilc = osrp2(buf + 5);
+                
+                    curpos += 9 + (2 * i);
+                }
+            }
+        }
+        else if (fioisrsc(buf, "REQ"))
+        {
+            curpos = osfpos(fp) - startofs;
+            siz = endpos - curpos;
+
+            if (osfrb(fp, buf, (uint)siz)) errsig(ec, ERR_RDGAM);
+            vctx->voccxme  = vctx->voccxme_init = osrp2(buf);
+            vctx->voccxvtk = osrp2(buf+2);
+            vctx->voccxstr = osrp2(buf+4);
+            vctx->voccxnum = osrp2(buf+6);
+            vctx->voccxprd = osrp2(buf+8);
+            vctx->voccxvag = osrp2(buf+10);
+            vctx->voccxini = osrp2(buf+12);
+            vctx->voccxpre = osrp2(buf+14);
+            vctx->voccxper = osrp2(buf+16);
+            
+            /* if we have a cmdPrompt function, read it */
+            if (siz >= 20)
+                vctx->voccxprom = osrp2(buf + 18);
+            else
+                vctx->voccxprom = MCMONINV;
+
+            /* if we have the NLS functions, read them */
+            if (siz >= 26)
+            {
+                vctx->voccxpdis = osrp2(buf + 20);
+                vctx->voccxper2 = osrp2(buf + 22);
+                vctx->voccxpdef = osrp2(buf + 24);
+            }
+            else
+            {
+                /* the new NLS functions aren't defined in this file */
+                vctx->voccxpdis = MCMONINV;
+                vctx->voccxper2 = MCMONINV;
+                vctx->voccxpdef = MCMONINV;
+            }
+
+            /* test for parseAskobj separately, as it was added later */
+            if (siz >= 28)
+                vctx->voccxpask = osrp2(buf + 26);
+            else
+                vctx->voccxpask = MCMONINV;
+
+            /* test for preparseCmd separately - it's another late comer */
+            if (siz >= 30)
+                vctx->voccxppc = osrp2(buf + 28);
+            else
+                vctx->voccxppc = MCMONINV;
+
+            /* check for parseAskobjActor separately - another late comer */
+            if (siz >= 32)
+                vctx->voccxpask2 = osrp2(buf + 30);
+            else
+                vctx->voccxpask2 = MCMONINV;
+
+            /* if we have parseErrorParam, read it as well */
+            if (siz >= 34)
+            {
+                vctx->voccxperp = osrp2(buf + 32);
+            }
+            else
+            {
+                /* parseErrorParam isn't defined in this file */
+                vctx->voccxperp = MCMONINV;
+            }
+
+            /* 
+             *   if we have commandAfterRead and initRestore, read them as
+             *   well 
+             */
+            if (siz >= 38)
+            {
+                vctx->voccxpostprom = osrp2(buf + 34);
+                vctx->voccxinitrestore = osrp2(buf + 36);
+            }
+            else
+            {
+                /* these new functions aren't defined in this game */
+                vctx->voccxpostprom = MCMONINV;
+                vctx->voccxinitrestore = MCMONINV;
+            }
+
+            /* check for and read parseUnknownVerb, parseNounPhrase */
+            if (siz >= 42)
+            {
+                vctx->voccxpuv = osrp2(buf + 38);
+                vctx->voccxpnp = osrp2(buf + 40);
+            }
+            else
+            {
+                vctx->voccxpuv = MCMONINV;
+                vctx->voccxpnp = MCMONINV;
+            }
+
+            /* check for postAction, endCommand */
+            if (siz >= 48)
+            {
+                vctx->voccxpostact = osrp2(buf + 42);
+                vctx->voccxendcmd = osrp2(buf + 44);
+                vctx->voccxprecmd = osrp2(buf + 46);
+            }
+            else
+            {
+                vctx->voccxpostact = MCMONINV;
+                vctx->voccxendcmd = MCMONINV;
+                vctx->voccxprecmd = MCMONINV;
+            }
+
+            /* check for parseAskobjIndirect */
+            if (siz >= 50)
+                vctx->voccxpask3 = osrp2(buf + 48);
+            else
+                vctx->voccxpask3 = MCMONINV;
+
+            /* check for preparseExt and parseDefaultExt */
+            if (siz >= 54)
+            {
+                vctx->voccxpre2 = osrp2(buf + 50);
+                vctx->voccxpdef2 = osrp2(buf + 52);
+            }
+            else
+            {
+                vctx->voccxpre2 = MCMONINV;
+                vctx->voccxpdef2 = MCMONINV;
+            }
+        }
+        else if (fioisrsc(buf, "VOC"))
+        {
+            uchar *p;
+            uchar *bufp;
+            ulong  bsiz;
+            int    len1;
+            int    len2;
+            
+            /* do it in a single file read, if we can, for speed */
+            curpos = osfpos(fp) - startofs;
+            bsiz = endpos - curpos;
+            if (bsiz && bsiz < OSMALMAX
+                && (bufp = p = (uchar *)osmalloc((size_t)bsiz)) != 0)
+            {
+                uchar *p1;
+                ulong  siz2;
+                uint   sizcur;
+
+                for (p1 = p, siz2 = bsiz ; siz2 ; siz2 -= sizcur, p1 += sizcur)
+                {
+                    sizcur = (siz2 > (uint)0xffff ? (uint)0xffff : siz2);
+                    if (osfrb(fp, p1, sizcur)) errsig(ec, ERR_RDGAM);
+                }
+                
+                while (bsiz)
+                {
+                    len1 = osrp2(p);
+                    len2 = osrp2(p + 2);
+                    if (*flagp & FIOFCRYPT)
+                        fioxor(p + 10, (uint)(len1 + len2),
+                               xor_seed, xor_inc);
+                    vocadd2(vctx, (prpnum)osrp2(p+4), (objnum)osrp2(p+6),
+                            osrp2(p+8), p + 10, len1,
+                            (len2 ? p + 10 + len1 : (uchar*)0), len2);
+                    
+                    p += 10 + len1 + len2;
+                    bsiz -= 10 + len1 + len2;
+                }
+                
+                /* done with the temporary block; free it up */
+                osfree(bufp);
+            }
+            else
+            {
+                /* can't do it in one file read; do it the slow way */
+                while (curpos != endpos)
+                {
+                    if (osfrb(fp, buf, 10)
+                        || osfrb(fp, buf + 10,
+                               (len1 = osrp2(buf)) + (len2 = osrp2(buf + 2))))
+                        errsig(ec, ERR_RDGAM);
+                
+                    if (*flagp & FIOFCRYPT)
+                        fioxor(buf + 10, (uint)(len1 + len2),
+                               xor_seed, xor_inc);
+                    vocadd2(vctx, (prpnum)osrp2(buf+4), (objnum)osrp2(buf+6),
+                            osrp2(buf+8), buf + 10, len1,
+                            (len2 ? buf + 10 + len1 : (uchar*)0), len2);
+                    curpos += 10 + len1 + len2;
+                }
+            }
+        }
+        else if (fioisrsc(buf, "FMTSTR"))
+        {
+            uchar *fmts;
+            uint   fmtl;
+            
+            if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+            fmtl = osrp2(buf);
+            fmts = mchalo(vctx->voccxerr, fmtl, "fiord1");
+            if (osfrb(fp, fmts, fmtl)) errsig(ec, ERR_RDGAM);
+            if (*flagp & FIOFCRYPT) fioxor(fmts, fmtl, xor_seed, xor_inc);
+            tiosetfmt(vctx->voccxtio, vctx->voccxrun, fmts, fmtl);
+            
+            if (fmtsp) *fmtsp = fmts;
+            if (fmtlp) *fmtlp = fmtl;
+        }
+        else if (fioisrsc(buf, "CMPD"))
+        {
+            if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+            vctx->voccxcpl = osrp2(buf);
+            vctx->voccxcpp = (char *)mchalo(vctx->voccxerr,
+                                            vctx->voccxcpl, "fiord1");
+            if (osfrb(fp, vctx->voccxcpp, (uint)vctx->voccxcpl))
+                errsig(ec, ERR_RDGAM);
+            if (*flagp & FIOFCRYPT)
+                fioxor((uchar *)vctx->voccxcpp, (uint)vctx->voccxcpl,
+                       xor_seed, xor_inc);
+        }
+        else if (fioisrsc(buf, "SPECWORD"))
+        {
+            if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+            vctx->voccxspl = osrp2(buf);
+            vctx->voccxspp = (char *)mchalo(vctx->voccxerr,
+                                            vctx->voccxspl, "fiord1");
+            if (osfrb(fp, vctx->voccxspp, (uint)vctx->voccxspl))
+                errsig(ec, ERR_RDGAM);
+            if (*flagp & FIOFCRYPT)
+                fioxor((uchar *)vctx->voccxspp, (uint)vctx->voccxspl,
+                       xor_seed, xor_inc);
+        }
+        else if (fioisrsc(buf, "SYMTAB"))
+        {
+            tokthdef *symtab;
+            
+            /* if there's no debugger context, don't bother with this */
+            if (!vctx->voccxrun->runcxdbg)
+            {
+                osfseek(fp, endpos + startofs, OSFSK_SET);
+                continue;
+            }
+            
+            if (!(symtab = vctx->voccxrun->runcxdbg->dbgcxtab))
+            {
+                symtab = (tokthdef *)mchalo(ec, sizeof(tokthdef),
+                                            "fiord:symtab");
+                tokthini(ec, mctx, (toktdef *)symtab);
+                vctx->voccxrun->runcxdbg->dbgcxtab = symtab;
+            }
+            
+            /* read symbols until we find a zero-length symbol */
+            for (;;)
+            {
+                int hash;
+                
+                if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM);
+                if (buf[0] == 0) break;
+                if (osfrb(fp, buf + 4, (int)buf[0])) errsig(ec, ERR_RDGAM);
+                buf[4 + buf[0]] = '\0';
+                hash = tokhsh((char *)buf + 4);
+                
+                (*symtab->tokthsc.toktfadd)((toktdef *)symtab,
+                                            (char *)buf + 4,
+                                            (int)buf[0], (int)buf[1],
+                                            osrp2(buf + 2), hash);
+            }
+        }
+        else if (fioisrsc(buf, "SRC"))
+        {
+            /* skip source file id's if there's no debugger context */
+            if (vctx->voccxrun->runcxdbg == 0)
+            {
+                osfseek(fp, endpos + startofs, OSFSK_SET);
+                continue;
+            }
+            
+            while ((osfpos(fp) - startofs) != endpos)
+            {
+                /* the only thing we know how to read is linfdef's */
+                if (linfload(fp, vctx->voccxrun->runcxdbg, ec, path))
+                    errsig(ec, ERR_RDGAM);
+            }
+        }
+        else if (fioisrsc(buf, "SRC2"))
+        {
+            /* 
+             *   this is simply a marker indicating that we have new-style
+             *   (line-number-based) source debugging information in the
+             *   file -- set the new-style debug info flag 
+             */
+            if (vctx->voccxrun->runcxdbg != 0)
+                vctx->voccxrun->runcxdbg->dbgcxflg |= DBGCXFLIN2;
+
+            /* the contents are empty - skip the block */
+            osfseek(fp, endpos + startofs, OSFSK_SET);
+        }
+        else if (fioisrsc(buf, "PREINIT"))
+        {
+            if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+            *preinit = osrp2(buf);
+        }
+        else if (fioisrsc(buf, "ERRMSG"))
+        {
+            errini(ec, fp);
+            osfseek(fp, endpos + startofs, OSFSK_SET);
+        }
+        else if (fioisrsc(buf, "EXTCNT"))
+        {
+            uchar  *p;
+            ushort  len;
+            ulong   bsiz;
+
+            curpos = osfpos(fp) - startofs;
+            bsiz = endpos - curpos;
+            if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+            i = osrp2(buf);
+
+            len = i * sizeof(runxdef);
+            p = mchalo(ec, len, "fiord:runxdef");
+            memset(p, 0, (size_t)len);
+
+            vctx->voccxrun->runcxext = (runxdef *)p;
+            vctx->voccxrun->runcxexc = i;
+
+            /* see if start-of-XFCN information is present */
+            if (bsiz >= 6)
+            {
+                /* get location of first XFCN, and seek there */
+                if (osfrb(fp, buf, 4)) errsig(ec, ERR_RDGAM);
+                xfcn_pos = osrp4(buf);
+            }
+
+            /* seek past this resource */
+            osfseek(fp, endpos + startofs, OSFSK_SET);
+        }
+        else if (fioisrsc(buf, "PRPCNT"))
+        {
+            if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+            if (pcntptr) *pcntptr = osrp2(buf);
+        }
+        else if (fioisrsc(buf, "TADSPP") && tctx != 0)
+        {
+            tok_read_defines(tctx, fp, ec);
+        }
+        else if (fioisrsc(buf, "XSI"))
+        {
+            if (osfrb(fp, buf, 2)) errsig(ec, ERR_RDGAM);
+            setupctx->fiolcxseed = xor_seed = buf[0];
+            setupctx->fiolcxinc = xor_inc = buf[1];
+            osfseek(fp, endpos + startofs, OSFSK_SET);
+        }
+        else if (fioisrsc(buf, "CHRSET"))
+        {
+            size_t len;
+            
+            /* read the character set ID and LDESC */
+            if (osfrb(fp, buf, 6)
+                || (len = osrp2(buf+4)) > CMAP_LDESC_MAX_LEN
+                || osfrb(fp, buf+6, len))
+                errsig(ec, ERR_RDGAM);
+
+            /* establish this character set mapping */
+            buf[4] = '\0';
+            cmap_set_game_charset(ec, (char *)buf, (char *)buf + 6, argv0);
+        }
+        else if (fioisrsc(buf, "$EOF"))
+        {
+            if (eof_reset)
+            {
+                osfseek(fp, eof_reset, OSFSK_SET);     /* back after EXTCNT */
+                eof_reset = 0;                   /* really done at next EOF */
+#if 0 // XFCNs are obsolete
+                xfcns_done = TRUE;                  /* don't do XFCNs again */
+#endif
+            }
+            else
+                break;
+        }
+        else
+            errsig(ec, ERR_UNKRSC);
+    }
+}
+
+/* read binary file */
+void fiord(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx, char *fname,
+           char *exename, fiolcxdef *setupctx, objnum *preinit, uint *flagp,
+           tokpdef *path, uchar **fmtsp, uint *fmtlp, uint *pcntptr,
+           int flags, struct appctxdef *appctx, char *argv0)
+{
+    osfildef *fp;
+    ulong     startofs;
+    char     *display_fname;
+    
+    /* presume there will be no need to run preinit */
+    *preinit = MCMONINV;
+
+    /* 
+     *   get the display filename - use the real filename if one is
+     *   provided, otherwise use the name of the executable file itself 
+     */
+    display_fname = (fname != 0 ? fname : exename);
+
+    /* save the filename in G_os_gamename */
+    if (display_fname != 0)
+    {
+        size_t copylen;
+
+        /* limit the copy to the buffer size */
+        if ((copylen = strlen(display_fname)) > sizeof(G_os_gamename) - 1)
+            copylen = sizeof(G_os_gamename) - 1;
+
+        /* save it */
+        memcpy(G_os_gamename, display_fname, copylen);
+        G_os_gamename[copylen] = '\0';
+    }
+    else
+        G_os_gamename[0] = '\0';
+    
+    /* open the file and read and check file header */
+    fp = (fname != 0 ? osfoprb(fname, OSFTGAME)
+                     : os_exeseek(exename, "TGAM"));
+    if (fp == 0)
+        errsig(vctx->voccxerr, ERR_OPRGAM);
+
+    /* 
+     *   we've identified the .GAM file source - tell the host system
+     *   about it, if it's interested 
+     */
+    if (appctx != 0 && appctx->set_game_name != 0)
+        (*appctx->set_game_name)(appctx->set_game_name_ctx, display_fname);
+
+    /* remember starting location in file */
+    startofs = osfpos(fp);
+
+    ERRBEGIN(vctx->voccxerr)
+
+    /* 
+     *   Read the game file.  Note that the .GAM file always has resource
+     *   file number zero. 
+     */
+    fiord1(mctx, vctx, tctx, fp, display_fname,
+           setupctx, startofs, preinit, flagp, path,
+           fmtsp, fmtlp, pcntptr, flags, appctx, argv0);
+
+    /*
+     *   If the host system accepts additional resource files, look for
+     *   additional resource files.  These are files in the same directory
+     *   as the .GAM file, with the .GAM suffix replaced by suffixes from
+     *.  RS0 to .RS9.  
+     */
+    if (appctx != 0 && appctx->add_resfile != 0)
+    {
+        char suffix_lc[4];
+        char suffix_uc[4];
+        int i;
+        char *base_name;
+
+        /* use the game or executable filename, as appropriate */
+        base_name = display_fname;
+
+        /* build the initial suffixes - try both upper- and lower-case */
+        suffix_uc[0] = 'R';
+        suffix_uc[1] = 'S';
+        suffix_uc[3] = '\0';
+        suffix_lc[0] = 'r';
+        suffix_lc[1] = 's';
+        suffix_lc[3] = '\0';
+
+        /* loop through each possible suffix (.RS0 through .RS9) */
+        for (i = 0 ; i < 9 ; ++i)
+        {
+            char resname[OSFNMAX];
+            osfildef *fpres;
+            int resfileno;
+
+            /* 
+             *   Build the next resource filename.  If there's an explicit
+             *   resource path, use it, otherwise use the same directory
+             *   that contains the .GAM file. 
+             */
+            if (appctx->ext_res_path != 0)
+            {
+                /*
+                 *   There's an explicit resource path - append the root
+                 *   (filename-only, minus path) portion of the .GAM file
+                 *   name to the resource path. 
+                 */
+                os_build_full_path(resname, sizeof(resname),
+                                   appctx->ext_res_path,
+                                   os_get_root_name(base_name));
+            }
+            else
+            {
+                /* 
+                 *   there's no resource path - use the entire .GAM
+                 *   filename, including directory, so that we look in the
+                 *   same directory that contains the .GAM file 
+                 */
+                if (base_name != 0)
+                    strcpy(resname, base_name);
+                else
+                    resname[0] = '\0';
+            }
+
+            /* add the current extension (replacing any current extension) */
+            os_remext(resname);
+            suffix_lc[2] = suffix_uc[2] = '0' + i;
+            os_addext(resname, suffix_lc);
+
+            /* try opening the file */
+            fpres = osfoprb(resname, OSFTGAME);
+
+            /* if that didn't work, try the upper-case name */
+            if (fpres == 0)
+            {
+                /* replace the suffix with the upper-case version */
+                os_remext(resname);
+                os_addext(resname, suffix_uc);
+
+                /* try again with the new name */
+                fpres = osfoprb(resname, OSFTGAME);
+            }
+
+            /* if we opened it successfully, read it */
+            if (fpres != 0)
+            {
+                /* tell the host system about it */
+                resfileno = (*appctx->add_resfile)
+                            (appctx->add_resfile_ctx, resname);
+
+                /* read the file */
+                fiordrscext(vctx->voccxerr, fpres, appctx,
+                            resfileno, resname);
+
+                /* we're done with the file, so close it */
+                osfcls(fpres);
+            }
+        }
+    }
+
+    ERRCLEAN(vctx->voccxerr)
+        /* if an error occurs during read, clean up by closing the file */
+        osfcls(fp);
+    ERRENDCLN(vctx->voccxerr);
+}
+
+/* save game header */
+#define FIOSAVHDR "TADS2 save\012\015\032"
+
+/* save game header prefix - .GAM file information */
+#define FIOSAVHDR_PREFIX "TADS2 save/g\012\015\032"
+
+/* 
+ *   Saved game format version string - note that the length of the
+ *   version string must be fixed, so when this is updated, it must be
+ *   updated to another string of the same length.  This should be updated
+ *   whenever a change is made to the format that can't be otherwise
+ *   detected from the data stream in the saved game file.  
+ */
+#define FIOSAVVSN "v2.2.1"
+
+/* old saved game format version strings */
+#define FIOSAVVSN1 "v2.2.0"
+
+/* read fuse/daemon/alarm record */
+static int fiorfda(osfildef *fp, vocddef *p, uint cnt)
+{
+    vocddef *q;
+    uint     i;
+    uchar    buf[14];
+    
+    /* start by clearing out entire record */
+    for (i = 0, q = p ; i < cnt ; ++q, ++i)
+        q->vocdfn = MCMONINV;
+    
+    /* now restore all the records from the file */
+    for (;;)
+    {
+        /* read a record, and quit if it's the last one */
+        if (osfrb(fp, buf, 13)) return(TRUE);
+        if ((i = osrp2(buf)) == 0xffff) return(FALSE);
+        
+        /* restore this record */
+        q = p + i;
+        q->vocdfn = osrp2(buf+2);
+        q->vocdarg.runstyp = buf[4];
+        switch(buf[4])
+        {
+        case DAT_NUMBER:
+            q->vocdarg.runsv.runsvnum = osrp4s(buf+5);
+            break;
+        case DAT_OBJECT:
+        case DAT_FNADDR:
+            q->vocdarg.runsv.runsvobj = osrp2(buf+5);
+            break;
+        case DAT_PROPNUM:
+            q->vocdarg.runsv.runsvprp = osrp2(buf+5);
+            break;
+        }
+        q->vocdprp = osrp2(buf+9);
+        q->vocdtim = osrp2(buf+11);
+    }
+}
+
+/*
+ *   Look in a saved game file to determine if it has information on which
+ *   GAM file created it.  If the GAM file information is available, this
+ *   routine returns true and stores the game file name in the given
+ *   buffer; if the information isn't available, we'll return false.  
+ */
+int fiorso_getgame(char *saved_file, char *fnamebuf, size_t buflen)
+{
+    osfildef *fp;
+    uint      namelen;
+    char      buf[sizeof(FIOSAVHDR_PREFIX) + 2];
+
+    /* open the input file */
+    if (!(fp = osfoprb(saved_file, OSFTSAVE)))
+        return FALSE;
+
+    /* read the prefix header and check */
+    if (osfrb(fp, buf, (int)(sizeof(FIOSAVHDR_PREFIX) + 2))
+        || memcmp(buf, FIOSAVHDR_PREFIX, sizeof(FIOSAVHDR_PREFIX)) != 0)
+    {
+        /* 
+         *   there's no game file information - close the file and
+         *   indicate that we have no information 
+         */
+        osfcls(fp);
+        return FALSE;
+    }
+
+    /* get the length of the filename */
+    namelen = osrp2(buf + sizeof(FIOSAVHDR_PREFIX));
+    if (namelen > buflen - 1)
+        namelen = buflen - 1;
+
+    /* read the filename */
+    if (osfrb(fp, fnamebuf, namelen))
+    {
+        osfcls(fp);
+        return FALSE;
+    }
+
+    /* null-terminate the string */
+    fnamebuf[namelen] = '\0';
+
+    /* done with the file */
+    osfcls(fp);
+
+    /* indicate that we found the information */
+    return TRUE;
+}
+
+/* restore game: returns TRUE on failure */
+int fiorso(voccxdef *vctx, char *fname)
+{
+    osfildef   *fp;
+    objnum      obj;
+    uchar      *p;
+    uchar      *mut;
+    uint        mutsiz;
+    uint        oldmutsiz;
+    int         propcnt;
+    mcmcxdef   *mctx = vctx->voccxmem;
+    uchar       buf[sizeof(FIOSAVHDR) + sizeof(FIOSAVVSN)];
+    ushort      newsiz;
+    int         err = FALSE;
+    char        timestamp[26];
+    int         version = 0;            /* version ID - 0 = current version */
+    int         result;
+
+    /* presume success */
+    result = FIORSO_SUCCESS;
+
+    /* open the input file */
+    if (!(fp = osfoprb(fname, OSFTSAVE)))
+        return FIORSO_FILE_NOT_FOUND;
+
+    /* check for a prefix header - if it's there, skip it */
+    if (!osfrb(fp, buf, (int)(sizeof(FIOSAVHDR_PREFIX) + 2))
+        && memcmp(buf, FIOSAVHDR_PREFIX, sizeof(FIOSAVHDR_PREFIX)) == 0)
+    {
+        ulong skip_len;
+        
+        /*
+         *   The prefix header is present - skip it.  The 2-byte value
+         *   following the header is the length of the prefix data block
+         *   (not including the header), so simply skip the additional
+         *   number of bytes specified.  
+         */
+        skip_len = (ulong)osrp2(buf + sizeof(FIOSAVHDR_PREFIX));
+        osfseek(fp, skip_len, OSFSK_CUR);
+    }
+    else
+    {
+        /* 
+         *   there's no prefix header - seek back to the start of the file
+         *   and read the standard header information
+         */
+        osfseek(fp, 0, OSFSK_SET);
+    }
+
+    
+    /* read headers and check */
+    if (osfrb(fp, buf, (int)(sizeof(FIOSAVHDR) + sizeof(FIOSAVVSN)))
+        || memcmp(buf, FIOSAVHDR, (size_t)sizeof(FIOSAVHDR)))
+    {
+        /* it's not a saved game file */
+        result = FIORSO_NOT_SAVE_FILE;
+        goto ret_error;
+    }
+
+    /* check the version string */
+    if (memcmp(buf + sizeof(FIOSAVHDR), FIOSAVVSN,
+               (size_t)sizeof(FIOSAVVSN)) == 0)
+    {
+        /* it's the current version */
+        version = 0;
+    }
+    else if (memcmp(buf + sizeof(FIOSAVHDR), FIOSAVVSN1,
+                    (size_t)sizeof(FIOSAVVSN1)) == 0)
+    {
+        /* it's old version #1 */
+        version = 1;
+    }
+    else
+    {
+        /* 
+         *   this isn't a recognized version - the file must have been
+         *   saved by a newer version of the system, so we can't assume we
+         *   will be able to parse the format 
+         */
+        result = FIORSO_BAD_FMT_VSN;
+        goto ret_error;
+    }
+    
+    /* 
+     *   Read timestamp and check - the game must have been saved by the
+     *   same .GAM file that we are now running, because the .SAV file is
+     *   written entirely in terms of the contents of the .GAM file; any
+     *   change in the .GAM file invalidates the .SAV file. 
+     */
+    if (osfrb(fp, timestamp, 26)
+        || memcmp(timestamp, vctx->voccxtim, (size_t)26))
+    {
+        result = FIORSO_BAD_GAME_VSN;
+        goto ret_error;
+    }
+    
+    /* first revert every object to original (post-compilation) state */
+    vocrevert(vctx);
+
+    /* 
+     *   the most common error from here on is simply a file read error,
+     *   so presume that this is what will happen; if we are successful or
+     *   encounter a different error, we'll change the status at that
+     *   point 
+     */
+    result = FIORSO_READ_ERROR;
+
+    /* go through file and load changed objects */
+    for (;;)
+    {
+        /* get the header */
+        if (osfrb(fp, buf, 7))
+            goto ret_error;
+
+        /* get the object number from the header, and stop if we're done */
+        obj = osrp2(buf+1);
+        if (obj == MCMONINV)
+            break;
+
+        /* if the object was dynamically allocated, recreate it */
+        if (buf[0] == 1)
+        {
+            int     sccnt;
+            objnum  sc;
+            
+            /* create the object */
+            mutsiz = osrp2(buf + 3);
+            p = mcmalonum(mctx, (ushort)mutsiz, (mcmon)obj);
+
+            /* read the object's contents */
+            if (osfrb(fp, p, mutsiz))
+                goto ret_error;
+
+            /* get the superclass data (at most one superclass) */
+            sccnt = objnsc(p);
+            if (sccnt) sc = osrp2(objsc(p));
+
+            /* create inheritance records for the object */
+            vociadd(vctx, obj, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC);
+
+#if 0
+            {
+                int     wrdcnt;
+
+                /* read the object's vocabulary and add it back */
+                if (osfrb(fp, buf, 2))
+                    goto ret_error;
+                wrdcnt = osrp2(buf);
+                while (wrdcnt--)
+                {
+                    int   len1;
+                    int   len2;
+                    char  wrd[80];
+                    
+                    /* read the header */
+                    if (osfrb(fp, buf, 6))
+                        goto ret_error;
+                    len1 = osrp2(buf+2);
+                    len2 = osrp2(buf+4);
+                    
+                    /* read the word text */
+                    if (osfrb(fp, wrd, len1 + len2))
+                        goto ret_error;
+                    
+                    /* add the word */
+                    vocadd2(vctx, buf[0], obj, buf[1], wrd, len1,
+                            wrd + len1, len2);
+                }
+            }
+#endif
+
+        }
+        else
+        {
+            /* get the remaining data from the header */
+            propcnt = osrp2(buf + 3);
+            mutsiz = osrp2(buf + 5);
+        
+            /* expand object if it's not big enough for mutsiz */
+            p = mcmlck(mctx, (mcmon)obj);
+            oldmutsiz = mcmobjsiz(mctx, (mcmon)obj) - objrst(p);
+            if (oldmutsiz < mutsiz)
+            {
+                newsiz = mutsiz - oldmutsiz;
+                p = (uchar *)objexp(mctx, obj, &newsiz);
+            }
+            
+            /* reset statistics, and read mutable part from file */
+            mut = p + objrst(p);
+            objsnp(p, propcnt);
+            objsfree(p, mutsiz + objrst(p));
+            if (osfrb(fp, mut, mutsiz))
+                err = TRUE;
+        
+            /* reset ignore flags as needed */
+            objsetign(mctx, obj);
+        }
+
+        /* touch and unlock the object */
+        mcmtch(mctx, (mcmon)obj);
+        mcmunlck(mctx, (mcmon)obj);
+        if (err)
+            goto ret_error;
+    }
+    
+    /* read fuses/daemons/alarms */
+    if (fiorfda(fp, vctx->voccxdmn, vctx->voccxdmc)
+        || fiorfda(fp, vctx->voccxfus, vctx->voccxfuc)
+        || fiorfda(fp, vctx->voccxalm, vctx->voccxalc))
+        goto ret_error;
+
+    /* read the dynamically added and deleted vocabulary */
+    for (;;)
+    {
+        int     len1;
+        int     len2;
+        char    wrd[80];
+        int     flags;
+        int     typ;
+        
+        /* read the header */
+        if (osfrb(fp, buf, 8))
+            goto ret_error;
+
+        typ = buf[0];
+        flags = buf[1];
+        len1 = osrp2(buf+2);
+        len2 = osrp2(buf+4);
+        obj = osrp2(buf+6);
+
+        /* check to see if this is the end marker */
+        if (obj == MCMONINV) break;
+        
+        /* read the word text */
+        if (osfrb(fp, wrd+2, len1))
+            goto ret_error;
+        if (len2)
+        {
+            wrd[len1 + 2] = ' ';
+            if (osfrb(fp, &wrd[len1 + 3], len2))
+                goto ret_error;
+            oswp2(wrd, len1 + len2 + 3);
+        }
+        else
+            oswp2(wrd, len1 + 2);
+        
+        /* add or delete the word as appropriate */
+        if (flags & VOCFDEL)
+            vocdel1(vctx, obj, (char *)wrd, (prpnum)typ, FALSE, FALSE, FALSE);
+        else
+            vocadd2(vctx, buf[0], obj, buf[1], (uchar *)wrd+2, len1,
+                    (uchar *)wrd+len1, len2);
+    }
+
+    /* 
+     *   the following was added in save format version "v2.2.1", so skip
+     *   it if the save version is older than that 
+     */
+    if (version != 1)
+    {
+        /* read the current "Me" object */
+        if (osfrb(fp, buf, 2))
+            goto ret_error;
+        vctx->voccxme = osrp2(buf);
+    }
+
+    /* done - close file and return success indication */
+    osfcls(fp);
+    return FIORSO_SUCCESS;
+
+    /* come here on failure - close file and return error indication */
+ret_error:
+    osfcls(fp);
+    return result;
+}
+
+/* write fuse/daemon/alarm block */
+static int fiowfda(osfildef *fp, vocddef *p, uint cnt)
+{
+    uchar buf[14];
+    uint  i;
+    
+    for (i = 0 ; i < cnt ; ++i, ++p)
+    {
+        if (p->vocdfn == MCMONINV) continue;            /* not set - ignore */
+        
+        oswp2(buf, i);                        /* element in array to be set */
+        oswp2(buf+2, p->vocdfn);       /* object number for function/target */
+        buf[4] = p->vocdarg.runstyp;                    /* type of argument */
+        switch(buf[4])
+        {
+        case DAT_NUMBER:
+            oswp4s(buf+5, p->vocdarg.runsv.runsvnum);
+            break;
+        case DAT_OBJECT:
+        case DAT_FNADDR:
+            oswp2(buf+5, p->vocdarg.runsv.runsvobj);
+            break;
+        case DAT_PROPNUM:
+            oswp2(buf+5, p->vocdarg.runsv.runsvprp);
+            break;
+        }
+        oswp2(buf+9, p->vocdprp);
+        oswp2(buf+11, p->vocdtim);
+        
+        /* write this record to file */
+        if (osfwb(fp, buf, 13)) return(TRUE);
+    }
+    
+    /* write end record - -1 for array element number */
+    oswp2(buf, 0xffff);
+    return(osfwb(fp, buf, 13));
+}
+
+/* context for vocabulary saver callback function */
+struct fiosav_cb_ctx
+{
+    int       err;
+    osfildef *fp;
+};
+
+#ifdef NEVER
+/*
+ *   callback for vocabulary saver - called by voc_iterate for each word
+ *   defined for a particular object, allowing us to write all the words
+ *   attached to a dynamically allocated object to the save file 
+ */
+static void fiosav_cb(struct fiosav_cb_ctx *ctx,
+                      vocdef *voc, vocwdef *vocw)
+{
+    char buf[10];
+    
+    /* write the part of speech, flags, and word lengths */
+    buf[0] = vocw->vocwtyp;
+    buf[1] = vocw->vocwflg;
+    oswp2(buf+2, voc->voclen);
+    oswp2(buf+4, voc->vocln2);
+    if (osfwb(ctx->fp, buf, 6)) ctx->err = TRUE;
+
+    /* write the words */
+    if (osfwb(ctx->fp, voc->voctxt, voc->voclen + voc->vocln2))
+        ctx->err = TRUE;
+}
+#endif
+
+/*
+ *   Callback for vocabulary saver - called by voc_iterate for every
+ *   word.  We'll write the word if it was dynamically added or deleted,
+ *   so that we can restore that status when the game is restored.  
+ */
+static void fiosav_voc_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+    struct fiosav_cb_ctx *ctx = (struct fiosav_cb_ctx *)ctx0;
+    char buf[10];
+    
+    /* if the word was dynamically allocated or deleted, save it */
+    if ((vocw->vocwflg & VOCFNEW) || (vocw->vocwflg & VOCFDEL))
+    {
+        /* write the header information */
+        buf[0] = vocw->vocwtyp;
+        buf[1] = vocw->vocwflg;
+        oswp2(buf+2, voc->voclen);
+        oswp2(buf+4, voc->vocln2);
+        oswp2(buf+6, vocw->vocwobj);
+        if (osfwb(ctx->fp, buf, 8)) ctx->err = TRUE;
+
+        /* write the words */
+        if (osfwb(ctx->fp, voc->voctxt, voc->voclen + voc->vocln2))
+            ctx->err = TRUE;
+    }
+}
+
+
+/* save game; returns TRUE on failure */
+int fiosav(voccxdef *vctx, char *fname, char *game_fname)
+{
+    osfildef   *fp;
+    vocidef  ***vpg;
+    vocidef   **v;
+    int         i;
+    int         j;
+    objnum      obj;
+    uchar      *p;
+    uchar      *mut;
+    uint        mutsiz;
+    int         propcnt;
+    mcmcxdef   *mctx = vctx->voccxmem;
+    uchar       buf[8];
+    int         err = FALSE;
+    struct fiosav_cb_ctx  fnctx;
+
+    /* open the output file */
+    if ((fp = osfopwb(fname, OSFTSAVE)) == 0)
+        return TRUE;
+
+    /*
+     *   If we have game file information, save the game file information
+     *   with the saved game file.  This lets the player start the
+     *   run-time and restore the game by specifying only the saved game
+     *   file.  
+     */
+    if (game_fname != 0)
+    {
+        size_t len;
+        
+        /* write the prefix header */
+        len = strlen(game_fname);
+        oswp2(buf, len);
+        if (osfwb(fp, FIOSAVHDR_PREFIX, (int)sizeof(FIOSAVHDR_PREFIX))
+            || osfwb(fp, buf, 2)
+            || osfwb(fp, game_fname, (int)len))
+            goto ret_error;
+    }
+    
+    /* write save game header and timestamp */
+    if (osfwb(fp, FIOSAVHDR, (int)sizeof(FIOSAVHDR))
+        || osfwb(fp, FIOSAVVSN, (int)sizeof(FIOSAVVSN))
+        || osfwb(fp, vctx->voccxtim, 26))
+        goto ret_error;
+
+    /* go through each object, and write if it's been changed */
+    for (vpg = vctx->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
+    {
+        if (!*vpg) continue;
+        for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
+        {
+            if (*v != 0)
+            {
+                /* write object if it's dirty */
+                if (mcmobjdirty(mctx, (mcmon)obj))
+                {
+                    p = mcmlck(mctx, (mcmon)obj);
+                    mut = p + objrst(p);
+                    propcnt = objnprop(p);
+                    mutsiz = objfree(p) - objrst(p);
+                    if ((objflg(p) & OBJFINDEX) != 0)
+                        mutsiz += propcnt * 4;
+
+                    /*
+                     *   If the object was dynamically allocated, write
+                     *   the whole object.  Otherwise, write just the
+                     *   mutable part. 
+                     */
+                    if ((*v)->vociflg & VOCIFNEW)
+                    {
+                        /* indicate that the object is dynamic */
+                        buf[0] = 1;
+                        oswp2(buf + 1, obj);
+
+                        /* write the entire object */
+                        mutsiz = objfree(p);
+                        oswp2(buf + 3, mutsiz);
+                        if (osfwb(fp, buf, 7)
+                            || osfwb(fp, p, mutsiz))
+                            err = TRUE;
+
+#ifdef NEVER
+                        {
+                            int         wrdcnt;
+                            
+                            /* count the words, and write the count */
+                            voc_count(vctx, obj, 0, &wrdcnt, (int *)0);
+                            oswp2(buf, wrdcnt);
+                            if (osfwb(fp, buf, 2))
+                                err = TRUE;
+                            
+                            /* write the words */
+                            fnctx.err = 0;
+                            fnctx.fp = fp;
+                            voc_iterate(vctx, obj, fiosav_cb, &fnctx);
+                            if (fnctx.err != 0)
+                                err = TRUE;
+                        }
+#endif
+                    }
+                    else if (mutsiz)
+                    {
+                        /* write number of properties, size of mut, and mut */
+                        buf[0] = 0;   /* indicate that the object is static */
+                        oswp2(buf + 1, obj);
+                        oswp2(buf + 3, propcnt);
+                        oswp2(buf + 5, mutsiz);
+                        if (osfwb(fp, buf, 7)
+                            || osfwb(fp, mut, mutsiz))
+                            err = TRUE;
+                    }
+                    
+                    mcmunlck(mctx, (mcmon)obj);
+                    if (err != 0)
+                        goto ret_error;
+                }
+            }
+        }
+    }
+
+    /* write end-of-objects indication */
+    buf[0] = 0;
+    oswp2(buf + 1, MCMONINV);
+    oswp4(buf + 3, 0);
+    if (osfwb(fp, buf, 7))
+        goto ret_error;
+
+    /* write fuses/daemons/alarms */
+    if (fiowfda(fp, vctx->voccxdmn, vctx->voccxdmc)
+        || fiowfda(fp, vctx->voccxfus, vctx->voccxfuc)
+        || fiowfda(fp, vctx->voccxalm, vctx->voccxalc))
+        goto ret_error;
+
+    /* write run-time vocabulary additions and deletions */
+    fnctx.fp = fp;
+    fnctx.err = 0;
+    voc_iterate(vctx, MCMONINV, fiosav_voc_cb, &fnctx);
+    if (fnctx.err)
+        goto ret_error;
+
+    /* write end marker for vocabulary additions and deletions */
+    oswp2(buf+6, MCMONINV);
+    if (osfwb(fp, buf, 8))
+        goto ret_error;
+
+    /* write the current "Me" object */
+    oswp2(buf, vctx->voccxme);
+    if (osfwb(fp, buf, 2))
+        goto ret_error;
+
+    /* done - close file and return success indication */
+    osfcls(fp);
+    os_settype(fname, OSFTSAVE);
+    return FALSE;
+
+    /* come here on failure - close file and return error indication */
+ret_error:
+    osfcls(fp);
+    return TRUE;
+}
+
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index 26536c0..b5da3d1 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -21,12 +21,1049 @@
  */
 
 #include "glk/tads/tads2/line_source_file.h"
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/tokenizer.h"
+#include "glk/tads/os_glk.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
 
+/* initialize a pre-allocated linfdef, skipping debugger page setup */
+void linfini2(mcmcxdef *mctx, linfdef *linf,
+              char *filename, int flen, osfildef *fp, int new_line_records)
+{
+    /* set up method pointers */
+    linf->linflin.lingetp = linfget;
+    linf->linflin.linclsp = linfcls;
+    linf->linflin.linppos = linfppos;
+    linf->linflin.linglop = (new_line_records ? linfglop2 : linfglop);
+    linf->linflin.linwrtp = linfwrt;
+    linf->linflin.lincmpp = linfcmp;
+    linf->linflin.linactp = linfact;
+    linf->linflin.lindisp = linfdis;
+    linf->linflin.lintellp = linftell;
+    linf->linflin.linseekp = linfseek;
+    linf->linflin.linreadp = linfread;
+    linf->linflin.linpaddp = linfpadd;
+    linf->linflin.linqtopp = linfqtop;
+    linf->linflin.lingetsp = linfgets;
+    linf->linflin.linnamp = linfnam;
+    linf->linflin.linlnump = linflnum;
+    linf->linflin.linfindp = linffind;
+    linf->linflin.lingotop = linfgoto;
+    linf->linflin.linofsp = linfofs;
+    linf->linflin.linrenp = linfren;
+    linf->linflin.lindelp = linfdelnum;
+
+    /* set up instance data */
+    linf->linflin.linbuf = linf->linfbuf;
+    linf->linflin.linflg = 0;
+    memcpy(linf->linfnam, filename, (size_t)flen);
+    linf->linfnam[flen] = '\0';
+    linf->linfbuf[0] = '\0';
+    linf->linfbufnxt = 0;
+    linf->linfnxtlen = 0;
+    linf->linffp = fp;
+    linf->linfnum = 0;
+    linf->linflin.linlln = 4;     /* OPCLINE operand is seek offset in file */
+    linf->linfmem = mctx;                    /* save memory manager context */
+    linf->linfcrec = 0;                  /* no debugger records written yet */
+}
+
+/* 
+ *   Initialize a file line source object.  If must_find_file is true,
+ *   we'll fail if we can't find the file.  Otherwise, we'll create the
+ *   linfdef even if we can't find the file, reserving the maximum space
+ *   for its path name to be filled in later. 
+ */
+linfdef *linfini(mcmcxdef *mctx, errcxdef *ec, char *filename,
+                 int flen, tokpdef *path, int must_find_file,
+                 int new_line_records)
+{
+    int       i;
+    objnum   *objp;
+    linfdef  *linf;
+    osfildef *fp;
+    char      fbuf[OSFNMAX + 1];
+    tokpdef   fakepath;
+    int       len;
+    
+    if (!path)
+    {
+        path = &fakepath;
+        fakepath.tokpnxt = (tokpdef *)0;
+        fakepath.tokplen = 0;
+    }
+
+    /* search through the path list */
+    for ( ; path ; path = path->tokpnxt)
+    {
+        char last;
+
+        /* prefix the current path */
+        if ((len = path->tokplen) != 0)
+        {
+            memcpy(fbuf, path->tokpdir, (size_t)len);
+            last = fbuf[len - 1];
+            if (last == OSPATHCHAR ||
+                (OSPATHALT && strchr(OSPATHALT, last)))
+                /* do nothing */ ;
+            else
+            {
+                /* append path separator character */
+                fbuf[len++] = OSPATHCHAR;
+            }
+        }
+
+        /* add the filename and null-terminate */
+        memcpy(fbuf + len, filename, (size_t)flen);
+        fbuf[len + flen] = '\0';
+        
+        /* attempt to open this file */
+        if ((fp = osfoprs(fbuf, OSFTTEXT)) != 0)
+            break;
+    }
+
+    /* 
+     *   If no file opened yet, search tads path; if that doesn't work,
+     *   let the debugger UI try to find the file.  If nothing works, give
+     *   up and return failure.  
+     */
+    if (fp == 0
+        && (!os_locate(filename, flen, (char *)0, fbuf, sizeof(fbuf))
+            || (fp = osfoprs(fbuf, OSFTTEXT)) == 0))
+    {
+        /*
+         *   Ask the debugger UI for advice.  If the debugger isn't
+         *   present, we'll get a failure code from this routine. 
+         */
+        if (!dbgu_find_src(filename, flen, fbuf, sizeof(fbuf),
+                           must_find_file))
+            return 0;
+
+        /* try opening the file */
+        if (fbuf[0] == '\0')
+        {
+            /* 
+             *   we didn't get a filename - the UI wants to defer finding
+             *   the file until later 
+             */
+            fp = 0;
+        }
+        else
+        {
+            /* we got a filename from the UI - try opening it */
+            fp = osfoprs(fbuf, OSFTTEXT);
+        }
+
+        /* 
+         *   if the file isn't present, and we're required to find it,
+         *   return failure 
+         */
+        if (fp == 0 && must_find_file)
+            return 0;
+    }
+    
+    /* figure out how much space we need for the file's full name */
+    if (fp == 0)
+    {
+        /* 
+         *   we didn't find the file, so we don't yet know its name - use
+         *   the maximum possible filename length for the buffer size, so
+         *   that we can store the final filename if we should figure out
+         *   where the file is later on 
+         */
+        fbuf[0] = '\0';
+        len = sizeof(fbuf);
+    }
+    else
+    {
+        /* 
+         *   we found the file, so we have its final name - allocate space
+         *   for the known name 
+         */
+        len = (int)strlen(fbuf);
+    }
+
+    /* allocate the linfdef */
+    linf = (linfdef *)mchalo(ec, (ushort)(sizeof(linfdef) + flen
+                                          + len + 1), "linfini");
+
+    /* do the basic initialization */
+    linfini2(mctx, linf, filename, flen, fp, new_line_records);
+
+    memcpy(linf->linfnam + flen + 1, fbuf, (size_t)len);
+    linf->linfnam[flen + 1 + len] = '\0';
+    
+    /* set all debugger pages to not-yet-allocated */
+    for (i = LINFPGMAX, objp = linf->linfpg ; i ; ++objp, --i)
+        *objp = MCMONINV;
+
+    /* return the new line source object */
+    return linf;
+}
+
+int linfget(lindef *lin)
+{
+#   define  linf ((linfdef *)lin)
+    char   *p;
+    size_t  rdlen;
+    int     nl_len;
+
+    /* remember seek position of start of current line */
+    linf->linfseek = osfpos(linf->linffp);
+
+    /*
+     *   if we have data left in the buffer after the end of this line,
+     *   move it to the start of the buffer
+     */
+    if (linf->linfnxtlen != 0)
+    {
+        /* move the data down */
+        memmove(linf->linfbuf, linf->linfbuf + linf->linfbufnxt,
+                linf->linfnxtlen);
+
+        /* 
+         *   adjust the seek position to account for the fact that we've
+         *   read ahead in the file 
+         */
+        linf->linfseek -= linf->linfnxtlen;
+
+        /* 
+         *   Fill up the rest of the buffer.  Leave one byte for a null
+         *   terminator and one byte for a possible extra newline pair
+         *   character (see below), hence fill to sizeof(buf)-2.  
+         */
+        rdlen = osfrbc(linf->linffp, linf->linfbuf + linf->linfnxtlen,
+                       sizeof(linf->linfbuf) - linf->linfnxtlen - 2);
+
+        /* 
+         *   the total space is the amount we had left over plus the
+         *   amount we just read 
+         */
+        rdlen += linf->linfnxtlen;
+    }
+    else
+    {
+        /* 
+         *   We have nothing in the buffer - fill it up.  Fill to
+         *   sizeof(buf)-2 to leave room for a null terminator plus a
+         *   possible extra newline pair character (see below). 
+         */
+        rdlen = osfrbc(linf->linffp, linf->linfbuf,
+                       sizeof(linf->linfbuf) - 2);
+    }
+
+    /* 
+     *   if there's nothing in the buffer at this point, we've reached the
+     *   end of the file 
+     */
+    if (rdlen == 0)
+        return TRUE;
+
+    /* 
+     *   if the last line was not a continuation line, increment the line
+     *   counter for the start of a new line 
+     */
+    if (!(lin->linflg & LINFMORE))
+        ++(linf->linfnum);
+
+    /* null-terminate the buffer contents */
+    linf->linfbuf[rdlen] = '\0';
+
+    /* perform character mapping on th new part only */
+    for (p = linf->linfbuf + linf->linfnxtlen ; *p != '\0' ; ++p)
+        *p = cmap_n2i(*p);
+
+    /* 
+     *   scan the for the first newline in the buffer, allowing newline
+     *   conventions that involve either CR or LF 
+     */
+    for (p = linf->linfbuf ; *p != '\n' && *p != '\r' && *p != '\0' ; ++p) ;
+
+    /*
+     *   Check to see if this character is followed by its newline pair
+     *   complement, to allow for either CR-LF or LF-CR sequences, as well
+     *   as plain single-byte newline (CR or LF) sequences.
+     *   
+     *   First, though, one weird special case: if this character is at
+     *   the read limit in the buffer, the complementary character might
+     *   be lurking in the next byte that we haven't read.  In this case,
+     *   use that one-byte reserve we have left (we filled the buffer only
+     *   to length-2 so far) and read the next byte.  
+     */
+    if (*p != '\0' && p + 1 == linf->linfbuf + sizeof(linf->linfbuf) - 2)
+    {
+        /* 
+         *   we've filled the buffer to but not including the reserve for
+         *   just this case - fetch the extra character 
+         */
+        if (osfrbc(linf->linffp, p + 1, 1) == 1)
+        {
+            /* increase the total read length for the extra byte */
+            ++rdlen;
+            *(p+2) = '\0';
+        }
+    }
+
+    /* 
+     *   now we can check for the newline type, since we have definitely
+     *   read the full paired sequence 
+     */
+    if (*p == '\0')
+    {
+        /* there's no newline in the buffer - we'll return a partial line */
+        nl_len = 0;
+
+        /* set the partial line flag */
+        lin->linflg |= LINFMORE;
+
+        /* return the entire buffer */
+        lin->linlen = rdlen;
+
+        /* there's nothing left for the next time through */
+        linf->linfnxtlen = 0;
+    }
+    else
+    {
+        /* check for a complementary pair */
+        if ((*p == '\n' && *(p+1) == '\r') || (*p == '\r' && *(p+1) == '\n'))
+        {
+            /* we have a paired newline */
+            nl_len = 2;
+        }
+        else
+        {
+            /* we have but a single-character newline sequence */
+            nl_len = 1;
+        }
+
+        /* this is the end of a line */
+        lin->linflg &= ~LINFMORE;
+
+        /* 
+         *   return only the part of the buffer up to, but not including,
+         *   the newline 
+         */
+        lin->linlen = (p - linf->linfbuf);
+
+        /* null-terminate the buffer at the newline */
+        *p = '\0';
+
+        /* 
+         *   anything remaining after the newline sequence is available
+         *   for reading the next time through 
+         */
+        linf->linfbufnxt = ((p + nl_len) - linf->linfbuf);
+        linf->linfnxtlen = rdlen - linf->linfbufnxt;
+    }
+
+    /* make sure buffer pointer is correct */
+    lin->linbuf = linf->linfbuf;
+
+    LINFDEBUG(printf("%s\n", linf->linfbuf));
+
+    /* success */
+    return FALSE;
+
+#   undef  linf
+}
+
+/* make printable string from position in file (for error reporting) */
+void linfppos(lindef *lin, char *buf, uint buflen)
+{
+    VARUSED(buflen);
+    
+    sprintf(buf, "%s(%lu): ", ((linfdef *)lin)->linfnam,
+            ((linfdef *)lin)->linfnum);
+}
+
+/* close line source */
+void linfcls(lindef *lin)
+{
+    osfcls(((linfdef *)lin)->linffp);
+}
+
+/* generate operand of OPCLINE (source-line debug) instruction */
+void linfglop(lindef *lin, uchar *buf)
+{
+    oswp4(buf, ((linfdef *)lin)->linfseek);   /* save seek position of line */
+}
+
+/* generate new-style operand of OPCLINE instruction */
+void linfglop2(lindef *lin, uchar *buf)
+{
+    oswp4(buf, ((linfdef *)lin)->linfnum);    /* save seek position of line */
+}
+
+/* save line source information to binary (.gam) file; TRUE ==> error */
+int linfwrt(lindef *lin, osfildef *fp)
+{
+#   define  linf ((linfdef *)lin)
+    uchar   buf[UCHAR_MAX + 6];
+    size_t  len;
+    uint    pgcnt;
+    uchar  *objp;
+    mcmon  *objn;
+
+    buf[0] = lin->linid;
+    len = strlen(linf->linfnam);
+    if (len > UCHAR_MAX)
+        return FALSE;
+    buf[1] = (uchar)len;
+    oswp4(buf + 2, linf->linfcrec);
+    memcpy(buf + 6, linf->linfnam, (size_t)buf[1]);
+    if (osfwb(fp, buf, (int)(buf[1] + 6))) return(TRUE);
+    
+    /* write the debug source pages */
+    if (!linf->linfcrec) return(FALSE);          /* no debug records at all */
+    pgcnt = 1 + ((linf->linfcrec - 1) >> 10);     /* figure number of pages */
+    
+    for (objn = linf->linfpg ; pgcnt ; ++objn, --pgcnt)
+    {
+        objp = mcmlck(linf->linfmem, *objn);
+        if (osfwb(fp, objp, (1024 * DBGLINFSIZ))) return(TRUE);
+        mcmunlck(linf->linfmem, *objn);
+    }
+    
+    return(FALSE);
+
+#   undef  linf
+}
+
+/* load a file-line-source from binary (.gam) file */
+int linfload(osfildef *fp, dbgcxdef *dbgctx, errcxdef *ec, tokpdef *path)
+{
+    linfdef *linf;
+    uchar    buf[UCHAR_MAX + 6];
+    uint     pgcnt;
+    uchar   *objp;
+    mcmon   *objn;
+    
+    /* read the source's description from the file */
+    if (osfrb(fp, buf, 6)
+        || osfrb(fp, buf + 6, (int)buf[1]))
+        return TRUE;
+    
+    /* initialize the linfdef */
+    if (!(linf = linfini(dbgctx->dbgcxmem, ec, (char *)buf + 6,
+                         (int)buf[1], path, FALSE, FALSE)))
+    {
+        errlog1(ec, ERR_NOSOURC, ERRTSTR,
+                errstr(ec, (char *)buf+6, (int)buf[1]));
+        return TRUE;
+    }
+    
+    /* if we opened the file, close it - don't hold all files open */
+    if (linf->linffp != 0)
+    {
+        osfcls(linf->linffp);
+        linf->linffp = 0;
+    }
+    
+    /* link into debug line source chain */
+    linf->linflin.linnxt = dbgctx->dbgcxlin;
+    dbgctx->dbgcxlin = &linf->linflin;
+    linf->linflin.linid = buf[0];
+    linf->linfcrec = osrp4(buf + 2);
+    
+    /* make sure the max line id is set above current line */
+    if (buf[0] >= dbgctx->dbgcxfid)
+        dbgctx->dbgcxfid = buf[0] + 1;
+    
+    /* make sure we have some debug records */
+    if (!linf->linfcrec)
+        return FALSE;
+
+    /* figure number of pages */
+    pgcnt = 1 + ((linf->linfcrec - 1) >> 10);
+    
+    /* allocate and read the debug source pages */
+    for (objn = linf->linfpg ; pgcnt ; ++objn, --pgcnt)
+    {
+        objp = mcmalo(linf->linfmem, (ushort)(1024 * DBGLINFSIZ), objn);
+        if (osfrb(fp, objp, (1024 * DBGLINFSIZ))) return(TRUE);
+        mcmunlck(linf->linfmem, *objn);
+    }
+
+    /* success */
+    return FALSE;
+}
+
+/* add a debugger line record for the current line being compiled */
+void linfcmp(lindef *lin, uchar *buf)
+{
+    uint    pg;
+    uchar  *objptr;
+#   define  linf ((linfdef *)lin)
+
+    /* figure out which page to use, and lock it */
+    pg = linf->linfcrec >> 10;                     /* 2^10 records per page */
+    if (pg >= LINFPGMAX)
+        errsig(linf->linfmem->mcmcxgl->mcmcxerr, ERR_MANYDBG);
+    if (linf->linfpg[pg] == MCMONINV)
+        objptr = mcmalo(linf->linfmem, (ushort)(1024 * DBGLINFSIZ),
+                        &linf->linfpg[pg]);
+    else
+        objptr = mcmlck(linf->linfmem, linf->linfpg[pg]);
+    
+    /* write the record to the appropriate offset within the page */
+    memcpy(objptr + (linf->linfcrec & 1023) * DBGLINFSIZ, buf,
+           (size_t)DBGLINFSIZ);
+    
+    /* increment counter of line records so far */
+    ++(linf->linfcrec);
+    
+    /* done with page - touch it and unlock it */
+    mcmtch(linf->linfmem, linf->linfpg[pg]);
+    mcmunlck(linf->linfmem, linf->linfpg[pg]);
+    
+#   undef linf
+}
+
+/*
+ *   Renumber an existing object.  Searches through all line records for
+ *   any with the given object number, and changes the number to the new
+ *   number if found.  
+ */
+void linfren(lindef *lin, objnum oldnum, objnum newnum)
+{
+#   define  linf ((linfdef *)lin)
+    uint    pgcnt;
+    uchar  *objp;
+    mcmon  *pgobjn;
+    int     i;
+    int     pgtot;
+    int     tot;
+
+    /* figure the number of pages - if no lines, stop now */
+    tot = linf->linfcrec;
+    if (tot == 0)
+        return;
+
+    /* calculate the number of pages to check */
+    pgcnt = 1 + ((tot - 1) >> 10);
+
+    /* scan each page */
+    for (pgobjn = linf->linfpg ; pgcnt ; ++pgobjn, --pgcnt, tot -= 1024)
+    {
+        /* lock the page */
+        objp = mcmlck(linf->linfmem, *pgobjn);
+
+        /* figure the number on this page */
+        pgtot = (tot > 1024 ? 1024 : tot);
+
+        /* scan each record on this page */
+        for (i = 0 ; i < pgtot ; ++i, objp += DBGLINFSIZ)
+        {
+            /* check this one */
+            if (osrp2(objp) == oldnum)
+            {
+                /* it matches - renumber it */
+                oswp2(objp, newnum);
+            }
+        }
+
+        /* done with the page - touch it and unlock it */
+        mcmtch(linf->linfmem, *pgobjn);
+        mcmunlck(linf->linfmem, *pgobjn);
+    }
+
+#   undef  linf
+}
+
+/*
+ *   Delete an existing object.  Searches through all line records for any
+ *   with the given object number, and removes line records for the object
+ *   number if found.  
+ */
+void linfdelnum(lindef *lin, objnum objn)
+{
+#   define  linf ((linfdef *)lin)
+    uint    pgcnt;
+    uchar  *objp;
+    uchar  *objp_orig;
+    mcmon  *pgobjn;
+    int     i;
+    int     pgtot;
+    int     tot;
+
+    /* figure the number of pages - if no lines, stop now */
+    tot = linf->linfcrec;
+    if (tot == 0)
+        return;
+
+    /* calculate the number of pages to check */
+    pgcnt = 1 + ((tot - 1) >> 10);
+
+    /* scan each page */
+    for (pgobjn = linf->linfpg ; pgcnt ; ++pgobjn, --pgcnt, tot -= 1024)
+    {
+        /* lock the page */
+        objp = objp_orig = mcmlck(linf->linfmem, *pgobjn);
+
+        /* figure the number on this page */
+        pgtot = (tot > 1024 ? 1024 : tot);
+
+        /* scan each record on this page */
+        for (i = 0 ; i < pgtot ; ++i, objp += DBGLINFSIZ)
+        {
+            int j;
+            
+            /* check this one */
+            if (osrp2(objp) == objn)
+            {
+                uchar *nxtp;
+                uint pg;
+                int delcnt;
+                int totrem;
+                
+                /* 
+                 *   it matches - delete it, along with any subsequent
+                 *   contiguous entries that also match it 
+                 */
+                for (delcnt = 1, j = i + 1 ; j < pgtot ; ++j, ++delcnt)
+                {
+                    /* 
+                     *   if this one doesn't match, we've found the end of
+                     *   the contiguous records for this object 
+                     */
+                    if (osrp2(objp + (j - i)*DBGLINFSIZ) != objn)
+                        break;
+                }
+
+                /* close up the gap on this page */
+                if (j < pgtot)
+                    memmove(objp, objp + delcnt*DBGLINFSIZ,
+                            (pgtot - j)*DBGLINFSIZ);
+
+                /* 
+                 *   if this isn't the last page, copy the bottom of the
+                 *   next page to the gap at the top of this page 
+                 */
+                if (pgcnt > 1)
+                {
+                    /* lock the next page */
+                    nxtp = mcmlck(linf->linfmem, *(pgobjn + 1));
+
+                    /* 
+                     *   copy from the beginning of the next page to the
+                     *   end of this page 
+                     */
+                    memcpy(objp_orig + (pgtot - delcnt)*DBGLINFSIZ,
+                           nxtp, delcnt*DBGLINFSIZ);
+
+                    /* done with the page */
+                    mcmunlck(linf->linfmem, *(pgobjn + 1));
+                }
+                else
+                {
+                    /* 
+                     *   this is the last page, so there's no next page to
+                     *   copy items from - reduce the count of items on
+                     *   this page accordingly 
+                     */
+                    pgtot -= delcnt;
+                }
+
+                /*
+                 *   Now rearrange all subsequent pages to accommodate the
+                 *   gap we just created 
+                 */
+                for (totrem = tot, pg = 1 ; pg < pgcnt ;
+                     totrem -= 1024, ++pg)
+                {
+                    uchar *curp;
+                    int curtot;
+
+                    /* figure how many we have on this page */
+                    curtot = (totrem > 1024 ? 1024 : totrem);
+                    
+                    /* lock this page */
+                    curp = mcmlck(linf->linfmem, *(pgobjn + pg));
+
+                    /* delete from the start of this page */
+                    memmove(curp, curp + delcnt*DBGLINFSIZ,
+                            (curtot - delcnt)*DBGLINFSIZ);
+
+                    /* if there's another page, copy from it */
+                    if (pg + 1 < pgcnt)
+                    {
+                        /* lock the next page */
+                        nxtp = mcmlck(linf->linfmem, *(pgobjn + pg + 1));
+
+                        /* 
+                         *   copy from the start of the next page to the
+                         *   end of this page 
+                         */
+                        memcpy(curp + (curtot - delcnt)*DBGLINFSIZ,
+                               nxtp, delcnt*DBGLINFSIZ);
+
+                        /* unlock it */
+                        mcmunlck(linf->linfmem, *(pgobjn + pg + 1));
+                    }
+
+                    /* done with the page - touch it and unlock it */
+                    mcmtch(linf->linfmem, *(pgobjn + pg));
+                    mcmunlck(linf->linfmem, *(pgobjn + pg));
+                }
+
+                /* deduct the removed records from the total */
+                linf->linfcrec -= delcnt;
+            }
+        }
+
+        /* done with the page - touch it and unlock it */
+        mcmtch(linf->linfmem, *pgobjn);
+        mcmunlck(linf->linfmem, *pgobjn);
+    }
+
+#   undef  linf
+}
+
+
+/* find the nearest line record to a file seek location */
+void linffind(lindef *lin, char *buf, objnum *objp, uint *ofsp)
+{
+#   define  linf ((linfdef *)lin)
+    uint    pg;
+    uchar  *objptr;
+    uchar  *bufptr;
+    long    first;
+    long    last;
+    long    cur;
+    ulong   seekpos;
+	ulong   curpos = 0;
+    objnum  objn;
+    uint    ofs;
+
+    /* get desired seek position out of buffer */
+    seekpos = osrp4(buf);
+
+    /* we haven't traversed any records yet */
+    objn = MCMONINV;
+    ofs = 0;
+    
+    /* run a binary search for the indicated line record */
+    first = 0;
+    last = linf->linfcrec - 1;
+    for (;;)
+    {
+        /* make sure we're not out of records entirely */
+        if (first > last)
+        {
+            /* return the most recent record found - it's closest */
+            *objp = objn;
+            *ofsp = ofs;
+
+            /* set the position to that of the line we actually found */
+            oswp4(buf, curpos);
+            return;
+        }
+
+        /* split the difference */
+        cur = first + (last - first)/2;
+
+        /* calculate the page containing this item */
+        pg = cur >> 10;
+        
+        /* get object + offset corresponding to current source line */
+        objptr = mcmlck(linf->linfmem, linf->linfpg[pg]);
+        bufptr = objptr + ((cur & 1023) * DBGLINFSIZ);
+        objn = osrp2(bufptr);
+        ofs = osrp2(bufptr + 2);
+        mcmunlck(linf->linfmem, linf->linfpg[pg]);
+
+        /* read user data out of the object's OPCLINE record */
+        objptr = mcmlck(linf->linfmem, (mcmon)objn);
+        bufptr = objptr + ofs + 5;
+        curpos = osrp4(bufptr);
+        mcmunlck(linf->linfmem, (mcmon)objn);
+
+        /* see what we have */
+        if (curpos == seekpos)
+        {
+            *objp = objn;
+            *ofsp = ofs;
+            return;
+        }
+        else if (curpos < seekpos)
+            first = (cur == first ? first + 1 : cur);
+        else
+            last = (cur == last ? last - 1 : cur);
+    }
+    
+#   undef linf
+}
+
+/* 
+ *   copy line records to an array of linfinfo structures 
+ */
+void linf_copy_linerecs(linfdef *linf, struct linfinfo *info)
+{
+    uint    pg;
+    uint    prvpg;
+    uchar  *objptr;
+    uchar  *bufptr;
+    long    last;
+    long    cur;
+
+    /* note the last element */
+    last = linf->linfcrec;
+
+    /* if there are no records, there's nothing to do */
+    if (last == 0)
+        return;
+
+    /* load the first page of records */
+    prvpg = 0;
+    pg = 0;
+    objptr = mcmlck(linf->linfmem, linf->linfpg[0]);
+
+    /* scan the records */
+    for (cur = 0 ; cur < last ; ++cur, ++info)
+    {
+        uchar *codeptr;
+        
+        /* calculate the page containing this item */
+        pg = cur >> 10;
+
+        /* if it's different than the last page, load the next page */
+        if (pg != prvpg)
+        {
+            /* unlock the previous page */
+            mcmunlck(linf->linfmem, linf->linfpg[prvpg]);
+
+            /* load the next page */
+            objptr = mcmlck(linf->linfmem, linf->linfpg[pg]);
+
+            /* this is now the previous page */
+            prvpg = pg;
+        }
+            
+        /* get object + offset corresponding to current source line */
+        bufptr = objptr + ((cur & 1023) * DBGLINFSIZ);
+        info->objn = osrp2(bufptr);
+        info->ofs = osrp2(bufptr + 2);
+
+        /* read source location data out of the object's OPCLINE record */
+        codeptr = mcmlck(linf->linfmem, (mcmon)info->objn);
+        bufptr = codeptr + info->ofs + 5;
+        info->fpos = osrp4(bufptr);
+        mcmunlck(linf->linfmem, (mcmon)info->objn);
+    }
+
+    /* unlock the last page */
+    mcmunlck(linf->linfmem, linf->linfpg[prvpg]);
+}
+
+/* disactivate line source under debugger - close file */
+void linfdis(lindef *lin)
+{
+#   define linf ((linfdef *)lin)
+
+    if (linf->linffp)
+    {
+        osfcls(linf->linffp);
+        linf->linffp = (osfildef *)0;
+    }
+    
+#   undef linf
+}
+
+/* activate line source under debugger - open file */
+void linfact(lindef *lin)
+{
+    char   *fname;
+#   define  linf ((linfdef *)lin)
+
+    /* get the name buffer, and advance to the full path name portion */
+    fname = linf->linfnam;
+    fname += strlen(fname) + 1;
+
+    /*
+     *   If the full path name is empty, it means that the UI told us to
+     *   defer searching for the file until we actually need the file.  At
+     *   this point, we actually need the file.  Ask the UI again to find
+     *   the file. 
+     */
+    if (fname[0] != '\0'
+        || dbgu_find_src(linf->linfnam, strlen(linf->linfnam),
+                         fname, OSFNMAX, TRUE))
+    {
+        /* open the file */
+        linf->linffp = osfoprs(fname, OSFTTEXT);
+    }
+    else
+    {
+        /* there's no file to open */
+        linf->linffp = 0;
+    }
+
+#   undef linf
+}
+
+/* get current seek position */
+void linftell(lindef *lin, uchar *pos)
+{
+#   define  linf ((linfdef *)lin)
+    long    seekpos;
+
+    seekpos = osfpos(linf->linffp);
+    oswp4(pos, seekpos);
+
+#   undef linf
+}
+
+/* seek to a new position */
+void linfseek(lindef *lin, uchar *pos)
+{
+#   define  linf ((linfdef *)lin)
+    long    seekpos;
+    
+    seekpos = osrp4(pos);
+    osfseek(linf->linffp, seekpos, OSFSK_SET);
+
+#   undef linf
+}
+
+/* read bytes - fread-style interface */
+int linfread(lindef *lin, uchar *buf, uint siz)
+{
+#   define  linf ((linfdef *)lin)
+
+    return osfrbc(linf->linffp, buf, siz);
+
+#   undef linf
+}
+
+/* add a signed delta to a seek position */
+void linfpadd(lindef *lin, uchar *pos, long delta)
+{
+#   define  linf ((linfdef *)lin)
+    long    seekpos;
+
+    seekpos = osrp4(pos);
+    seekpos += delta;
+    if (seekpos < 0) seekpos = 0;
+    oswp4(pos, seekpos);
+
+#   undef linf
+}
+
+/* query whether we're at top of file */
+int linfqtop(lindef *lin, uchar *pos)
+{
+#   define  linf ((linfdef *)lin)
+
+    return(osrp4(pos) == 0);
+
+#   undef linf
+}
+
+/* read one line at current position - fgets-style interface */
+int linfgets(lindef *lin, uchar *buf, uint siz)
+{
+    int    ret;
+    long   startpos;
+    uchar *p;
+#   define  linf ((linfdef *)lin)
+
+    /* note the seek offset at the start of the line */
+    startpos = osfpos(linf->linffp);
+
+    /* read the next line */
+    ret = (osfgets((char *)buf, siz, linf->linffp) != 0);
+    if (!ret)
+        return ret;
+
+    /* scan for non-standard line endings */
+    for (p = buf ; *p != '\0' && *p != '\r' && *p != '\n' ; ++p) ;
+    if (*p != '\0')
+    {
+        uchar *nxt;
+        
+        /* 
+         *   Scan for non-line-ending characters after this line-ending
+         *   character.  If we find any, we must have non-standard newline
+         *   conventions in this file.  To be tolerant of these, seek back
+         *   to the start of the next line in these cases and read the
+         *   next line from the new location. 
+         */
+        for (nxt = p + 1 ; *nxt == '\r' || *nxt == '\n' ; ++nxt) ;
+        if (*nxt == '\0')
+        {
+            /* 
+             *   we had only line-ending characters after the first
+             *   line-ending character -- simply end the line after the
+             *   first line-ending character 
+             */
+            *(p+1) = '\0';
+        }
+        else
+        {
+            /*
+             *   We had a line-ending character in the middle of other
+             *   text, so we must have a file that doesn't conform to
+             *   local newline conventions.  Seek back to the next
+             *   character following the last line-ending character so
+             *   that we start the next line here, and end the current
+             *   line after the first line-ending character. 
+             */
+            *(p+1) = '\0';
+            osfseek(linf->linffp, startpos + (nxt - buf), OSFSK_SET);
+        }
+    }
+
+    /* return the result */
+    return ret;
+
+#   undef linf
+}
+
+/* get name of line source */
+void linfnam(lindef *lin, char *buf)
+{
+#   define  linf ((linfdef *)lin)
+
+    strcpy(buf, linf->linfnam);
+
+#   undef linf
+}
+
+/* get the current line number */
+ulong linflnum(lindef *lin)
+{
+#   define  linf ((linfdef *)lin)
+
+    return linf->linfnum;
+
+#   undef linf
+}
+
+/* go to top/bottom of line source */
+void linfgoto(lindef *lin, int where)
+{
+#   define  linf ((linfdef *)lin)
+
+    osfseek(linf->linffp, 0L, where);
+
+#   undef linf
+}
+
+/* return current seek offset within source */
+long linfofs(lindef *lin)
+{
+#   define linf ((linfdef *)lin)
+
+    return(osfpos(linf->linffp));
+    
+#   undef  linf
+}
 
 } // End of namespace TADS2
 } // End of namespace TADS
diff --git a/engines/glk/tads/tads2/memory_cache.cpp b/engines/glk/tads/tads2/memory_cache.cpp
index 3bab3b0..359f1b3 100644
--- a/engines/glk/tads/tads2/memory_cache.cpp
+++ b/engines/glk/tads/tads2/memory_cache.cpp
@@ -21,11 +21,1167 @@
  */
 
 #include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/os_glk.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
+/* get an unused object cache entry, allocating a new page if needed */
+static mcmodef *mcmoal(mcmcx1def *ctx, mcmon *objnum);
+
+/* split a (previously free) block into two pieces */
+static void mcmsplt(mcmcx1def *ctx, mcmon n, ushort siz);
+
+/* unlink an object from a doubly-linked list */
+static void mcmunl(mcmcx1def *ctx, mcmon n, mcmon *lst);
+
+/* initialize a cache, return cache context */
+/* find free block: find a block from the free pool to satisfy a request */
+static mcmodef *mcmffb(mcmcx1def *ctx, ushort siz, mcmon *nump);
+
+/* add page pagenum, initializing entries after firstunu to unused */
+static void mcmadpg(mcmcx1def *ctx, uint pagenum, mcmon firstunu);
+
+/* link an object into a doubly-linked list at the head of the list */
+static void mcmlnkhd(mcmcx1def *ctx, mcmon *lst, mcmon n);
+
+/* try to allocate a new chunk from the heap */
+static uchar *mcmhalo(mcmcx1def *ctx);
+
+/* relocate blocks in a heap */
+static uchar *mcmreloc(mcmcx1def *ctx, uchar *start, uchar *end);
+
+/* find next free heap block */
+static uchar *mcmffh(mcmcx1def *ctx, uchar *p);
+
+#ifdef NEVER
+/* update entry to account for a block relocation */
+static void mcmmove(mcmcx1def *ctx, mcmodef *obj, uchar *newaddr);
+#else /* NEVER */
+#define mcmmove(ctx, o, new) ((o)->mcmoptr = (new))
+#endif /* NEVER */
+
+/* consolidate two contiguous free blocks into a single block */
+static void mcmconsol(mcmcx1def *ctx, uchar *p);
+
+/* collect garbage in all heaps */
+static void mcmgarb(mcmcx1def *ctx);
+
+/* make some room by swapping or discarding objects */
+static int mcmswap(mcmcx1def *ctx, ushort siz);
+
+/* toss out an object; returns TRUE if successful */
+static int mcmtoss(mcmcx1def *ctx, mcmon objnum);
+
+/* next heap block, given a heap block (points to header) */
+/* uchar *mcmhnxt(mcmcx1def *ctx, uchar *p) */
+
+#define mcmnxh(ctx, p) \
+ ((p) + osrndsz(sizeof(mcmon)) + mcmgobje(ctx, *(mcmon*)(p))->mcmosiz)
+
+#ifdef DEBUG
+# define MCMCLICTX(ctx) assert(*(((ulong *)ctx) - 1) == 0x02020202)
+# define MCMGLBCTX(ctx) assert(*(((ulong *)ctx) - 1) == 0x01010101)
+#else /* DEBUG */
+# define MCMCLICTX(ctx)
+# define MCMGLBCTX(ctx)
+#endif /* DEBUG */
+
+/* initialize a new client context */
+mcmcxdef *mcmcini(mcmcx1def *globalctx, uint pages,
+                  void (*loadfn)(void *, mclhd, uchar *, ushort),
+                  void *loadctx,
+                  void (*revertfn)(void *, mcmon), void *revertctx)
+{
+    mcmcxdef *ret;
+    ushort    siz;
+    
+    siz = sizeof(mcmcxdef) + sizeof(mcmon *) * (pages - 1);
+    IF_DEBUG(siz += sizeof(ulong));
+    
+    ret = (mcmcxdef *)mchalo(globalctx->mcmcxerr, siz, "mcm client context");
+    IF_DEBUG((*(ulong *)ret = 0x02020202,
+              ret = (mcmcxdef *)((uchar *)ret + sizeof(ulong))));
+
+    ret->mcmcxmsz = pages;
+    ret->mcmcxgl = globalctx;
+    ret->mcmcxldf = loadfn;
+    ret->mcmcxldc = loadctx;
+    ret->mcmcxrvf = revertfn;
+    ret->mcmcxrvc = revertctx;
+    ret->mcmcxflg = 0;
+    memset(ret->mcmcxmtb, 0, (size_t)(pages * sizeof(mcmon *)));
+    return(ret);
+}
+
+/* uninitialize a client context */
+void mcmcterm(mcmcxdef *ctx)
+{
+    /* delete the context memory */
+    mchfre(ctx);
+}
+
+/* initialize a new global context */
+mcmcx1def *mcmini(ulong max, uint pages, ulong swapsize,
+                  osfildef *swapfp, char *swapfilename, errcxdef *errctx)
+{
+    mcmcx1def *ctx;                /* newly-allocated cache manager context */
+    uchar     *noreg chunk;/* 1st chunk of memory managed by this cache mgr */
+    mcmodef   *obj;                      /* pointer to a cache object entry */
+    ushort     siz;                /* size of current thing being allocated */
+    ushort     rem;                             /* bytes remaining in chunk */
+    int        err;
+    
+    NOREG((&chunk))
+    
+    /* make sure 'max' is big enough - must be at least one chunk */
+    if (max < (ulong)MCMCHUNK) max = (ulong)MCMCHUNK;
+    
+    /* allocate space for control structures from low-level heap */
+    rem = MCMCHUNK;
+    
+    IF_DEBUG(rem += sizeof(long));
+    chunk = mchalo(errctx, rem, "mcmini");
+    IF_DEBUG((*(ulong *)chunk = 0x01010101, chunk += sizeof(ulong),
+        rem -= sizeof(ulong)));
+    
+    ctx = (mcmcx1def *)chunk;              /* put context at start of chunk */
+    
+    /* initialize swapper; clean up if it fails */
+    ERRBEGIN(errctx)
+        mcsini(&ctx->mcmcxswc, ctx, swapsize, swapfp, swapfilename, errctx);
+    ERRCATCH(errctx, err)
+        mcsclose(&ctx->mcmcxswc);
+        mchfre(chunk);
+        errsig(errctx, err);
+    ERREND(errctx)
+    
+    chunk += sizeof(mcmcx1def);           /* rest of chunk is after context */
+    rem -= sizeof(mcmcx1def);         /* remove from remaining size counter */
+
+    /* allocate the page table (an array of pointers to pages) */
+    ctx->mcmcxtab = (mcmodef **)chunk;            /* put at bottom of chunk */
+    siz = pages * sizeof(mcmodef *);              /* calcuate size of table */
+
+    memset(ctx->mcmcxtab, 0, (size_t)siz);            /* clear entire table */
+    chunk += siz;                                  /* reflect size of table */
+    rem -= siz;                       /* take it out of the remaining count */
+
+    /* here we begin normal heap marking with object references */
+    ctx->mcmcxhpch = (mcmhdef *)chunk;           /* set start of heap chain */
+    chunk += sizeof(mcmhdef);
+    rem -= sizeof(mcmhdef);
+    ctx->mcmcxhpch->mcmhnxt = (mcmhdef *)0;    /* no next heap in chain yet */
+    
+    /* allocate the first page */
+    *(mcmon *)chunk = 0;               /* set object number header in chunk */
+    chunk += osrndsz(sizeof(mcmon));
+    rem -= osrndsz(sizeof(mcmon));
+
+    ctx->mcmcxtab[0] = (mcmodef *)chunk;          /* put at bottom of chunk */
+    memset(ctx->mcmcxtab[0], 0, (size_t)MCMPAGESIZE);
+    chunk += MCMPAGESIZE;                           /* reflect size of page */
+    rem -= MCMPAGESIZE;                     /* take it out of the remainder */
+    
+    /* set up the first page with an entry for itself */
+    obj = mcmgobje(ctx, (mcmon)0);             /* point to first page entry */
+    obj->mcmoflg = MCMOFPRES | MCMOFNODISC | MCMOFPAGE | MCMOFNOSWAP;
+    obj->mcmoptr = (uchar *)ctx->mcmcxtab[0];
+    obj->mcmosiz = MCMPAGESIZE;
+    
+    /* set up the rest of the context */
+    ctx->mcmcxlru = ctx->mcmcxmru = MCMONINV;        /* no mru/lru list yet */
+    ctx->mcmcxmax = max - (ulong)MCMCHUNK;
+    ctx->mcmcxpage = 1;        /* next page slot to be allocated will be #1 */
+    ctx->mcmcxpgmx = pages;          /* max number of pages we can allocate */
+    ctx->mcmcxerr = errctx;
+    ctx->mcmcxcsw = mcmcswf;
+    
+    /* set up the free list with the remainder of the chunk */
+    ctx->mcmcxfre = 1;     /* we've allocated object 0; obj 1 is free space */
+    obj = mcmgobje(ctx, ctx->mcmcxfre);       /* point to free object entry */
+    obj->mcmonxt = obj->mcmoprv = MCMONINV;             /* end of free list */
+    obj->mcmoflg = MCMOFFREE;                /* mark the free block as such */
+    *(mcmon *)chunk = ctx->mcmcxfre;                /* set free list header */
+
+    chunk += osrndsz(sizeof(mcmon));
+    rem -= osrndsz(sizeof(mcmon));
+    obj->mcmoptr = chunk;                                  /* rest of chunk */
+
+    obj->mcmosiz = rem - osrndsz(sizeof(mcmon));          /* remaining size in chunk */
+
+    /* set flag for end of chunk (invalid object header) */
+    *((mcmon *)(chunk + rem - osrndsz(sizeof(mcmon)))) = MCMONINV;
+    
+    /* set up the unused entry list with the remaining headers in the page */
+    mcmadpg(ctx, 0, 2);
+    
+    return(ctx);
+}
+
+/*
+ *   Uninitialize the cache manager.  Frees the memory allocated for the
+ *   cache, including the context structure itself.  
+ */
+void mcmterm(mcmcx1def *ctx)
+{
+    mcmhdef *cur, *nxt;
+    
+    /* 
+     *   Free each chunk in the cache block list, *except* the last one.  The
+     *   last one is special: it's actually the first chunk allocated, since
+     *   we build the list in reverse order, and the first chunk pointer
+     *   points into the middle of the actual allocation block, since we
+     *   sub-allocated the context structure itself and the page table out of
+     *   that memory. 
+     */
+    for (cur = ctx->mcmcxhpch ; cur != 0 && cur->mcmhnxt != 0 ; cur = nxt)
+    {
+        /* remember the next chunk, and delete this one */
+        nxt = cur->mcmhnxt;
+        mchfre(cur);
+    }
+
+    /* 
+     *   As described above, the last chunk in the list is the first
+     *   allocated, and it points into the middle of the actual allocated
+     *   memory block.  Luckily, we do have a handy pointer to the start of
+     *   the memory block, namely the context pointer - it's the first thing
+     *   allocated out of the block, so it's the same as the block pointer.
+     *   Freeing the context frees this last/first chunk. 
+     */
+    mchfre(ctx);
+}
+
+/*
+ *   Allocate a new object, returning a pointer to its memory.  The new
+ *   object is locked upon return.  The object number for the new object
+ *   is returned at *nump.
+ */
+static uchar *mcmalo1(mcmcx1def *ctx, ushort siz, mcmon *nump)
+{
+    mcmon    n;
+    mcmodef *o;
+    uchar   *chunk;
+    
+    MCMGLBCTX(ctx);
+
+    /* round size to appropriate multiple */
+    siz = osrndsz(siz);
+
+    /* if it's bigger than the chunk size, we can't allocate it */
+    if (siz > MCMCHUNK)
+        errsig(ctx->mcmcxerr, ERR_BIGOBJ);
+
+startover:
+    /* look in the free block chain for a fit to the request */
+    o = mcmffb(ctx, siz, &n);
+    if (n != MCMONINV)
+    {
+        mcmsplt(ctx, n, siz);               /* split the block if necessary */
+        mcmgobje(ctx, n)->mcmoflg = MCMOFNODISC | MCMOFLOCK | MCMOFPRES;
+        mcmgobje(ctx, n)->mcmolcnt = 1;                /* one locker so far */
+        *nump = n;
+        return(o->mcmoptr);
+    }
+    
+    /* nothing found; we must get space out of the heap if possible */
+    chunk = mcmhalo(ctx);                            /* get space from heap */
+    if (!chunk) goto error;           /* can't get any more space from heap */
+    o = mcmoal(ctx, &n);               /* set up cache entry for free space */
+    if (n == MCMONINV)
+    {
+        mcmhdef *chunk_hdr = ((mcmhdef *)chunk) - 1;
+        ctx->mcmcxhpch = chunk_hdr->mcmhnxt;
+        mchfre(chunk_hdr);
+        goto error;         /* any error means we can't allocate the memory */
+    }
+    
+    *(mcmon *)chunk = n;                               /* set object header */
+    chunk += osrndsz(sizeof(mcmon));
+    o->mcmoptr = chunk;
+    o->mcmosiz = MCMCHUNK - osrndsz(sizeof(mcmon));
+    o->mcmoflg = MCMOFFREE;
+    mcmlnkhd(ctx, &ctx->mcmcxfre, n);
+    goto startover;              /* try again, now that we have some memory */
+    
+error:
+    *nump = MCMONINV;
+    return((uchar *)0);
+}
+
+static void mcmcliexp(mcmcxdef *cctx, mcmon clinum)
+{
+    /* add global number to client mapping table at client number */
+    if (cctx->mcmcxmtb[clinum >> 8] == (mcmon *)0)
+    {
+        mcmcx1def *ctx = cctx->mcmcxgl;
+        int        i;
+        mcmon     *p;
+        
+        /* this page is not allocated - allocate it */
+        p = (mcmon *)mchalo(ctx->mcmcxerr, (256 * sizeof(mcmon)),
+                            "client mapping page");
+        cctx->mcmcxmtb[clinum >> 8] = p;
+        for (i = 0 ; i < 256 ; ++i) *p++ = MCMONINV;
+    }
+}
+
+/* high-level allocate:  try, collect garbage, then try again */
+uchar *mcmalo0(mcmcxdef *cctx, ushort siz, mcmon *nump,
+               mcmon clinum, int noclitrans)
+{
+    uchar     *ret;
+    mcmcx1def *ctx = cctx->mcmcxgl;                       /* global context */
+    mcmon      glb;                       /* global object number allocated */
+    
+    MCMCLICTX(cctx);
+    MCMGLBCTX(ctx);
+    
+    /* try once */
+    if ((ret = mcmalo1(ctx, siz, &glb)) != 0)
+        goto done;
+
+    /* collect some garbage */
+    mcmgarb(ctx);
+    
+    /* try swapping until we get the memory or have nothing left to swap */
+    for ( ;; )
+    {
+        /* try again */
+        if ((ret = mcmalo1(ctx, siz, &glb)) != 0)
+            goto done;
+
+        /* nothing left to swap? */
+        if (!mcmswap(ctx, siz))
+            break;
+
+        /* try yet again */
+        if ((ret = mcmalo1(ctx, siz, &glb)) != 0)
+            goto done;
+
+        /* collect garbage once again */        
+        mcmgarb(ctx);
+    }
+    
+    /* try again */
+    if ((ret = mcmalo1(ctx, siz, &glb)) != 0)
+        goto done;
+    
+    /* we have no other way of getting more memory, so signal an error */
+    errsig(ctx->mcmcxerr, ERR_NOMEM1);
+    NOTREACHEDV(uchar *);
+    
+done:
+    if (noclitrans)
+    {
+        *nump = glb;
+        return(ret);
+    }
+    
+    /* we have an object - generate client number */
+    if (clinum == MCMONINV)
+    {
+        /* find a free number */
+        mcmon **p;
+        uint    i;
+        mcmon   j = 0;
+        mcmon  *q;
+        int     found = FALSE;
+        int     unused = -1;
+        
+        for (i = 0, p = cctx->mcmcxmtb ; i < cctx->mcmcxmsz ; ++i, ++p)
+        {
+            if (*p)
+            {
+                for (j = 0, q = *p ; j < 256 ; ++j, ++q)
+                {
+                    if (*q == MCMONINV)
+                    {
+                        found = TRUE;
+                        break;
+                    }
+                }
+            }
+            else if (unused == -1)
+                unused = i;            /* note an unused page mapping table */
+
+            if (found) break;
+        }
+        
+        if (found)
+            clinum = (i << 8) + j;
+        else if (unused != -1)
+            clinum = (unused << 8);
+        else
+            errsig(ctx->mcmcxerr, ERR_CLIFULL);
+    }
+
+    /* expand client mapping table if necessary */
+    mcmcliexp(cctx, clinum);
+
+    /* make sure the entry isn't already in use */
+    if (mcmc2g(cctx, clinum) != MCMONINV)
+        errsig(ctx->mcmcxerr, ERR_CLIUSE);
+
+    cctx->mcmcxmtb[clinum >> 8][clinum & 255] = glb;
+    if (nump) *nump = clinum;
+    return(ret);
+}
+
+/* reserve space for an object at a client object number */
+void mcmrsrv(mcmcxdef *cctx, ushort siz, mcmon clinum, mclhd loadhd)
+{
+    mcmcx1def *ctx = cctx->mcmcxgl;                       /* global context */
+    mcmon      glb;                       /* global object number allocated */
+    mcmodef   *o;
+    
+    MCMCLICTX(cctx);
+    MCMGLBCTX(ctx);
+    
+    o = mcmoal(ctx, &glb);                       /* get a new object header */
+    if (!o) errsig(ctx->mcmcxerr, ERR_NOHDR);     /* can't get a new header */
+    
+    o->mcmoldh = loadhd;
+    o->mcmoflg = 0;
+    o->mcmosiz = siz;
+    
+    mcmcliexp(cctx, clinum);
+    if (mcmc2g(cctx, clinum) != MCMONINV)
+        errsig(ctx->mcmcxerr, ERR_CLIUSE);
+    
+    cctx->mcmcxmtb[clinum >> 8][clinum & 255] = glb;
+}
+
+/* resize an existing object */
+uchar *mcmrealo(mcmcxdef *cctx, mcmon cliobj, ushort newsize)
+{
+    mcmcx1def *ctx = cctx->mcmcxgl;                       /* global context */
+    mcmon      obj = mcmc2g(cctx, cliobj); 
+    mcmodef   *o = mcmgobje(ctx, obj);
+    mcmon      nxt;
+    mcmodef   *nxto;
+    uchar     *p;
+    int        local_lock;
+    
+    MCMCLICTX(cctx);
+    MCMGLBCTX(ctx);
+    
+    newsize = osrndsz(newsize);
+    
+    /* make sure the object is locked, and note if we locked it */
+    if ((local_lock = !(o->mcmoflg & MCMOFLOCK)) != 0)
+        (void)mcmlck(cctx, cliobj);
+    
+    ERRBEGIN(ctx->mcmcxerr)
+    
+    if (newsize < o->mcmosiz)
+        mcmsplt(ctx, obj, newsize);            /* smaller; just split block */
+    else
+    {
+        /* see if there's a free block after this block */
+        p = o->mcmoptr;
+        nxt = *(mcmon *)(p + o->mcmosiz);
+        nxto = (nxt == MCMONINV) ? (mcmodef *)0 : mcmgobje(ctx, nxt);
+        
+        if (nxto && ((nxto->mcmoflg & MCMOFFREE)
+                     && nxto->mcmosiz >= newsize - o->mcmosiz))
+        {
+            /* sanity check - make sure heap and page table agree */
+            assert(nxto->mcmoptr == p + o->mcmosiz + osrndsz(sizeof(mcmon)));
+            /* annex the free block */
+            o->mcmosiz += nxto->mcmosiz + osrndsz(sizeof(mcmon));
+            /* move the free block to the unused list */
+            mcmunl(ctx, nxt, &ctx->mcmcxfre);
+            nxto->mcmonxt = ctx->mcmcxunu;
+            ctx->mcmcxunu = nxt;
+            nxto->mcmoflg = 0;
+            
+            /* split the newly grown block if necessary */
+            mcmsplt(ctx, obj, newsize);
+        }
+        else
+        {
+            /* can't annex; allocate new memory and copy */
+            
+            if (o->mcmolcnt != 1)           /* if anyone else has a lock... */
+                errsig(ctx->mcmcxerr, ERR_REALCK);      /* we can't move it */
+    
+            p = mcmalo0(cctx, newsize, &nxt, MCMONINV, TRUE);
+            if (nxt == MCMONINV) errsig(ctx->mcmcxerr, ERR_NOMEM2);
+            memcpy(p, o->mcmoptr, (size_t)o->mcmosiz);
+            
+            /* adjust the object entries */
+            nxto = mcmgobje(ctx, nxt);          /* get pointer to new entry */
+            newsize = nxto->mcmosiz;        /* get actual size of new block */
+            nxto->mcmoptr = o->mcmoptr;   /* copy current block info to new */
+            nxto->mcmosiz = o->mcmosiz;
+            o->mcmoptr = p;        /* copy new block info to original entry */
+            o->mcmosiz = newsize;
+            
+            /* now fix up the heap pointers, and free the temp object */
+            *(mcmon *)(p - osrndsz(sizeof(mcmon))) = obj;
+            *(mcmon *)(nxto->mcmoptr - osrndsz(sizeof(mcmon))) = nxt;
+            mcmgunlck(ctx, nxt);
+            mcmgfre(ctx, nxt);
+        }
+    }
+    
+    ERRCLEAN(ctx->mcmcxerr)
+        /* release our lock, if we had to obtain one */
+        if (local_lock) mcmunlck(cctx, cliobj);
+    ERRENDCLN(ctx->mcmcxerr)
+    
+    /* return the address of the object */
+    return(o->mcmoptr);
+}
+
+/*
+ *   Free an object by GLOBAL number:  move object to free list.
+ */
+void mcmgfre(mcmcx1def *ctx, mcmon obj)
+{
+    mcmodef   *o = mcmgobje(ctx, obj);
+    
+    MCMGLBCTX(ctx);
+    
+    /* signal an error if the object is locked */
+    if (o->mcmolcnt) errsig(ctx->mcmcxerr, ERR_LCKFRE);
+
+    /* take out of LRU chain if it's in the chain */
+    if (o->mcmoflg & MCMOFLRU) mcmunl(ctx, obj, &ctx->mcmcxlru);
+    
+    /* put it in the free list */
+    mcmlnkhd(ctx, &ctx->mcmcxfre, obj);
+    o->mcmoflg = MCMOFFREE;
+}
+
+/*
+ *   load and lock an object that has been swapped out or discarded 
+ */
+uchar *mcmload(mcmcxdef *cctx, mcmon cnum)
+{
+    mcmcx1def   *ctx = cctx->mcmcxgl;
+    mcmodef     *o = mcmobje(cctx, cnum);
+    mcmodef     *newdef;
+    mcmon        newn;
+    mcmon        num = mcmc2g(cctx, cnum);
+    
+    MCMCLICTX(cctx);
+    MCMGLBCTX(ctx);
+
+    /* we first need to obtain some memory for this object */
+    (void)mcmalo0(cctx, o->mcmosiz, &newn, MCMONINV, TRUE);
+    newdef = mcmgobje(ctx, newn);
+    
+    /* use memory block from our new object */
+    o->mcmoptr = newdef->mcmoptr;
+    o->mcmosiz = newdef->mcmosiz;
+
+    /* load or swap the object in */
+    ERRBEGIN(ctx->mcmcxerr)
+        if (o->mcmoflg & (MCMOFNODISC | MCMOFDIRTY))
+            mcsin(&ctx->mcmcxswc, o->mcmoswh, o->mcmoptr, o->mcmosiz);
+        else if (cctx->mcmcxldf)
+            (*cctx->mcmcxldf)(cctx->mcmcxldc, o->mcmoldh, o->mcmoptr,
+                              o->mcmosiz);
+        else
+            errsig(ctx->mcmcxerr, ERR_NOLOAD);
+    ERRCLEAN(ctx->mcmcxerr)
+        mcmgunlck(ctx, newn);                          /* unlock the object */
+        mcmgfre(ctx, newn);              /* don't need new memory after all */
+    ERRENDCLN(ctx->mcmcxerr)
+
+    /* unuse the new cache entry we obtained (we just wanted the memory) */
+/* @@@ */
+    *(mcmon *)(o->mcmoptr - osrndsz(sizeof(mcmon))) = num;      /* set obj# */
+    newdef->mcmoflg = 0;                        /* mark new block as unused */
+    newdef->mcmonxt = ctx->mcmcxunu;                /* link to unused chain */
+    ctx->mcmcxunu = newn;
+
+    /* set flags in the newly loaded object and return */
+    o->mcmoflg |= MCMOFPRES | MCMOFLOCK; /* object is now present in memory */
+    o->mcmoflg &= ~MCMOFDIRTY;         /* not written since last swapped in */
+    o->mcmoflg |= MCMOFNODISC; /* don't discard once it's been to swap file */
+    o->mcmolcnt = 1;                                   /* one locker so far */
+    
+    /* if the object is to be reverted upon loading, revert it now */
+    if (o->mcmoflg & MCMOFREVRT)
+    {
+        (*cctx->mcmcxrvf)(cctx->mcmcxrvc, cnum);
+        o->mcmoflg &= ~MCMOFREVRT;
+    }
+
+    return(o->mcmoptr);
+}
+
+/*
+ *   Allocate a new object header.  This doesn't allocate an object, just
+ *   the header for one. 
+ */
+static mcmodef *mcmoal(mcmcx1def *ctx, mcmon *nump)
+{
+    mcmodef  *ret;
+    uint      pagenum;
+    
+    MCMGLBCTX(ctx);
+    
+    /* look first in list of unused headers */
+startover:
+    if (ctx->mcmcxunu != MCMONINV)
+    {
+        /* we have something in the unused list; return it */
+        *nump = ctx->mcmcxunu;
+        ret = mcmgobje(ctx, *nump);
+        ctx->mcmcxunu = ret->mcmonxt;
+        ret->mcmoswh = MCSSEGINV;
+        return(ret);
+    }
+    
+    /*
+     *   No unused entries: we must create a new page.  To do so, we
+     *   simply allocate memory for a new page.  Allocate the memory
+     *   ourselves, to avoid deadlocking with the allocator (which can
+     *   try to get a new entry to satisfy our request for memory).
+     */
+    if (ctx->mcmcxpage == ctx->mcmcxpgmx) goto error;      /* no more pages */
+    pagenum = ctx->mcmcxpage++;                      /* get a new page slot */
+    
+    ctx->mcmcxtab[pagenum] =
+         (mcmodef *)mchalo(ctx->mcmcxerr, MCMPAGESIZE, "mcmoal");
+    mcmadpg(ctx, pagenum, MCMONINV);
+    goto startover;
+
+error:
+    *nump = MCMONINV;
+    return((mcmodef *)0);
+}
+
+/* find free block:  find a block from the free pool to satisfy allocation */
+static mcmodef *mcmffb(mcmcx1def *ctx, ushort siz, mcmon *nump)
+{
+    mcmon    n;
+    mcmodef *o;
+    mcmon    minn;
+    mcmodef *mino;
+    ushort   min = 0;
+    
+    MCMGLBCTX(ctx);
+
+    for (minn = MCMONINV, mino = 0, n = ctx->mcmcxfre ; n != MCMONINV ;
+         n = o->mcmonxt)
+    {
+        o = mcmgobje(ctx, n);
+        if (o->mcmosiz == siz)
+        {
+            /* found exact match - use it immediately */
+            minn = n;
+            min = siz;
+            mino = o;
+            break;
+        }
+        else if (o->mcmosiz > siz)
+        {
+            /* found something at least as big; is it smallest yet? */
+            if (minn == MCMONINV || o->mcmosiz < min)
+            {
+                /* yes, best fit so far, use it; but keep looking */
+                minn = n;
+                mino = o;
+                min = o->mcmosiz;
+            }
+        }
+    }
+    
+    /* if we found something, remove from the free list */
+    if (minn != MCMONINV)
+    {
+        mcmunl(ctx, minn, &ctx->mcmcxfre);
+        mino->mcmoflg &= ~MCMOFFREE;
+        mino->mcmoswh = MCSSEGINV;
+    }
+    
+    *nump = minn;
+    return mino;
+}
+
+/*
+ *   unlink an object header from one of the doubly-linked lists 
+ */
+static void mcmunl(mcmcx1def *ctx, mcmon n, mcmon *lst)
+{
+    mcmodef *o = mcmgobje(ctx, n);
+    mcmodef *nxt;
+    mcmodef *prv;
+    
+    MCMGLBCTX(ctx);
+
+    /* see if this is LRU chain - must deal with MRU pointer if so */
+    if (lst == &ctx->mcmcxlru)
+    {
+        /* if it's at MRU, set MRU pointer to previous object in list */
+        if (ctx->mcmcxmru == n)
+        {
+            ctx->mcmcxmru = o->mcmoprv;     /* set MRU to previous in chain */
+            if (ctx->mcmcxmru != MCMONINV)           /* set nxt for new MRU */
+                mcmgobje(ctx, ctx->mcmcxmru)->mcmonxt = MCMONINV;
+            else
+                ctx->mcmcxlru = MCMONINV;     /* nothing in list; clear LRU */
+        }
+        o->mcmoflg &= ~MCMOFLRU;
+    }
+    
+    nxt = o->mcmonxt == MCMONINV ? (mcmodef *)0 : mcmgobje(ctx, o->mcmonxt);
+    prv = o->mcmoprv == MCMONINV ? (mcmodef *)0 : mcmgobje(ctx, o->mcmoprv);
+    
+    /* set back link for next object, if there is a next object */
+    if (nxt) nxt->mcmoprv = o->mcmoprv;
+    
+    /* set forward link for previous object, or head if no previous object */
+    if (prv) prv->mcmonxt = o->mcmonxt;
+    else *lst = o->mcmonxt;
+    
+    o->mcmonxt = o->mcmoprv = MCMONINV;
+}
+
+/* link an item to the head of a doubly-linked list */
+static void mcmlnkhd(mcmcx1def *ctx, mcmon *lst, mcmon n)
+{
+    MCMGLBCTX(ctx);
+
+    if (*lst != MCMONINV) mcmgobje(ctx, *lst)->mcmoprv = n;
+    mcmgobje(ctx, n)->mcmonxt = *lst;      /* next is previous head of list */
+    *lst = n;                               /* make object new head of list */
+    mcmgobje(ctx, n)->mcmoprv = MCMONINV;     /* there is no previous entry */
+}
+
+/* add page pagenum, initializing entries after firstunu to unused */
+static void mcmadpg(mcmcx1def *ctx, uint pagenum, mcmon firstunu)
+{
+    mcmon    unu;
+    mcmodef *obj;
+    mcmon    lastunu;
+    
+    MCMGLBCTX(ctx);
+
+    unu = (firstunu == MCMONINV ? pagenum * MCMPAGECNT : firstunu);
+    ctx->mcmcxunu = unu;
+    lastunu = (pagenum * MCMPAGECNT) + MCMPAGECNT - 1;
+    for (obj = mcmgobje(ctx, unu) ; unu < lastunu ; ++obj)
+        obj->mcmonxt = ++unu;
+    obj->mcmonxt = MCMONINV;
+}
+
+/*
+ *   split a previously-free block into two chunks, adding the remainder
+ *   back into the free list, if there's enough left over 
+ */ 
+static void mcmsplt(mcmcx1def *ctx, mcmon n, ushort siz)
+{
+    mcmodef *o = mcmgobje(ctx, n);
+    mcmon    newn;
+    mcmodef *newp;
+    
+    MCMGLBCTX(ctx);
+
+    if (o->mcmosiz < siz + MCMSPLIT) return;     /* don't split; we're done */
+
+    newp = mcmoal(ctx, &newn);
+    if (newn == MCMONINV) return;         /* ignore error - just skip split */
+
+    /* set up the new entry, and link into free list */
+    *(mcmon *)(o->mcmoptr + siz) = newn;
+    newp->mcmoptr = o->mcmoptr + siz + osrndsz(sizeof(mcmon));
+    newp->mcmosiz = o->mcmosiz - siz - osrndsz(sizeof(mcmon));
+    newp->mcmoflg = MCMOFFREE;
+    mcmlnkhd(ctx, &ctx->mcmcxfre, newn);
+    
+    o->mcmosiz = siz;       /* size of new object is now exactly as request */
+}
+
+/* allocate a new chunk from the heap if possible */
+static uchar *mcmhalo(mcmcx1def *ctx)
+{
+    uchar  *chunk;
+    int     err;
+#   define  size (MCMCHUNK + sizeof(mcmhdef) + 2*osrndsz(sizeof(mcmon)))
+
+    VARUSED(err);
+
+    MCMGLBCTX(ctx);
+
+    if (ctx->mcmcxmax < MCMCHUNK) return((uchar *)0);
+
+    ERRBEGIN(ctx->mcmcxerr)
+        chunk = mchalo(ctx->mcmcxerr, size, "mcmhalo");
+    ERRCATCH(ctx->mcmcxerr, err)
+        ctx->mcmcxmax = 0;      /* remember we can't allocate anything more */
+        return((uchar *)0);                             /* return no memory */
+    ERREND(ctx->mcmcxerr)
+
+    ctx->mcmcxmax -= MCMCHUNK;
+    
+    /* link into heap chain */
+    ((mcmhdef *)chunk)->mcmhnxt = ctx->mcmcxhpch;
+    ctx->mcmcxhpch = (mcmhdef *)chunk;
+/*@@@@*/
+    *(mcmon *)(chunk + osrndsz(sizeof(mcmhdef) + MCMCHUNK)) = MCMONINV;
+    return(chunk + sizeof(mcmhdef));
+
+#   undef size
+}
+
+/* "use" an object - move to most-recent position in LRU chain */
+void mcmuse(mcmcx1def *ctx, mcmon obj)
+{
+    mcmodef   *o = mcmgobje(ctx, obj);
+    
+    MCMGLBCTX(ctx);
+
+    if (ctx->mcmcxmru == obj) return;         /* already MRU; nothing to do */
+    
+    /* remove from LRU chain if it's in it */
+    if (o->mcmoflg & MCMOFLRU) mcmunl(ctx, obj, &ctx->mcmcxlru);
+
+    /* set forward pointer of last block, if there is one */
+    if (ctx->mcmcxmru != MCMONINV)
+        mcmgobje(ctx, ctx->mcmcxmru)->mcmonxt = obj;
+    
+    o->mcmoprv = ctx->mcmcxmru;               /* point back to previous MRU */
+    o->mcmonxt = MCMONINV;                /* nothing in list after this one */
+    ctx->mcmcxmru = obj;                          /* point MRU to new block */
+    
+    /* if there's nothing in the chain at all, set LRU to this block, too */
+    if (ctx->mcmcxlru == MCMONINV) ctx->mcmcxlru = obj;
+
+    /* note that object is in LRU chain */
+    o->mcmoflg |= MCMOFLRU;
+}
+
+/* find next free block in a heap, starting with pointer */
+static uchar *mcmffh(mcmcx1def *ctx, uchar *p)
+{
+    mcmodef *o;
+
+    MCMGLBCTX(ctx);
+
+    while (*(mcmon *)p != MCMONINV)
+    {
+        o = mcmgobje(ctx, *(mcmon *)p);
+        assert(o->mcmoptr == p + osrndsz(sizeof(mcmon)));
+        if (o->mcmoflg & MCMOFFREE) return(p);
+        p += osrndsz(sizeof(mcmon)) + o->mcmosiz;  /* move on to next chunk */
+    }
+    return((uchar *)0);                      /* no more free blocks in heap */
+}
+
+#ifdef NEVER
+static void mcmmove(mcmcx1def *ctx, mcmodef *o, uchar *newpage)
+{
+    mcmodef **page;
+    
+    MCMGLBCTX(ctx);
+
+    /* see if we need to update page table (we do if moving a page) */
+    if (o->mcmoflg & MCMOFPAGE)
+    {
+        for (page = ctx->mcmcxtab ; *page ; ++page)
+        {
+            if (*page == (mcmodef *)(o->mcmoptr))
+            {
+                *page = (mcmodef *)newpag;
+                break;
+            }
+        }
+        if (!*page) printf("\n*** internal error - relocating page\n");
+    }
+    o->mcmoptr = newpage;
+}
+#endif /* NEVER */
+
+/* relocate blocks from p to (but not including) q */
+static uchar *mcmreloc(mcmcx1def *ctx, uchar *p, uchar *q)
+{
+    mcmodef *o;
+    ushort   dist;
+    mcmon    objnum;
+
+    MCMGLBCTX(ctx);
+
+    objnum = *(mcmon *)p;      /* get number of free block being bubbled up */
+    o = mcmgobje(ctx, objnum);                /* get pointer to free object */
+    assert(o->mcmoptr == p + osrndsz(sizeof(mcmon)));
+    dist = osrndsz(sizeof(mcmon)) + o->mcmosiz; /* compute distance to move */
+    mcmmove(ctx, o, q - dist + osrndsz(sizeof(mcmon)));  /* move obj to top */
+
+    memmove(p, p+dist, (size_t)(q - p - o->mcmosiz));        /* move memory */
+
+    /* update cache entries for the blocks we moved */
+    while (p != q - dist)
+    {
+        mcmmove(ctx, mcmgobje(ctx, *(mcmon *)p), p + osrndsz(sizeof(mcmon)));
+        p = mcmnxh(ctx, p);
+    }
+
+    *(mcmon *)(q - dist) = objnum;                       /* set bubbled num */
+    return(q - dist);               /* return new location of bubbled block */
+}
+
+/* consolidate the two (free) blocks starting at p into one block */
+static void mcmconsol(mcmcx1def *ctx, uchar *p)
+{
+    uchar   *q;
+    mcmodef *obj1, *obj2;
+    
+    MCMGLBCTX(ctx);
+
+    q = mcmnxh(ctx, p);
+    obj1 = mcmgobje(ctx, *(mcmon *)p);
+    obj2 = mcmgobje(ctx, *(mcmon *)q);
+    
+    assert(obj1->mcmoptr == p + osrndsz(sizeof(mcmon)));
+    assert(obj2->mcmoptr == q + osrndsz(sizeof(mcmon)));
+
+    obj1->mcmosiz += osrndsz(sizeof(mcmon)) + obj2->mcmosiz;
+    mcmunl(ctx, *(mcmon *)q, &ctx->mcmcxfre);
+                    
+    /* add second object entry to unused list */
+    obj2->mcmonxt = ctx->mcmcxunu;
+    ctx->mcmcxunu = *(mcmon *)q;
+    obj2->mcmoflg = 0;
+}
+
+/* attempt to compact all heaps by consolidating free space */
+static void mcmgarb(mcmcx1def *ctx)
+{
+    mcmhdef *h;
+    uchar   *p;
+    uchar   *q;
+    uchar   *nxt;
+    ushort   flags;
+    
+    MCMGLBCTX(ctx);
+
+    for (h = ctx->mcmcxhpch ; h ; h = h->mcmhnxt)
+    {
+        p = (uchar *)(h+1);                   /* get pointer to actual heap */
+        p = mcmffh(ctx, p);                 /* get first free block in heap */
+        if (!p) continue;             /* can't do anything - no free blocks */
+        nxt = mcmnxh(ctx, p);              /* remember immediate next block */
+        
+        for (q=p ;; )
+        {
+            q = mcmnxh(ctx, q);                  /* find next chunk in heap */
+            if (*(mcmon *)q == MCMONINV) break;      /* reached end of heap */
+            assert(mcmgobje(ctx, *(mcmon *)q)->mcmoptr
+                   == q + osrndsz(sizeof(mcmon)));
+            flags = mcmgobje(ctx, *(mcmon *)q)->mcmoflg;       /* get flags */
+
+            /* if the block is locked, p can't be relocated */
+            if (flags & MCMOFLOCK)
+            {
+                p = mcmffh(ctx, q);         /* find next free block after p */
+                q = p;
+                if (p) continue;   /* try again; start with next free block */
+                else break;         /* no more free blocks - done with heap */
+            }
+
+            /* if the block is free, we can relocate between p and q */
+            if (flags & MCMOFFREE)
+            {
+                if (q != nxt) p = mcmreloc(ctx, p, q);          /* relocate */
+                mcmconsol(ctx, p);           /* consolidate two free blocks */
+                
+                /* resume looking, starting with consolidated block */
+                nxt = mcmnxh(ctx, p);
+                q = p;
+                continue;
+            }
+        }
+    }
+}
+
+/* toss out a particular object */
+static int mcmtoss(mcmcx1def *ctx, mcmon n)
+{
+    mcmodef *o = mcmgobje(ctx, n);
+    mcmodef *newp;
+    mcmon    newn;
+
+    MCMGLBCTX(ctx);
+
+    /* make a new block for the free space */
+    newp = mcmoal(ctx, &newn);
+    if (newn == MCMONINV)
+        return(FALSE);           /* ignore the error, but can't toss it out */
+
+    /* write object to swap file if not discardable */
+    if (o->mcmoflg & (MCMOFNODISC | MCMOFDIRTY))
+    {
+        mcsseg old_swap_seg;
+        
+        /*
+         *   If this object was last loaded out of the load file, rather
+         *   than the swap file, don't attempt to find it in the swap file
+         *   -- so note by setting the old swap segment parameter to null.
+         */
+        if (!(o->mcmoflg & MCMOFNODISC))
+            old_swap_seg = o->mcmoswh;
+        else
+            old_swap_seg = MCSSEGINV;
+        
+        o->mcmoswh = mcsout(&ctx->mcmcxswc, (uint)n, o->mcmoptr, o->mcmosiz,
+                            old_swap_seg, o->mcmoflg & MCMOFDIRTY);
+    }
+    
+    /* give the object's space to the newly created block */
+    newp->mcmoptr = o->mcmoptr;
+    newp->mcmosiz = o->mcmosiz;
+    newp->mcmoflg = MCMOFFREE;
+/*@@@*/
+    *(mcmon *)(o->mcmoptr - osrndsz(sizeof(mcmon))) = newn;
+    mcmlnkhd(ctx, &ctx->mcmcxfre, newn);
+    
+    o->mcmoflg &= ~MCMOFPRES;              /* object is no longer in memory */
+    mcmunl(ctx, n, &ctx->mcmcxlru);                 /* remove from LRU list */
+    return(TRUE);                             /* successful, so return TRUE */
+}
+
+/* swap or discard to make room for siz; return 0 if nothing swapped */
+static int mcmswap(mcmcx1def *ctx, ushort siz)
+{
+    mcmon    n;
+    mcmodef *o;
+    mcmon    nxt;
+    int      pass;                     /* pass 1: swap one piece big enough */
+                      /* pass 2: swap enough pieces to add up to right size */
+    ushort   tot;
+
+    MCMGLBCTX(ctx);
+
+    for (pass = 1, tot = 0 ; pass < 3 && tot < siz ; ++pass)
+    {
+        for (n = ctx->mcmcxlru ; n != MCMONINV && tot < siz ; n = nxt)
+        {
+            o = mcmgobje(ctx, n);
+            nxt = o->mcmonxt;             /* get next now, as we may unlink */
+            if (!(o->mcmoflg & (MCMOFLOCK | MCMOFNOSWAP | MCMOFPAGE))
+                 && (pass == 2 || o->mcmosiz >= siz))
+            {
+                /* toss out, and add into size if successful */
+                if (mcmtoss(ctx, n)) tot += o->mcmosiz;
+            }
+        }
+    }
+    
+    /* if we managed to remove anything, return TRUE, otherwise FALSE */
+    return(tot != 0);
+}
+
+/* compute size of cache */
+ulong mcmcsiz(mcmcxdef *cctx)
+{
+    mcmcx1def *ctx = cctx->mcmcxgl;
+    mcmhdef   *p;
+    ulong      tot;
+    
+    MCMCLICTX(cctx);
+    MCMGLBCTX(ctx);
+    
+    /* count number of heaps, adding in chunk size for each */
+    for (tot = 0, p = ctx->mcmcxhpch ; p ; p = p->mcmhnxt)
+        tot += MCMCHUNK;
+    
+    return(tot);
+}
+
+#ifdef MCM_NO_MACRO
+/* routines that can be either macros or functions */
+
+uchar *mcmlck(mcmcxdef *ctx, mcmon objnum)
+{
+    mcmodef *o = mcmobje(ctx, objnum);
+
+    if ((o->mcmoflg & MCMOFFREE) != 0 || mcmc2g(ctx, objnum) == MCMONINV)
+    {
+        errsig(ctx->mcmcxgl->mcmcxerr, ERR_INVOBJ);
+        return 0;
+    }
+    else if (o->mcmoflg & MCMOFPRES)
+    {
+        o->mcmoflg |= MCMOFLOCK;
+        ++(o->mcmolcnt);
+        return(o->mcmoptr);
+    }
+    else
+        return(mcmload(ctx, objnum));
+}
+
+void mcmunlck(mcmcxdef *ctx, mcmon obj)
+{
+    mcmodef *o = mcmobje(ctx, obj);
+    
+    if (o->mcmoflg & MCMOFLOCK)
+    {
+        if (!(--(o->mcmolcnt)))
+        {
+            o->mcmoflg &= ~MCMOFLOCK;
+            mcmuse(ctx->mcmcxgl, mcmc2g(ctx, obj));
+        }
+    }
+}
+
+void mcmgunlck(mcmcx1def *ctx, mcmon obj)
+{
+    mcmodef *o = mcmgobje(ctx, obj);
+    
+    if (o->mcmoflg & MCMOFLOCK)
+    {
+        if (!(--(o->mcmolcnt)))
+        {
+            o->mcmoflg &= ~MCMOFLOCK;
+            mcmuse(ctx, obj);
+        }
+    }
+}
+
+#endif /* MCM_NO_MACRO */
+
+/*
+ *   Change an object's swap file handle.  This routine will only be
+ *   called for an object that is either present or swapped out (i.e., an
+ *   object with a valid mcsseg number in its swap state).  
+ */
+void mcmcswf(mcmcx1def *ctx, mcmon objn, mcsseg swapn, mcsseg oldswapn)
+{
+    mcmodef *o = mcmgobje(ctx, objn);
+    
+    MCMGLBCTX(ctx);
+    
+    /*
+     *   Reset the swap number only if the object is swapped out and its
+     *   swap file number matches the old one, or the object is currently
+     *   present (in which case the swap file number is irrelevant and can
+     *   be replaced).  
+     */
+    if (((o->mcmoflg & (MCMOFDIRTY | MCMOFNODISC)) && o->mcmoswh == oldswapn)
+        || (o->mcmoflg & MCMOFPRES))
+        o->mcmoswh = swapn;
+}
+
+
+void mcmfre(mcmcxdef *ctx, mcmon obj)
+{
+    /* free the actual object */
+    mcmgfre(ctx->mcmcxgl, mcmc2g(ctx, obj));
+
+    /* unmap the client object number */
+    mcmc2g(ctx, obj) = MCMONINV;
+}
+
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache_loader.cpp b/engines/glk/tads/tads2/memory_cache_loader.cpp
deleted file mode 100644
index f403299..0000000
--- a/engines/glk/tads/tads2/memory_cache_loader.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "glk/tads/tads2/memory_cache_loader.h"
-
-namespace Glk {
-namespace TADS {
-namespace TADS2 {
-
-} // End of namespace TADS2
-} // End of namespace TADS
-} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/memory_cache_swap.cpp b/engines/glk/tads/tads2/memory_cache_swap.cpp
index 114a725..8d78892 100644
--- a/engines/glk/tads/tads2/memory_cache_swap.cpp
+++ b/engines/glk/tads/tads2/memory_cache_swap.cpp
@@ -21,11 +21,311 @@
  */
 
 #include "glk/tads/tads2/memory_cache_swap.h"
+#include "glk/tads/tads2/memory_cache.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/error.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
+
+/* initialize swapper:  allocate memory for swap page table */
+void mcsini(mcscxdef *ctx, mcmcx1def *gmemctx, ulong maxsiz,
+            osfildef *fp, char *swapfilename, errcxdef *errctx)
+{
+    uchar  *p;
+
+    ctx->mcscxtab = (mcsdsdef **)0;                   /* anticipate failure */
+    
+    /* allocate space from the low-level heap for page table and one page */
+    p = mchalo(errctx, ((MCSPAGETAB * sizeof(mcsdsdef *))
+                        + (MCSPAGECNT * sizeof(mcsdsdef))), "mcsini");
+    
+    /* set up the context with pointers to this chunk */
+    ctx->mcscxtab = (mcsdsdef **)p;
+    memset(p, 0, (size_t)(MCSPAGETAB * sizeof(mcsdsdef *)));
+    p += MCSPAGETAB * sizeof(mcsdsdef *);
+    ctx->mcscxtab[0] = (mcsdsdef *)p;
+    
+    /* set up the rest of the context */
+    ctx->mcscxtop = (ulong)0;
+    ctx->mcscxmax = maxsiz;
+    ctx->mcscxmsg = 0;
+    ctx->mcscxfp  = fp;
+    ctx->mcscxerr = errctx;
+    ctx->mcscxmem = gmemctx;
+
+    /* 
+     *   store the swap filename - make a copy so that the caller doesn't
+     *   have to retain the original copy (in case it's on the stack) 
+     */
+    if (swapfilename != 0)
+    {
+        ctx->mcscxfname = (char *)mchalo(errctx,
+                                         (strlen(swapfilename)+1),
+                                         "mcsini");
+        strcpy(ctx->mcscxfname, swapfilename);
+    }
+    else
+        ctx->mcscxfname = 0;
+}
+
+/* close the swapper */
+void mcsclose(mcscxdef *ctx)
+{
+    if (ctx->mcscxtab) mchfre(ctx->mcscxtab);
+}
+
+/*
+ *   Attempt to compact the swap file when it grows too big.  The segment
+ *   descriptors are always allocated in increasing seek location within
+ *   the swap file.  To compress the file, make each descriptor's
+ *   allocated size equal its used size for each in-use segment, and leave
+ *   free segments at their allocated sizes.
+ */
+static void mcscompact(mcscxdef *ctx)
+{
+    char      buf[512];
+    ulong     max;
+    mcsseg    cur_in;
+    mcsseg    cur_out;
+    mcsdsdef *desc_in;
+    mcsdsdef *desc_out;
+    uint      siz;
+    uint      rdsiz;
+    ulong     ptr_in;
+    ulong     ptr_out;
+    
+    max = 0;                            /* start at offset zero within file */
+    for (cur_in = cur_out = 0 ; cur_in < ctx->mcscxmsg ; ++cur_in)
+    {
+        desc_in = mcsdsc(ctx, cur_in);
+        
+        /*
+         *   If the present descriptor's address is wrong, and the swap
+         *   segment is in use, move the swap segment.  If it's not in
+         *   use, we don't need to move it, because we're going to throw
+         *   away the segment entirely.  
+         */
+        if (desc_in->mcsdsptr != max
+            && (desc_in->mcsdsflg & MCSDSFINUSE))
+        {
+            /* ptr_in is the old location, ptr_out is the new location */
+            ptr_in = desc_in->mcsdsptr;
+            ptr_out = max;
+            
+            /* copy through our buffer */
+            for (siz = desc_in->mcsdsosz ; siz ; siz -= rdsiz)
+            {
+                /* size is whole buffer, or last piece if smaller */
+                rdsiz = (siz > sizeof(buf) ? sizeof(buf) : siz);
+
+                /* seek to old location and get the piece */
+                osfseek(ctx->mcscxfp, ptr_in, OSFSK_SET);
+                (void)osfrb(ctx->mcscxfp, buf, (size_t)rdsiz);
+                
+                /* seek to new location and write the piece */
+                osfseek(ctx->mcscxfp, ptr_out, OSFSK_SET);
+                (void)osfwb(ctx->mcscxfp, buf, (size_t)rdsiz);
+                
+                /* adjust the pointers by the size copied */
+                ptr_in += rdsiz;
+                ptr_out += rdsiz;
+            }
+        }
+        
+        /* adjust object descriptor to reflect new location */
+        desc_in->mcsdsptr = max;
+        
+        /*
+         *   Make current object's size exact if it's in use.  If it's
+         *   not in use, delete the segment altogether.
+         */
+        if (desc_in->mcsdsflg & MCSDSFINUSE)
+        {
+            desc_in->mcsdssiz = desc_in->mcsdsosz;
+            max += desc_in->mcsdssiz;
+            
+            /* copy descriptor to correct position to close any holes */
+            if (cur_out != cur_in)
+            {
+                desc_out = mcsdsc(ctx, cur_out);
+                OSCPYSTRUCT(*desc_out, *desc_in);
+                
+                /* we need to renumber the corresponding object as well */
+                mcmcsw(ctx->mcscxmem, (mcmon)desc_in->mcsdsobj,
+                       cur_out, cur_in);
+            }
+            
+            /* we actually wrote this one, so move output pointer */
+            ++cur_out;
+        }
+        else
+        {
+            /*
+             *   We need to renumber the corresponding object so that it
+             *   knows there is no swap segment for it any more.  
+             */
+            mcmcsw(ctx->mcscxmem, (mcmon)desc_in->mcsdsobj,
+                   MCSSEGINV, cur_in);
+        }
+    }
+    
+    /*
+     *   Adjust the top of the file for our new size, and add the savings
+     *   into the available space counter.  Also, adjust the total handle
+     *   count to reflect any descriptors that we've deleted.
+     */
+    ctx->mcscxmax += (ctx->mcscxtop - max);
+    ctx->mcscxtop = max;
+    ctx->mcscxmsg = cur_out;
+}
+
+/* swap an object out to the swap file */
+mcsseg mcsout(mcscxdef *ctx, uint objid, uchar *ptr, ushort siz,
+              mcsseg oldseg, int dirty)
+{
+    mcsdsdef  *desc;
+    mcsdsdef **pagep;
+    uint       i;
+    uint       j;
+    mcsseg     min;
+    mcsseg     cur;
+	ushort     minsiz = 0;
+    
+    IF_DEBUG(printf("<< mcsout: objid=%d, ptr=%lx, siz=%u, oldseg=%u >>\n",
+                    objid, (unsigned long)ptr, siz, oldseg));
+    
+    /* see if old segment can be reused */
+    if (oldseg != MCSSEGINV)
+    {
+        desc = mcsdsc(ctx, oldseg);
+        if (!(desc->mcsdsflg & MCSDSFINUSE)     /* if old seg is not in use */
+            && desc->mcsdsobj == objid            /* and it has same object */
+            && desc->mcsdssiz >= siz           /* and it's still big enough */
+            && !dirty)      /* and the object in memory hasn't been changed */
+        {
+            /* we can reuse the old segment without rewriting it */
+            desc->mcsdsflg |= MCSDSFINUSE;        /* mark segment as in use */
+            return(oldseg);
+        }
+    }
+    
+    /* look for the smallest unused segment big enough for this object */
+    for (cur = 0, min = MCSSEGINV, i = 0, pagep = ctx->mcscxtab
+         ; cur < ctx->mcscxmsg && i < MCSPAGETAB && *pagep ; ++pagep, ++i)
+    {
+        for (j = 0, desc = *pagep ; cur < ctx->mcscxmsg && j < MCSPAGECNT
+             ; ++desc, ++j, ++cur)
+        {
+            if (!(desc->mcsdsflg & MCSDSFINUSE)
+                && desc->mcsdssiz >= siz
+                && (min == MCSSEGINV || desc->mcsdssiz < minsiz))
+            {
+                min = cur;
+                minsiz = desc->mcsdssiz;
+                if (minsiz == siz) break;       /* exact match - we're done */
+            }
+        }
+        /* quit if we found an exact match */
+        if (min != MCSSEGINV && minsiz == siz) break;
+    }
+    
+    /* if we found nothing, allocate a new segment if possible */
+    if (min == MCSSEGINV)
+    {
+        if (siz > ctx->mcscxmax)
+        {
+            /* swap file is too big; compact it and try again */
+            mcscompact(ctx);
+            if (siz > ctx->mcscxmax)
+                errsig(ctx->mcscxerr, ERR_SWAPBIG);
+        }
+            
+        min = ctx->mcscxmsg;
+        if ((min >> 8) >= MCSPAGETAB)      /* exceeded pages in page table? */
+            errsig(ctx->mcscxerr, ERR_SWAPPG);
+        
+        if (!ctx->mcscxtab[min >> 8])         /* haven't allocate page yet? */
+        {
+            ctx->mcscxtab[min >> 8] = 
+                (mcsdsdef *)mchalo(ctx->mcscxerr,
+                                   (MCSPAGECNT * sizeof(mcsdsdef)),
+                                   "mcsout");
+        }
+
+        /* set up new descriptor */
+        desc = mcsdsc(ctx, min);
+        desc->mcsdsptr = ctx->mcscxtop;
+        desc->mcsdssiz = siz;
+        desc->mcsdsobj = objid;
+
+        /* write out the segment */
+        mcswrt(ctx, desc, ptr, siz);
+        desc->mcsdsflg = MCSDSFINUSE;
+
+        /* update context information to account for new segment */
+        ctx->mcscxtop += siz;             /* add to top seek offset in file */
+        ctx->mcscxmax -= siz;                     /* take size out of quota */
+        ctx->mcscxmsg++;                /* increment last segment allocated */
+        
+        return(min);
+    }
+    else
+    {
+        desc = mcsdsc(ctx, min);
+        desc->mcsdsobj = objid;
+        mcswrt(ctx, desc, ptr, siz);
+        desc->mcsdsflg |= MCSDSFINUSE;
+
+        return(min);
+    }
+}
+
+void mcsin(mcscxdef *ctx, mcsseg seg, uchar *ptr, ushort siz)
+{
+    mcsdsdef *desc = mcsdsc(ctx, seg);
+
+    IF_DEBUG(printf("<< mcsin: seg=%u, ptr=%lx, siz=%d, objid=%u >>\n",
+                    seg, (unsigned long)ptr, siz, desc->mcsdsobj));
+
+    assert(seg < ctx->mcscxmsg);
+
+    /* can only swap in as much as we wrote */
+    if (desc->mcsdsosz < siz) siz = desc->mcsdsosz;
+
+    /* seek to and read the segment */
+    if (osfseek(ctx->mcscxfp, desc->mcsdsptr, OSFSK_SET))
+        errsig(ctx->mcscxerr, ERR_FSEEK);
+    if (osfrb(ctx->mcscxfp, ptr, (size_t)siz))
+        errsig(ctx->mcscxerr, ERR_FREAD);
+    
+    desc->mcsdsflg &= ~MCSDSFINUSE;             /* segment no longer in use */
+}
+
+void mcswrt(mcscxdef *ctx, mcsdsdef *desc, uchar *buf, ushort bufl)
+{
+    int tries;
+    
+    desc->mcsdsosz = bufl;
+    
+    for (tries = 0 ; tries < 2 ; ++tries)
+    {
+        /* attempt to write the object to the swap file */
+        if (osfseek(ctx->mcscxfp, desc->mcsdsptr, OSFSK_SET))
+            errsig(ctx->mcscxerr, ERR_FSEEK);
+        if (!osfwb(ctx->mcscxfp, buf, (size_t)bufl))
+            return;
+        
+        /* couldn't write it; compact the swap file */
+        mcscompact(ctx);
+    }
+    
+    /* couldn't write to swap file, even after compacting it */
+    errsig(ctx->mcscxerr, ERR_FWRITE);
+}
+
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp
index 2ba1161..4360f5f 100644
--- a/engines/glk/tads/tads2/object.cpp
+++ b/engines/glk/tads/tads2/object.cpp
@@ -21,15 +21,1056 @@
  */
 
 #include "glk/tads/tads2/object.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/os_glk.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
+/*
+ *   Get a property WITHOUT INHERITANCE.  The offset of the property's
+ *   prpdef is returned.  An offset of zero means the property wasn't
+ *   found.
+ */
+uint objgetp(mcmcxdef *mctx, objnum objn, prpnum prop, dattyp *typptr)
+{
+    objdef *objptr;
+    prpdef *p;
+    int     cnt;
+    uint    retval;                       /* property offset, if we find it */
+    uint    ignprop; /* ignored property - use if real property isn't found */
+    uchar   pbuf[2];                  /* property number in portable format */
+    uchar  *indp;
+    uchar  *indbase;
+    int     last;
+    int     first;
+    int     cur;
+    
+    oswp2(pbuf, prop);            /* get property number in portable foramt */
+    objptr = (objdef *)mcmlck(mctx, objn);      /* get a lock on the object */
+    ignprop = 0;                   /* assume we won't find ignored property */
+    cnt = objnprop(objptr);             /* get number of properties defined */
+    retval = 0;                                          /* presume failure */
+    
+    if (objflg(objptr) & OBJFINDEX)
+    {
+        /* there's an index -> do a binary search through the index */
+        indbase = (uchar *)objpfre(objptr);                   /* find index */
+        first = 0;
+        last = cnt - 1;
+        for (;;)
+        {
+            if (first > last) break;           /* crossed over -> not found */
+            cur = first + (last - first)/2;         /* split the difference */
+            indp = indbase + cur*4;            /* get pointer to this entry */
+            if (indp[0] == pbuf[0] && indp[1] == pbuf[1])
+            {
+                retval = osrp2(indp + 2);
+                break;
+            }
+            else if (indp[0] < pbuf[0]
+                     || (indp[0] == pbuf[0] && indp[1] < pbuf[1]))
+                first = (cur == first ? first + 1 : cur);
+            else
+                last = (cur == last ? last - 1 : cur);
+        }
+
+        /* ignore ignored and deleted properties if possible */
+        while (retval
+               && ((prpflg(objptr + retval) & PRPFIGN) != 0
+                   || ((prpflg(objptr + retval) & PRPFDEL) != 0
+                       && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0))
+               && cur < cnt && indp[0] == indp[4] && indp[1] == indp[5])
+        {
+            indp += 4;
+            retval = osrp2(indp + 2);
+        }
+        if (retval && osrp2(objptr + retval) != prop)
+            assert(FALSE);
+    }
+    else
+    {
+        /* there's no index -> do sequential search through properties */
+        for (p = objprp(objptr) ; cnt ; p = objpnxt(p), --cnt)
+        {
+            /* if this is the property, and it's not being ignored, use it */
+            if (*(uchar *)p == pbuf[0] && *(((uchar *)p) + 1) == pbuf[1])
+            {
+                if (prpflg(p) & PRPFIGN)                 /* this is ignored */
+                    ignprop = objpofs(objptr, p);  /* ... make a note of it */
+                else if ((prpflg(p) & PRPFDEL) != 0         /* it's deleted */
+                         && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0)
+                    /* simply skip it */ ;
+                else
+                {
+                    retval = objpofs(objptr, p);         /* this is the one */
+                    break;                                    /* we're done */
+                }
+            }
+        }
+    }
+
+    if (!retval) retval = ignprop;     /* use ignored value if nothing else */
+    if (retval && typptr) *typptr = prptype(objofsp(objptr, retval));
+    
+    mcmunlck(mctx, objn);                 /* done with object, so unlock it */
+    return(retval);
+}
+
+/* get the offset of the end of a property in an object */
+uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop)
+{
+    objdef *objp;
+    prpdef *propptr;
+    uint    ofs;
+    uint    valsiz;
+    
+    /* get the start of the object */
+    ofs = objgetp(ctx, objn, prop, 0);
+    if (ofs == 0)
+        return 0;
+
+    /* get the object */
+    objp = mcmlck(ctx, (mcmon)objn);
+
+    /* get the property */
+    propptr = objofsp(objp, ofs);
+
+    /* get the data size */
+    valsiz = prpsize(propptr);
+
+    /* done with the object */
+    mcmunlck(ctx, (mcmon)objn);
+
+    /* 
+     *   return the ending offset - it's the starting offset plus the
+     *   property header size plus the size of the property data 
+     */
+    return ofs + PRPHDRSIZ + valsiz;
+}
+
+/* determine whether an object is a descendant of another object */
+static int objisd(mcmcxdef *ctx, objdef *objptr, objnum parentnum)
+{
+    uchar *sc;
+    int    cnt;
+    
+    for (sc = objsc(objptr), cnt = objnsc(objptr) ; cnt ;
+         sc += 2, --cnt)
+    {
+        int     cursc = osrp2(sc);
+        int     ret;
+        objdef *curptr;
+        
+        if (cursc == parentnum) return(TRUE);
+        
+        curptr = (objdef *)mcmlck(ctx, (mcmon)cursc);
+        ret = objisd(ctx, curptr, parentnum);
+        mcmunlck(ctx, (mcmon)cursc);
+        if (ret) return(TRUE);
+    }
+    return(FALSE);
+}
+
+/*
+ *   Get a property of an object, either from the object or from a
+ *   superclass (inherited).  If the inh flag is TRUE, we do not look at
+ *   all in the object itself, but restrict our search to inherited
+ *   properties only.  We return the byte offset of the prpdef within the
+ *   object in which the prpdef is found; the superclass object itself is
+ *   NOT locked upon return, but we will NOT unlock the object passed in
+ *   (in other words, all object locking status is the same as it was on
+ *   entry).  If the offset is zero, the property was not found.
+ *   
+ *   This is an internal helper routine - it's not meant to be called
+ *   except by objgetap().  
+ */
+static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
+                      objnum *orn, int inh, dattyp *ortyp)
+{
+    uchar  *sc;
+    ushort  sccnt;
+    ushort  psav;
+    dattyp  typsav = DAT_NIL;
+    objnum  osavn = MCMONINV;
+    uchar  *o1;
+    objnum  o1n;
+    ushort  poff;
+    int     found;
+    uint    retval;
+    dattyp  typ;
+    uchar   sclist[100];                           /* up to 50 superclasses */
+    objdef *objptr;
+    
+    NOREG((&obj))
+    
+    /* see if the property is in the current object first */
+    if (!inh && (retval = objgetp(ctx, obj, prop, &typ)) != 0)
+    {
+        /* 
+         *   tell the caller which object this came from, if the caller
+         *   wants to know 
+         */
+        if (orn != 0)
+            *orn = obj;
+
+        /* if the caller wants to know the type, return it */
+        if (ortyp != 0)
+            *ortyp = typ;
+
+        /* return the property offset */
+        return retval;
+    }
+    
+    /* lock the object, cache its superclass list, and unlock it */
+    objptr = (objdef *)mcmlck(ctx, (mcmon)obj);
+    sccnt = objnsc(objptr);
+    memcpy(sclist, objsc(objptr), (size_t)(sccnt << 1));
+    sc = sclist;
+    mcmunlck(ctx, (mcmon)obj);
+    
+    /* try to inherit the property */
+    for (found = FALSE ; sccnt != 0 ; sc += 2, --sccnt)
+    {
+        /* recursively look up the property in this superclass */
+        poff = objgetap0(ctx, (objnum)osrp2(sc), prop, &o1n, FALSE, &typ);
+
+        /* if we found the property, remember it */
+        if (poff != 0)
+        {
+            int isdesc;
+            
+            /* if we have a previous object, determine lineage */
+            if (found)
+            {
+                o1 = mcmlck(ctx, o1n);
+                isdesc = objisd(ctx, o1, osavn);
+                mcmunlck(ctx, o1n);
+            }
+            
+            /*
+             *   if we don't already have a property, or the new object
+             *   is a descendant of the previously found object (meaning
+             *   that the new object's property should override the
+             *   previously found object's property), use this new
+             *   property 
+             */
+            if (!found || isdesc)
+            {
+                psav = poff;
+                osavn = o1n;
+                typsav = typ;
+                found = TRUE;
+            }
+        }
+    }
+
+    /* set return pointer and return the offset of what we found */
+    if (orn != 0)
+        *orn = osavn;
+
+    /* return the object type if the caller wanted it */
+    if (ortyp != 0)
+        *ortyp = typsav;
+
+    /* return the offset of the property if we found one, or zero if not */
+    return (found ? psav : 0);
+}
+
+/*
+ *   Get a property of an object, either from the object or from a
+ *   superclass (inherited).  If the inh flag is TRUE, we do not look at
+ *   all in the object itself, but restrict our search to inherited
+ *   properties only.  We return the byte offset of the prpdef within the
+ *   object in which the prpdef is found; the superclass object itself is
+ *   NOT locked upon return, but we will NOT unlock the object passed in
+ *   (in other words, all object locking status is the same as it was on
+ *   entry).  If the offset is zero, the property was not found.  
+ */
+uint objgetap(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
+              objnum *ornp, int inh)
+{
+    uint    retval;
+    dattyp  typ;
+    objnum  orn;
+
+    /* 
+     *   even if the caller doesn't care about the original object number,
+     *   we do, so provide our own location to store it if necessary 
+     */
+    if (ornp == 0)
+        ornp = &orn;
+
+    /* keep going until we've finished translating synonyms */
+    for (;;)
+    {
+        /* look up the property */
+        retval = objgetap0(ctx, obj, prop, ornp, inh, &typ);
+        
+        /* 
+         *   If we found something (i.e., retval != 0), check to see if we
+         *   have a synonym; if so, synonym translation is required 
+         */
+        if (retval != 0 && typ == DAT_SYN)
+        {
+            prpnum  prvprop;
+            objdef *objptr;
+            prpdef *p;
+
+            /* 
+             *   Translation is required - get new property and try again.
+             *   First, remember the original property, so we can make
+             *   sure we're not going to loop (at least, not in this one
+             *   synonym definition).  
+             */
+            prvprop = prop;
+
+            objptr = (objdef *)mcmlck(ctx, (mcmon)*ornp);
+            p = objofsp(objptr, retval);
+            prop = osrp2(prpvalp(p));
+            mcmunlck(ctx, (mcmon)*ornp);
+
+            /* check for direct circularity */
+            if (prop == prvprop)
+                errsig(ctx->mcmcxgl->mcmcxerr, ERR_CIRCSYN);
+
+            /* go back for another try with the new property */
+            continue;
+        }
+
+        /* we don't have to perform a translation; return the result */
+        return retval;
+    }
+}
+
+
+/*
+ *   Expand an object by a requested size, and return a pointer to the
+ *   object's location.  The object will be unlocked and relocked by this
+ *   call.  The new size is written to the *siz argument.
+ */
+objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz)
+{
+    ushort  oldsiz;
+    uchar  *p;
+    
+    oldsiz = mcmobjsiz(ctx, (mcmon)obj);
+    p = mcmrealo(ctx, (mcmon)obj, (ushort)(oldsiz + *siz));
+    *siz = mcmobjsiz(ctx, (mcmon)obj) - oldsiz;
+    return((objdef *)p);
+}
+
+/*
+ *   Delete a property in an object.  Note that we never actually remove
+ *   anything marked as an original property, but just mark it 'ignore'.
+ *   This way, it's easy to restore the entire original state of the
+ *   objects, simply by deleting everything not marked original and
+ *   clearing the 'ignore' flag on the remaining properties.  If
+ *   'mark_only' is true, we'll only mark the property as deleted without
+ *   actually reclaiming the space; this is necessary when deleting a
+ *   method when other methods may follow, since p-code is not entirely
+ *   self-relative and thus can't always be relocated within an object.  
+ */
+void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only)
+{
+    objdef *objptr;
+    uint    pofs;
+    prpdef *p;
+    prpdef *nxt;
+    size_t  movsiz;
+    
+    pofs = objgetp(mctx, objn, prop, (dattyp *)0);  /* try to find property */
+    if (!pofs) return;                   /* not defined - nothing to delete */
+    
+    objptr = (objdef *)mcmlck(mctx, objn);            /* get lock on object */
+    p = objofsp(objptr, pofs);                 /* get actual prpdef pointer */
+    nxt = objpnxt(p);                    /* find next prpdef after this one */
+    
+    /* if this is original, just mark 'ignore' */
+    if (prpflg(p) & PRPFORG)
+    {
+        prpflg(p) |= PRPFIGN;                    /* mark this as overridden */
+    }
+    else if (mark_only)
+    {
+        prpflg(p) |= PRPFDEL;     /* mark as deleted without removing space */
+    }
+    else
+    {
+        /* move prpdef's after current one down over current one */
+        movsiz = (uchar *)objptr + objfree(objptr) - (uchar *)nxt;
+        memmove(p, nxt, movsiz);
+
+        objsnp(objptr, objnprop(objptr)-1);
+        objsfree(objptr, objfree(objptr) - (((uchar *)nxt) - ((uchar *)p)));
+    }
+    
+    /* tell cache manager this object has been changed, and unlock it */
+    mcmtch(mctx, objn);
+    mcmunlck(mctx, objn);
+}
+
+/*
+ *   Set a property of an object to a new value, overwriting the original
+ *   value (if any); the object must be unlocked coming in.  If an undo
+ *   context is provided, an undo record is written; if the undo context
+ *   pointer is null, no undo information is kept.  
+ */
+void objsetp(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ,
+             void *val, objucxdef *undoctx)
+{
+    objdef *objptr;
+    prpdef *p;
+    uint    pofs;
+    uint    siz;
+    ushort  newsiz;
+    int     indexed;
+    int     prop_was_set;
+    
+    /* get a lock on the object */
+    objptr = (objdef *)mcmlck(ctx, objn);
+    indexed = objflg(objptr) & OBJFINDEX;
+
+    /* catch any errors so we can unlock the object */
+    ERRBEGIN(ctx->mcmcxgl->mcmcxerr)
+    {
+        /* get the previous value of the property, if any */
+        pofs = objgetp(ctx, objn, prop, (dattyp *)0);
+        p = objofsp(objptr, pofs);
+        prop_was_set = (p != 0);
+
+        /* start the undo record if we are keeping undo information */
+        if (undoctx && objuok(undoctx))
+        {
+            uchar  *up;
+            uchar   cmd;
+
+            if (p)
+            {
+                if (prpflg(p) & PRPFORG)
+                {
+                    cmd = OBJUOVR;                     /* override original */
+                    p = (prpdef *)0;       /* pretend it doesn't even exist */
+                }
+                else cmd = OBJUCHG;                      /* change property */
+            }
+            else cmd = OBJUADD;            /* prop didn't exist - adding it */
+
+            /* write header, reserve space, and get a pointer to the space */
+            up = objures(undoctx, cmd,
+                         (ushort)(sizeof(mcmon) + sizeof(prpnum)
+                                  + (p ? PRPHDRSIZ + prpsize(p) : 0)));
+
+            /* write the object and property numbers */
+            memcpy(up, &objn, (size_t)sizeof(objn));
+            up += sizeof(mcmon);
+            memcpy(up, &prop, (size_t)sizeof(prop));
+            up += sizeof(prop);
+
+            /* if there's existing data, write it */
+            if (p)
+            {
+                memcpy(up, p, (size_t)(PRPHDRSIZ + prpsize(p)));
+                up += PRPHDRSIZ + prpsize(p);
+            }
+
+            /* update the undo context's head offset for the new value */
+            undoctx->objucxhead = up - undoctx->objucxbuf;
+        }
+
+        /* get the size of the data */
+        siz = datsiz(typ, val);
+
+        /*
+         *   If the property is already set, and the new data fits, use the
+         *   existing slot.  However, do not use existing slot if it's
+         *   in the non-mutable portion of the object.
+         */
+        if (!p || (uint)prpsize(p) < siz || pofs < (uint)objrst(objptr))
+        {
+            uint   avail;
+
+            /* delete any existing value */
+            if (prop_was_set)
+                objdelp(ctx, objn, prop, FALSE);
+
+            /* get the top of the property area */
+            p = objpfre(objptr);        
 
+            /* make sure there's room at the top */
+            avail = mcmobjsiz(ctx, (mcmon)objn) - objfree(objptr);
+            if (avail < siz + PRPHDRSIZ)
+            {
+                newsiz = 64 + ((objfree(objptr) + siz + PRPHDRSIZ) -
+                               mcmobjsiz(ctx, (mcmon)objn));
+                objptr = objexp(ctx, objn, &newsiz);
+                p = objpfre(objptr);       /* reset pointer if object moved */
+                /* NOTE! Index (if present) is now invalid! */
+            }
+
+            prpsetsize(p, siz);                /* set the new property size */
+            prpsetprop(p, prop);                     /* ... and property id */
+            prpflg(p) = 0;                         /* no property flags yet */
+            objsnp(objptr, objnprop(objptr) + 1);          /* one more prop */
+            objsfree(objptr, objfree(objptr) + siz + PRPHDRSIZ);
+        }
+
+        /* copy the new data to top of object's free space */
+        prptype(p) = typ;
+        if (siz != 0) memcpy(prpvalp(p), val, (size_t)siz);
+    }
+    ERRCLEAN(ctx->mcmcxgl->mcmcxerr)
+    {
+        mcmunlck(ctx, objn);                           /* unlock the object */
+    }
+    ERRENDCLN(ctx->mcmcxgl->mcmcxerr)
+        
+    /* dirty the object, and release lock on object before return */
+    mcmtch(ctx, objn);                        /* mark the object as changed */
+    mcmunlck(ctx, objn);                                       /* unlock it */
+
+    /* if necessary, rebuild the property index */
+    if (indexed) objindx(ctx, objn);
+}
+
+/* set an undo savepoint */
+void objusav(objucxdef *undoctx)
+{
+    /* the only thing in this record is the OBJUSAV header */
+    objures(undoctx, OBJUSAV, (ushort)0);
+}
+
+/* reserve space in an undo buffer, and write header */
+uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz)
+{
+    ushort  prv;
+    uchar  *p;
+    
+    /* adjust size to include header information */
+    siz += 1 + sizeof(ushort);
+    
+    /* make sure there's enough room overall for the record */
+    if (siz > undoctx->objucxsiz) errsig(undoctx->objucxerr, ERR_UNDOVF);
+    
+    /* if there's no information, reset buffers */
+    if (undoctx->objucxhead == undoctx->objucxprv)
+    {
+        undoctx->objucxhead = undoctx->objucxprv = undoctx->objucxtail = 0;
+        undoctx->objucxtop = 0;
+        goto done;
+    }
+    
+    /* if tail is below head, we can use to top of entire buffer */
+    if (undoctx->objucxtail < undoctx->objucxhead)
+    {
+        /* if there's enough space left after head, we're done */
+        if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
+            goto done;
+        
+        /* insufficient space:  wrap head down to bottom of buffer */
+        undoctx->objucxtop = undoctx->objucxprv;            /* last was top */
+        undoctx->objucxhead = 0;
+    }
+    
+    /* head is below tail:  delete records until we have enough room */
+    while (undoctx->objucxtail - undoctx->objucxhead < siz)
+    {
+        objutadv(undoctx);
+        
+        /* if the tail wrapped, advancing won't do any more good */
+        if (undoctx->objucxtail <= undoctx->objucxhead)
+        {
+            /* if there's enough room at the top, we're done */
+            if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
+                goto done;
+            
+            /* still not enough room; wrap the head this time */
+            undoctx->objucxtop = undoctx->objucxprv;        /* last was top */
+            undoctx->objucxhead = 0;
+        }
+    }
+    
+done:
+    /* save back-link, and set objucxprv pointer to the new record */
+    prv = undoctx->objucxprv;
+    undoctx->objucxprv = undoctx->objucxhead;
+    
+    /* write the header:  command byte, back-link to previous record */
+    p = &undoctx->objucxbuf[undoctx->objucxhead];
+    *p++ = cmd;
+    memcpy(p, &prv, sizeof(prv));
+    
+    /* advance the head pointer past the header */
+    undoctx->objucxhead += 1 + sizeof(prv);
+    
+    /* set the high-water mark if we've exceeded the old one */
+    if (undoctx->objucxprv > undoctx->objucxtop)
+        undoctx->objucxtop = undoctx->objucxprv;
+
+    /* return the reserved space */
+    return &undoctx->objucxbuf[undoctx->objucxhead];
+}
+
+/* advance the undo tail pointer over the record it points to */
+void objutadv(objucxdef *undoctx)
+{
+    uchar  *p;
+    ushort  siz;
+    uchar   pr[PRPHDRSIZ];                   /* space for a property header */
+    uchar   cmd;
+    
+    /* if we're at the most recently written record, flush buffer */
+    if (undoctx->objucxtail == undoctx->objucxprv)
+    {
+        undoctx->objucxtail = 0;
+        undoctx->objucxprv = 0;
+        undoctx->objucxhead = 0;
+        undoctx->objucxtop = 0;
+    }
+
+    /* if we've reached high water mark, wrap back to bottom */
+    if (undoctx->objucxtail == undoctx->objucxtop)
+    {
+        undoctx->objucxtail = 0;
+        return;
+    }
+    
+    /* determine size by inspecting current record */
+    p = undoctx->objucxbuf + undoctx->objucxtail;
+    siz = 1 + sizeof(ushort);                          /* basic header size */
+    
+    cmd = *p++;
+    p += sizeof(ushort);                       /* skip the previous pointer */
+    
+    switch(cmd)
+    {
+    case OBJUCHG:
+        /* change:  property header (added below) plus data value */
+        memcpy(pr, p + sizeof(mcmon) + sizeof(prpnum), (size_t)PRPHDRSIZ);
+        siz += PRPHDRSIZ + prpsize(pr);
+        /* FALLTHROUGH */
+
+    case OBJUADD:
+    case OBJUOVR:
+        /* add/override:  property header only */
+        siz += sizeof(mcmon) + sizeof(prpnum);
+        break;
+        
+    case OBJUCLI:
+        siz += (*undoctx->objucxcsz)(undoctx->objucxccx, p);
+        break;
+
+    case OBJUSAV:
+        break;
+    }
+    
+    undoctx->objucxtail += siz;
+}
+
+/* undo one undo record, and remove it from the undo list */
+void obj1undo(mcmcxdef *mctx, objucxdef *undoctx)
+{
+    uchar  *p;
+	prpnum  prop = 0;
+	objnum  objn = 0;
+    uchar   cmd;
+    uchar   pr[PRPHDRSIZ];                     /* space for property header */
+    ushort  prv;
+    ushort  pofs;
+    objdef *objptr;
+	int     indexed = 0;
+
+    /* if there's no more undo, signal an error */
+    if (undoctx->objucxprv == undoctx->objucxhead)
+        errsig(undoctx->objucxerr, ERR_NOUNDO);
+    
+    /* move back to previous record */
+    undoctx->objucxhead = undoctx->objucxprv;
+    p = &undoctx->objucxbuf[undoctx->objucxprv];
+    
+    /* get command, and set undocxprv to previous record */
+    cmd = *p++;
+    memcpy(&prv, p, sizeof(prv));
+    p += sizeof(prv);
+    
+    /* if we're at the tail, no more undo; otherwise, use back link */
+    if (undoctx->objucxprv == undoctx->objucxtail)
+        undoctx->objucxprv = undoctx->objucxhead;
+    else
+        undoctx->objucxprv = prv;
+    
+    if (cmd == OBJUSAV) return;       /* savepointer marker - nothing to do */
+    
+    /* get object/property information for property-changing undo */
+    if (cmd != OBJUCLI)
+    {
+        memcpy(&objn, p, (size_t)sizeof(objn));
+        p += sizeof(objn);
+        memcpy(&prop, p, (size_t)sizeof(prop));
+        p += sizeof(prop);
+        objptr = mcmlck(mctx, objn);
+        indexed = (objflg(objptr) & OBJFINDEX);
+        mcmunlck(mctx, objn);
+    }
+    
+    switch(cmd)
+    {
+    case OBJUADD:
+        objdelp(mctx, objn, prop, FALSE);
+        if (indexed) objindx(mctx, objn);
+        break;
+        
+    case OBJUOVR:
+        objdelp(mctx, objn, prop, FALSE);  /* delete the non-original value */
+        pofs = objgetp(mctx, objn, prop, (dattyp *)0);  /* get ignored prop */
+        objptr = (objdef *)mcmlck(mctx, objn);           /* lock the object */
+        prpflg(objofsp(objptr, pofs)) &= ~PRPFIGN;     /* no longer ignored */
+        mcmunlck(mctx, objn);                          /* unlock the object */
+        break;
+        
+    case OBJUCHG:
+        memcpy(pr, p, (size_t)PRPHDRSIZ);
+        p += PRPHDRSIZ;
+        objsetp(mctx, objn, prop, prptype(pr), (void *)p, (objucxdef *)0);
+        break;
+        
+    case OBJUCLI:
+        (*undoctx->objucxcun)(undoctx->objucxccx, p);
+        break;
+    }
+}
+
+/*
+ *   Determine if it's ok to add undo records - returns TRUE if a
+ *   savepoint has been stored in the undo log, FALSE if not. 
+ */
+int objuok(objucxdef *undoctx)
+{
+    ushort prv;
+
+    /* see if there's any more undo information */
+    if (undoctx->objucxprv == undoctx->objucxhead)
+        return(FALSE);
+
+    /* look for most recent savepoint marker */
+    for (prv = undoctx->objucxprv ;; )
+    {
+        if (undoctx->objucxbuf[prv] == OBJUSAV)
+            return(TRUE);               /* found a savepoint - can add undo */
+
+        /* if we've reached the tail, there are no more undo records */
+        if (prv == undoctx->objucxtail)
+            return(FALSE);                /* no savepoints - can't add undo */
+
+        /* get previous record */
+        memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
+    }
+}
+
+/*
+ *   Undo back to the most recent savepoint.  If there is no savepoint in
+ *   the undo list, NOTHING will be undone.  This prevents reaching an
+ *   inconsistent state in which some, but not all, of the operations
+ *   between two savepoints are undone: either all operations between two
+ *   savepoints will be undone, or none will. 
+ */
+void objundo(mcmcxdef *mctx, objucxdef *undoctx)
+{
+    ushort prv;
+    ushort sav;
+
+    /* see if there's any more undo information */
+    if (undoctx->objucxprv == undoctx->objucxhead)
+        errsig(undoctx->objucxerr, ERR_NOUNDO);
+
+    /* look for most recent savepoint marker */
+    for (prv = undoctx->objucxprv ;; )
+    {
+        if (undoctx->objucxbuf[prv] == OBJUSAV)
+        {
+            sav = prv;
+            break;
+        }
+        
+        /* if we've reached the tail, there are no more undo records */
+        if (prv == undoctx->objucxtail)
+            errsig(undoctx->objucxerr, ERR_ICUNDO);
+
+        /* get previous record */
+        memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
+    }
+    
+    /* now undo everything until we get to the savepoint */
+    do { obj1undo(mctx, undoctx); } while (undoctx->objucxhead != sav);
+}
+
+/* initialize undo context */
+objucxdef *objuini(mcmcxdef *ctx, ushort siz,
+                   void (*undocb)(void *, uchar *), 
+                   ushort (*sizecb)(void *, uchar *),
+                   void *callctx)
+{
+    objucxdef *ret;
+    long       totsiz;
+
+    /* force size into valid range */
+    totsiz = (long)siz + sizeof(objucxdef) - 1;
+    if (totsiz > 0xff00)
+        siz = 0xff00 - sizeof(objucxdef) + 1;
+
+    ret = (objucxdef *)mchalo(ctx->mcmcxgl->mcmcxerr,
+                              (sizeof(objucxdef) + siz - 1),
+                              "objuini");
+    
+    ret->objucxmem  = ctx;
+    ret->objucxerr  = ctx->mcmcxgl->mcmcxerr;
+    ret->objucxsiz  = siz;
+    ret->objucxhead = ret->objucxprv = ret->objucxtail = ret->objucxtop = 0;
+    
+    /* set client callback functions */
+    ret->objucxcun = undocb;               /* callback to apply client undo */
+    ret->objucxcsz = sizecb;         /* callback to get size of client undo */
+    ret->objucxccx = callctx;      /* context for client callback functions */
+    
+    return(ret);
+}
+
+/* discard all undo records */
+void objulose(objucxdef *ctx)
+{
+    if (ctx)
+        ctx->objucxhead =
+        ctx->objucxprv  =
+        ctx->objucxtail =
+        ctx->objucxtop  = 0;
+}
+
+/* uninitialize the undo context - release allocated memory */
+void objuterm(objucxdef *uctx)
+{
+    /* free the undo memory block */
+    mchfre(uctx);
+}
+
+/* revert object to original (post-compilation) values */
+void objrevert(void *ctx0, mcmon objn)
+{
+    mcmcxdef *mctx = (mcmcxdef *)ctx0;
+    uchar    *p;
+    prpdef   *pr;
+    int       cnt;
+    int       indexed;
+    
+    p = mcmlck(mctx, objn);
+    pr = objprp(p);
+    indexed = objflg(p) & OBJFINDEX;
+    
+    /* restore original settings */
+    objsfree(p, objrst(p));
+    objsnp(p, objstat(p));
+
+    /* go through original properties and remove 'ignore' flag if set */
+    for (cnt = objnprop(p) ; cnt ; pr = objpnxt(pr), --cnt)
+        prpflg(pr) &= ~PRPFIGN;
+    
+    /* touch object and unlock it */
+    mcmtch(mctx, objn);
+    mcmunlck(mctx, objn);
+    
+    /* if it's indexed, rebuild the index */
+    if (indexed) objindx(mctx, objn);
+}
+
+/* set 'ignore' flag for original properties set in mutable part */
+void objsetign(mcmcxdef *mctx, objnum objn)
+{
+    objdef *objptr;
+    prpdef *mut;
+    prpdef *p;
+    int     statcnt;
+    int     cnt;
+    int     indexed;
+    prpdef *p1;
+    
+    objptr = (objdef *)mcmlck(mctx, (mcmon)objn);
+    p1 = objprp(objptr);
+    indexed = objflg(objptr) & OBJFINDEX;
+
+    /* go through mutables, and set ignore on duplicates in non-mutables */
+    for (mut = (prpdef *)(objptr + objrst(objptr)),
+         cnt = objnprop(objptr) - objstat(objptr) ; cnt ;
+         mut = objpnxt(mut), --cnt)
+    {
+        for (p = p1, statcnt = objstat(objptr) ; statcnt ;
+             p = objpnxt(p), --statcnt)
+        {
+            /* if this static prop matches a mutable prop, ignore it */
+            if (prpprop(p) == prpprop(mut))
+            {
+                prpflg(p) |= PRPFIGN;
+                break;
+            }
+        }
+    }
+    
+    mcmtch(mctx, (mcmon)objn);
+    mcmunlck(mctx, (mcmon)objn);
+    if (indexed) objindx(mctx, objn);
+}
+
+/*
+ *   Build or rebuild a property index for an object.  
+ */
+void objindx(mcmcxdef *mctx, objnum objn)
+{
+    uint    newsiz;
+    uint    avail;
+    objdef *objptr;
+    uint    cnt;
+    prpdef *p;
+	uchar  *indp = nullptr;
+    uchar  *indbase;
+    uint    icnt;
+    uint    first;
+    uint    last;
+	uint    cur = 0;
+    
+    objptr = (objdef *)mcmlck(mctx, objn);            /* get object pointer */
+    cnt = objnprop(objptr);                     /* get number of properties */
+    p = objprp(objptr);         /* get pointer to properties (or old index) */
+    newsiz = 2 + 4*cnt;                 /* figure size needed for the index */
+    
+    avail = mcmobjsiz(mctx, objn) - objfree(objptr);
+    
+    /* insert space for the index; expand the object if necessary */
+    if (avail < newsiz)
+    {
+        ushort  need;
+        
+        newsiz += 10*4;                   /* add some extra space for later */
+        need = newsiz - avail;            /* compute amount of space needed */
+        objptr = objexp(mctx, objn, &need);
+        p = objprp(objptr);
+    }
+    
+    /* now build the index */
+    indbase = objpfre(objptr);
+    for (icnt = 0 ; cnt ; p = objpnxt(p), --cnt, ++icnt)
+    {
+        uint ofs = (uchar *)p - (uchar *)objptr;
+        
+        if (icnt)
+        {
+            /* figure out where to insert this property */
+            first = 0;
+            last = icnt - 1;
+            for (;;)
+            {
+                if (first > last) break;
+                cur = first + (last - first)/2;
+                indp = indbase + cur*4;
+                if (indp[0] == p[0] && indp[1] == p[1])
+                    break;
+                else if (indp[0] < p[0]
+                         || (indp[0] == p[0] && indp[1] < p[1]))
+                    first = (cur == first ? first + 1 : cur);
+                else
+                    last = (cur == last ? last - 1 : cur);
+            }
+            
+            /* make sure we're positioned just before insertion point */
+            while (cur < icnt
+                   && (indp[0] <= p[0]
+                       || (indp[0] == p[0] && indp[1] <= p[1])))
+            {
+                indp += 4;
+                ++cur;
+            }
+                
+            /* move elements above if any */
+            if (cur < icnt)
+                memmove(indp + 4, indp, (size_t)((icnt - cur) * 4));
+        }
+        else
+            indp = indbase;
+        
+        /* insert property into index */
+        indp[0] = p[0];
+        indp[1] = p[1];
+        oswp2(indp+2, ofs);
+    }
+    
+    /* set the index flag, and dirty and free the object */
+    objsflg(objptr, objflg(objptr) | OBJFINDEX);
+    mcmtch(mctx, (mcmon)objn);
+    mcmunlck(mctx, (mcmon)objn);
+}
+
+/* allocate and initialize an object */
+objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace,
+               objnum *objnptr, int classflg)
+{
+    objdef *o;
+    mcmon   objn;
+
+    /* allocate cache object */
+    o = (objdef *)mcmalo(mctx, (ushort)(OBJDEFSIZ + sccnt * 2 + propspace),
+                         &objn);
+    
+    /* set up object descriptor for the new object */
+    objini(mctx, sccnt, (objnum)objn, classflg);
+
+    *objnptr = (objnum)objn;
+    return(o);
+}
+
+/* initialize an already allocated object */
+void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg)
+{
+    objdef *o;
+    uint    flags = 0;
+
+    /* get a lock on the object */
+    o = (objdef *)mcmlck(mctx, objn);
+    
+    memset(o, 0, (size_t)10);
+    objsnsc(o, sccnt);
+    objsfree(o, ((uchar *)objsc(o) + 2*sccnt) - (uchar *)o);
+    
+    /* set up flags */
+    if (classflg) flags |= OBJFCLASS;
+    objsflg(o, flags);
+
+    /* tell cache manager that this object has been modified */
+    mcmtch(mctx, objn);
+    mcmunlck(mctx, objn);
+}
+
+/*
+ *   Get the first superclass of an object.  If it doesn't have any
+ *   superclasses, return invalid.
+ */
+objnum objget1sc(mcmcxdef *ctx, objnum objn)
+{
+    objdef *p;
+    objnum  retval;
+
+    /* lock the object */
+    p = mcmlck(ctx, (mcmon)objn);
+
+    /* get the first superclass if it has any */
+    if (objnsc(p) == 0)
+        retval = MCMONINV;
+    else
+        retval = osrp2(objsc(p));
+
+    /* unlock the object and return the superclass value */
+    mcmunlck(ctx, (mcmon)objn);
+    return retval;
+}
 
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
-
-#endif
diff --git a/engines/glk/tads/tads2/opcode.h b/engines/glk/tads/tads2/opcode.h
new file mode 100644
index 0000000..4175db3
--- /dev/null
+++ b/engines/glk/tads/tads2/opcode.h
@@ -0,0 +1,218 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_OPCODE
+#define GLK_TADS_TADS2_OPCODE
+
+/*
+ * Opcode definitions
+ *
+ * Lifted largely from the old TADS, since the basic run - time interpreter's
+ * operation is essentially the same.
+ */
+
+#include "glk/tads/tads.h"
+#include "glk/tads/tads2/data.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+#define OPCPUSHNUM  1       /* push a constant numeric value */
+#define OPCPUSHOBJ  2       /* push an object */
+#define OPCNEG      3       /* unary negation */
+#define OPCNOT      4       /* logical negation */
+#define OPCADD      5       /* addition/list concatenation */
+#define OPCSUB      6       /* subtraction/list difference */
+#define OPCMUL      7       /* multiplication */
+#define OPCDIV      8       /* division */
+#define OPCAND      9       /* logical AND */
+#define OPCOR       10      /* logical OR */
+#define OPCEQ       11      /* equality */
+#define OPCNE       12      /* inequality */
+#define OPCGT       13      /* greater than */
+#define OPCGE       14      /* greater or equal */
+#define OPCLT       15      /* less than */
+#define OPCLE       16      /* less or equal */
+#define OPCCALL     17      /* call a function */
+#define OPCGETP     18      /* get property */
+#define OPCGETPDATA 19      /* get a property, allowing only data values */
+#define OPCGETLCL   20      /* get a local variable's value */
+#define OPCPTRGETPDATA 21   /* get property via pointer; only allow data */   
+#define OPCRETURN   22      /* return without a value */
+#define OPCRETVAL   23      /* return a value */
+#define OPCENTER    24      /* enter a function */
+#define OPCDISCARD  25      /* discard top of stack */
+#define OPCJMP      26      /* unconditional jump */
+#define OPCJF       27      /* jump if false */
+#define OPCPUSHSELF 28      /* push current object */
+#define OPCSAY      29      /* implicit printout for doublequote strings */
+#define OPCBUILTIN  30      /* call a built-in function */
+#define OPCPUSHSTR  31      /* push a string */
+#define OPCPUSHLST  32      /* push a list */
+#define OPCPUSHNIL  33      /* push the NIL value */
+#define OPCPUSHTRUE 34      /* push the TRUE value */
+#define OPCPUSHFN   35      /* push the address of a function */
+#define OPCGETPSELFDATA 36  /* push property of self; only allow data */
+
+#define OPCPTRCALL  38          /* call function pointed to by top of stack */
+#define OPCPTRINH   39          /* inherit pointer to property (stack=prop) */
+#define OPCPTRGETP  40          /* get property by pointer (stack=obj,prop) */
+
+#define OPCPASS     41      /* pass to inherited handler */
+#define OPCEXIT     42      /* exit turn, but continue with fuses/daemons */
+#define OPCABORT    43      /* abort turn, skipping fuses/daemons */
+#define OPCASKDO    44      /* ask for a direct object */
+#define OPCASKIO    45      /* ask for indirect object and set preposition */
+
+/* explicit superclass inheritance opcodes */
+#define OPCEXPINH   46      /* "inherited <superclass>.<property>" */
+#define OPCEXPINHPTR 47     /* "inherited <superclass>.<prop-pointer>" */
+
+/*
+ *   Special opcodes for peephole optimization.  These are essentially
+ *   pairs of operations that occur frequently so have been collapsed into
+ *   a single instruction.
+ */
+#define OPCCALLD    48      /* call function and discard value */
+#define OPCGETPD    49      /* evaluate property and discard any value */
+#define OPCBUILTIND 50      /* call built-in function and discard value */
+
+#define OPCJE       51      /* jump if equal */
+#define OPCJNE      52      /* jump if not equal */
+#define OPCJGT      53      /* jump if greater than */
+#define OPCJGE      54      /* jump if greater or equal */
+#define OPCJLT      55      /* jump if less than */
+#define OPCJLE      56      /* jump if less or equal */
+#define OPCJNAND    57      /* jump if not AND */
+#define OPCJNOR     58      /* jump if not OR */
+#define OPCJT       59      /* jump if true */
+
+#define OPCGETPSELF 60      /* get property of the 'self' object */
+#define OPCGETPSLFD 61      /* get property of 'self' and discard result */
+#define OPCGETPOBJ  62      /* get property of a given object */
+                            /*  note: differs from GETP in that object is */
+                            /*  encoded into the instruction */
+#define OPCGETPOBJD 63      /* get property of an object and discard result */
+#define OPCINDEX    64      /* get an indexed entry from a list */
+
+#define OPCPUSHPN   67      /* push a property number */
+
+#define OPCJST      68      /* jump and save top-of-stack if true */
+#define OPCJSF      69      /* jump and save top-of-stack if false */
+#define OPCJMPD     70      /* discard stack and then jump unconditionally */
+
+#define OPCINHERIT  71      /* inherit a property from superclass */
+#define OPCCALLEXT  72      /* call external function */
+#define OPCDBGRET   73      /* return to debugger (no stack frame leaving) */
+
+#define OPCCONS     74      /* construct list from top two stack elements */
+#define OPCSWITCH   75      /* switch statement */
+
+#define OPCARGC     76      /* get argument count */
+#define OPCCHKARGC  77      /* check actual arguments against formal count */
+
+#define OPCLINE     78      /* line record */
+#define OPCFRAME    79      /* local variable frame record */
+#define OPCBP       80      /* breakpoint - replaces an OPCLINE instruction */
+#define OPCGETDBLCL 81                                /* get debugger local */
+#define OPCGETPPTRSELF 82                 /* get property pointer from self */
+#define OPCMOD      83                                            /* modulo */
+#define OPCBAND     84                                        /* binary AND */
+#define OPCBOR      85                                         /* binary OR */
+#define OPCXOR      86                                        /* binary XOR */
+#define OPCBNOT     87                                   /* binary negation */
+#define OPCSHL      88                                    /* bit shift left */
+#define OPCSHR      89                                   /* bit shift right */
+
+#define OPCNEW      90                                 /* create new object */
+#define OPCDELETE   91                                     /* delete object */
+
+
+/* ----- opcodes 192 and above are reserved for assignment operations ----- */
+
+/*
+ASSIGNMENT OPERATIONS
+    When (opcode & 0xc0 == 0xc0), we have an assignment operation.
+    (Note that this means that opcodes from 0xc0 up are all reserved
+    for assignment operations.)  The low six bits of the opcode
+    specify exactly what kind of operation is to be performed:
+    
+    bits 0-1:  specifies destination type:
+               00    2-byte operand is local number
+               01    2-byte operand is property to set in obj at tos
+               10    tos is index, [sp-1] is list to be indexed and set
+               11    tos is property pointer, [sp-1] is object
+    
+    bits 2-4:  specifies assignment operation:
+               000   := (direct assignment)
+               001   += (add tos to destination)
+               010   -= (subtract tos from destination)
+               011   *= (multiply destination by tos)
+               100   /= (divide destination by tos)
+               101   ++ (increment tos)
+               110   -- (decrement tos)
+               111   *reserved*
+               
+    bit 5:     specifies what to do with value computed by assignment
+               0     leave on stack (implies pre increment/decrement)
+               1     discard (implies post increment/decrement)
+*/
+#define OPCASI_MASK      0xc0                     /* assignment instruction */
+
+#define OPCASIDEST_MASK  0x03              /* mask to get destination field */
+#define OPCASILCL        0x00                          /* assign to a local */
+#define OPCASIPRP        0x01               /* assign to an object.property */
+#define OPCASIIND        0x02             /* assign to an element of a list */
+#define OPCASIPRPPTR     0x03                /* assign property via pointer */
+
+#define OPCASITYP_MASK   0x1c          /* mask to get assignment type field */
+#define OPCASIDIR        0x00                          /* direct assignment */
+#define OPCASIADD        0x04                             /* assign and add */
+#define OPCASISUB        0x08                        /* assign and subtract */
+#define OPCASIMUL        0x0c                        /* assign and multiply */
+#define OPCASIDIV        0x10                          /* assign and divide */
+#define OPCASIINC        0x14                                  /* increment */
+#define OPCASIDEC        0x18                                  /* decrement */
+#define OPCASIEXT        0x1c                     /* other - extension flag */
+
+/* extended assignment flags - next byte when OPCASIEXT is used */
+#define OPCASIMOD        1                             /* modulo and assign */
+#define OPCASIBAND       2                         /* binary AND and assign */
+#define OPCASIBOR        3                          /* binary OR and assign */
+#define OPCASIXOR        4                         /* binary XOR and assign */
+#define OPCASISHL        5                         /* shift left and assign */
+#define OPCASISHR        6                        /* shift right and assign */
+
+
+#define OPCASIPRE_MASK   0x20                    /* mask for pre/post field */
+#define OPCASIPOST       0x00                       /* increment after push */
+#define OPCASIPRE        0x20                      /* increment before push */
+
+/* some composite opcodes for convenience */
+#define OPCSETLCL (OPCASI_MASK | OPCASILCL | OPCASIDIR)
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/opcode_defs.h b/engines/glk/tads/tads2/opcode_defs.h
deleted file mode 100644
index 32519ae..0000000
--- a/engines/glk/tads/tads2/opcode_defs.h
+++ /dev/null
@@ -1,218 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#ifndef GLK_TADS_TADS2_OPCODE_DEFS
-#define GLK_TADS_TADS2_OPCODE_DEFS
-
-/*
- * Opcode definitions
- *
- * Lifted largely from the old TADS, since the basic run - time interpreter's
- * operation is essentially the same.
- */
-
-#include "glk/tads/tads.h"
-#include "glk/tads/tads2/data.h"
-
-namespace Glk {
-namespace TADS {
-namespace TADS2 {
-
-#define OPCPUSHNUM  1       /* push a constant numeric value */
-#define OPCPUSHOBJ  2       /* push an object */
-#define OPCNEG      3       /* unary negation */
-#define OPCNOT      4       /* logical negation */
-#define OPCADD      5       /* addition/list concatenation */
-#define OPCSUB      6       /* subtraction/list difference */
-#define OPCMUL      7       /* multiplication */
-#define OPCDIV      8       /* division */
-#define OPCAND      9       /* logical AND */
-#define OPCOR       10      /* logical OR */
-#define OPCEQ       11      /* equality */
-#define OPCNE       12      /* inequality */
-#define OPCGT       13      /* greater than */
-#define OPCGE       14      /* greater or equal */
-#define OPCLT       15      /* less than */
-#define OPCLE       16      /* less or equal */
-#define OPCCALL     17      /* call a function */
-#define OPCGETP     18      /* get property */
-#define OPCGETPDATA 19      /* get a property, allowing only data values */
-#define OPCGETLCL   20      /* get a local variable's value */
-#define OPCPTRGETPDATA 21   /* get property via pointer; only allow data */   
-#define OPCRETURN   22      /* return without a value */
-#define OPCRETVAL   23      /* return a value */
-#define OPCENTER    24      /* enter a function */
-#define OPCDISCARD  25      /* discard top of stack */
-#define OPCJMP      26      /* unconditional jump */
-#define OPCJF       27      /* jump if false */
-#define OPCPUSHSELF 28      /* push current object */
-#define OPCSAY      29      /* implicit printout for doublequote strings */
-#define OPCBUILTIN  30      /* call a built-in function */
-#define OPCPUSHSTR  31      /* push a string */
-#define OPCPUSHLST  32      /* push a list */
-#define OPCPUSHNIL  33      /* push the NIL value */
-#define OPCPUSHTRUE 34      /* push the TRUE value */
-#define OPCPUSHFN   35      /* push the address of a function */
-#define OPCGETPSELFDATA 36  /* push property of self; only allow data */
-
-#define OPCPTRCALL  38          /* call function pointed to by top of stack */
-#define OPCPTRINH   39          /* inherit pointer to property (stack=prop) */
-#define OPCPTRGETP  40          /* get property by pointer (stack=obj,prop) */
-
-#define OPCPASS     41      /* pass to inherited handler */
-#define OPCEXIT     42      /* exit turn, but continue with fuses/daemons */
-#define OPCABORT    43      /* abort turn, skipping fuses/daemons */
-#define OPCASKDO    44      /* ask for a direct object */
-#define OPCASKIO    45      /* ask for indirect object and set preposition */
-
-/* explicit superclass inheritance opcodes */
-#define OPCEXPINH   46      /* "inherited <superclass>.<property>" */
-#define OPCEXPINHPTR 47     /* "inherited <superclass>.<prop-pointer>" */
-
-/*
- *   Special opcodes for peephole optimization.  These are essentially
- *   pairs of operations that occur frequently so have been collapsed into
- *   a single instruction.
- */
-#define OPCCALLD    48      /* call function and discard value */
-#define OPCGETPD    49      /* evaluate property and discard any value */
-#define OPCBUILTIND 50      /* call built-in function and discard value */
-
-#define OPCJE       51      /* jump if equal */
-#define OPCJNE      52      /* jump if not equal */
-#define OPCJGT      53      /* jump if greater than */
-#define OPCJGE      54      /* jump if greater or equal */
-#define OPCJLT      55      /* jump if less than */
-#define OPCJLE      56      /* jump if less or equal */
-#define OPCJNAND    57      /* jump if not AND */
-#define OPCJNOR     58      /* jump if not OR */
-#define OPCJT       59      /* jump if true */
-
-#define OPCGETPSELF 60      /* get property of the 'self' object */
-#define OPCGETPSLFD 61      /* get property of 'self' and discard result */
-#define OPCGETPOBJ  62      /* get property of a given object */
-                            /*  note: differs from GETP in that object is */
-                            /*  encoded into the instruction */
-#define OPCGETPOBJD 63      /* get property of an object and discard result */
-#define OPCINDEX    64      /* get an indexed entry from a list */
-
-#define OPCPUSHPN   67      /* push a property number */
-
-#define OPCJST      68      /* jump and save top-of-stack if true */
-#define OPCJSF      69      /* jump and save top-of-stack if false */
-#define OPCJMPD     70      /* discard stack and then jump unconditionally */
-
-#define OPCINHERIT  71      /* inherit a property from superclass */
-#define OPCCALLEXT  72      /* call external function */
-#define OPCDBGRET   73      /* return to debugger (no stack frame leaving) */
-
-#define OPCCONS     74      /* construct list from top two stack elements */
-#define OPCSWITCH   75      /* switch statement */
-
-#define OPCARGC     76      /* get argument count */
-#define OPCCHKARGC  77      /* check actual arguments against formal count */
-
-#define OPCLINE     78      /* line record */
-#define OPCFRAME    79      /* local variable frame record */
-#define OPCBP       80      /* breakpoint - replaces an OPCLINE instruction */
-#define OPCGETDBLCL 81                                /* get debugger local */
-#define OPCGETPPTRSELF 82                 /* get property pointer from self */
-#define OPCMOD      83                                            /* modulo */
-#define OPCBAND     84                                        /* binary AND */
-#define OPCBOR      85                                         /* binary OR */
-#define OPCXOR      86                                        /* binary XOR */
-#define OPCBNOT     87                                   /* binary negation */
-#define OPCSHL      88                                    /* bit shift left */
-#define OPCSHR      89                                   /* bit shift right */
-
-#define OPCNEW      90                                 /* create new object */
-#define OPCDELETE   91                                     /* delete object */
-
-
-/* ----- opcodes 192 and above are reserved for assignment operations ----- */
-
-/*
-ASSIGNMENT OPERATIONS
-    When (opcode & 0xc0 == 0xc0), we have an assignment operation.
-    (Note that this means that opcodes from 0xc0 up are all reserved
-    for assignment operations.)  The low six bits of the opcode
-    specify exactly what kind of operation is to be performed:
-    
-    bits 0-1:  specifies destination type:
-               00    2-byte operand is local number
-               01    2-byte operand is property to set in obj at tos
-               10    tos is index, [sp-1] is list to be indexed and set
-               11    tos is property pointer, [sp-1] is object
-    
-    bits 2-4:  specifies assignment operation:
-               000   := (direct assignment)
-               001   += (add tos to destination)
-               010   -= (subtract tos from destination)
-               011   *= (multiply destination by tos)
-               100   /= (divide destination by tos)
-               101   ++ (increment tos)
-               110   -- (decrement tos)
-               111   *reserved*
-               
-    bit 5:     specifies what to do with value computed by assignment
-               0     leave on stack (implies pre increment/decrement)
-               1     discard (implies post increment/decrement)
-*/
-#define OPCASI_MASK      0xc0                     /* assignment instruction */
-
-#define OPCASIDEST_MASK  0x03              /* mask to get destination field */
-#define OPCASILCL        0x00                          /* assign to a local */
-#define OPCASIPRP        0x01               /* assign to an object.property */
-#define OPCASIIND        0x02             /* assign to an element of a list */
-#define OPCASIPRPPTR     0x03                /* assign property via pointer */
-
-#define OPCASITYP_MASK   0x1c          /* mask to get assignment type field */
-#define OPCASIDIR        0x00                          /* direct assignment */
-#define OPCASIADD        0x04                             /* assign and add */
-#define OPCASISUB        0x08                        /* assign and subtract */
-#define OPCASIMUL        0x0c                        /* assign and multiply */
-#define OPCASIDIV        0x10                          /* assign and divide */
-#define OPCASIINC        0x14                                  /* increment */
-#define OPCASIDEC        0x18                                  /* decrement */
-#define OPCASIEXT        0x1c                     /* other - extension flag */
-
-/* extended assignment flags - next byte when OPCASIEXT is used */
-#define OPCASIMOD        1                             /* modulo and assign */
-#define OPCASIBAND       2                         /* binary AND and assign */
-#define OPCASIBOR        3                          /* binary OR and assign */
-#define OPCASIXOR        4                         /* binary XOR and assign */
-#define OPCASISHL        5                         /* shift left and assign */
-#define OPCASISHR        6                        /* shift right and assign */
-
-
-#define OPCASIPRE_MASK   0x20                    /* mask for pre/post field */
-#define OPCASIPOST       0x00                       /* increment after push */
-#define OPCASIPRE        0x20                      /* increment before push */
-
-/* some composite opcodes for convenience */
-#define OPCSETLCL (OPCASI_MASK | OPCASILCL | OPCASIDIR)
-
-} // End of namespace TADS2
-} // End of namespace TADS
-} // End of namespace Glk
-
-#endif
diff --git a/engines/glk/tads/tads2/post_compilation.cpp b/engines/glk/tads/tads2/post_compilation.cpp
new file mode 100644
index 0000000..ca29601
--- /dev/null
+++ b/engines/glk/tads/tads2/post_compilation.cpp
@@ -0,0 +1,466 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/post_compilation.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/os.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/*
+ *   Special character sequence description table for TADS language.
+ *   Note that operators that start with common sequences must be grouped
+ *   together, with the shorter sequences preceding the longer sequences.
+ *   For example, ":" and ":=" must be adjacent, and ":" must precede
+ *   ":=".  Other than this restriction, the order of tokens doesn't
+ *   matter.  
+ */
+tokldef supsctab[] =
+{
+    { TOKTCOLON,   ":"  },
+    { TOKTASSIGN,  ":=" },
+    { TOKTLT,      "<"  },
+    { TOKTLE,      "<=" },
+    { TOKTSHL,     "<<" },
+    { TOKTSHLEQ,   "<<="},
+    { TOKTNE,      "<>" },
+    { TOKTGT,      ">"  },
+    { TOKTSHR,     ">>" },
+    { TOKTSHREQ,   ">>="},
+    { TOKTGE,      ">=" },
+    { TOKTLPAR,    "("  },
+    { TOKTRPAR,    ")"  },
+    { TOKTPLUS,    "+"  },
+    { TOKTINC,     "++" },
+    { TOKTPLEQ,    "+=" },
+    { TOKTMINUS,   "-"  },
+    { TOKTPOINTER, "->" },
+    { TOKTDEC,     "--" },
+    { TOKTMINEQ,   "-=" },
+    { TOKTDIV,     "/"  },
+    { TOKTMOD,     "%"  },
+    { TOKTMODEQ,   "%=" },
+    { TOKTDIVEQ,   "/=" },
+    { TOKTTIMES,   "*"  },
+    { TOKTTIMEQ,   "*=" },
+    { TOKTEQ,      "="  },
+    { TOKTEQEQ,    "==" },
+    { TOKTLBRACK,  "["  },
+    { TOKTRBRACK,  "]"  },
+    { TOKTLBRACE,  "{"  },
+    { TOKTRBRACE,  "}"  },
+    { TOKTSEM,     ";"  },
+    { TOKTCOMMA,   ","  },
+    { TOKTDOT,     "."  },
+    { TOKTELLIPSIS,"..." },
+    { TOKTPOUND,   "#"  },
+    { TOKTBAND,    "&"  },
+    { TOKTBANDEQ,  "&=" },
+    { TOKTAND,     "&&" },
+    { TOKTBOR,     "|"  },
+    { TOKTBOREQ,   "|=" },
+    { TOKTOR,      "||" },
+    { TOKTQUESTION,"?"  },
+    { TOKTDSTRING, "\"" },
+    { TOKTSSTRING, "'"  },
+    { TOKTNOT,     "!"  },
+    { TOKTNE,      "!=" },
+    { TOKTXOR,     "^"  },
+    { TOKTXOREQ,   "^=" },
+    { TOKTTILDE,   "~"  },
+    { 0,           ""   }
+};
+
+typedef struct supkwdef supkwdef;
+struct supkwdef
+{
+    char *supkwnam;
+    int   supkwtok;
+};
+
+static supkwdef supkwtab[] =
+{
+    { "not", TOKTNOT },
+    { "if", TOKTIF },
+    { "else", TOKTELSE },
+    { "while", TOKTWHILE },
+    { "break", TOKTBREAK },
+    { "continue", TOKTCONTINUE },
+    { "exit", TOKTEXIT },
+    { "abort", TOKTABORT },
+    { "and", TOKTAND },
+    { "or", TOKTOR },
+    { "function", TOKTFUNCTION },
+    { "return", TOKTRETURN },
+    { "local", TOKTLOCAL },
+    { "object", TOKTOBJECT },
+    { "nil", TOKTNIL },
+    { "true", TOKTTRUE },
+    { "pass", TOKTPASS },
+    { "askdo", TOKTASKDO },
+    { "askio", TOKTASKIO },
+    { "ioSynonym", TOKTIOSYN },
+    { "doSynonym", TOKTDOSYN },
+    { "external", TOKTEXTERN },
+    { "formatstring", TOKTFORMAT },
+    { "compoundWord", TOKTCOMPOUND },
+    { "specialWords", TOKTSPECIAL },
+    { "class", TOKTCLASS },
+        
+    /* new keywords for V2 */
+    { "\002", 0 },                  /* special flag for start of v2 section */
+    { "for", TOKTFOR },
+    { "\001", 0 },                /* special flag that "do" is next keyword */
+    { "do", TOKTDO },
+    { "switch", TOKTSWITCH },
+    { "case", TOKTCASE },
+    { "default", TOKTDEFAULT },
+    { "goto", TOKTGOTO },
+    { "replace", TOKTREPLACE },
+    { "modify", TOKTMODIFY },
+
+    { "new", TOKTNEW },
+    { "delete", TOKTDELETE },
+    { (char *)0, 0 }
+};
+
+typedef struct supprdef supprdef;
+struct supprdef
+{
+    char   *supprnam;
+    prpnum  supprval;
+};
+
+static supprdef supprtab[] =
+{
+    { "verb", PRP_VERB },
+    { "noun", PRP_NOUN },
+    { "adjective", PRP_ADJ },
+    { "preposition", PRP_PREP },
+    { "article", PRP_ARTICLE },
+    { "plural", PRP_PLURAL },
+
+    /* add some more built-in properties */
+    { "doAction", PRP_DOACTION },
+    { "ioAction", PRP_IOACTION },
+    { "sdesc", PRP_SDESC },
+    { "thedesc", PRP_THEDESC },
+    { "ioDefault", PRP_IODEFAULT },
+    { "doDefault", PRP_DODEFAULT },
+    { "location", PRP_LOCATION },
+    { "value", PRP_VALUE },
+    { "roomAction", PRP_ROOMACTION },
+    { "actorAction", PRP_ACTORACTION },
+    { "contents", PRP_CONTENTS },
+    { "prepDefault", PRP_PREPDEFAULT },
+    { "verActor", PRP_VERACTOR },
+    { "validDo", PRP_VALIDDO },
+    { "validIo", PRP_VALIDIO },
+    { "lookAround", PRP_LOOKAROUND },
+    { "roomCheck", PRP_ROOMCHECK },
+    { "statusLine", PRP_STATUSLINE },
+    { "locationOK", PRP_LOCOK },
+    { "isVisible", PRP_ISVIS },
+    { "cantReach", PRP_NOREACH },
+    { "isHim", PRP_ISHIM },
+    { "isHer", PRP_ISHER },
+    { "action", PRP_ACTION },
+    { "validDoList", PRP_VALDOLIST },
+    { "validIoList", PRP_VALIOLIST },
+    { "dobjGen", PRP_DOBJGEN },
+    { "iobjGen", PRP_IOBJGEN },
+    { "nilPrep", PRP_NILPREP },
+    { "rejectMultiDobj", PRP_REJECTMDO },
+    { "moveInto", PRP_MOVEINTO },
+    { "construct", PRP_CONSTRUCT },
+    { "destruct", PRP_DESTRUCT },
+    { "validActor", PRP_VALIDACTOR },
+    { "preferredActor", PRP_PREFACTOR },
+    { "isEquivalent", PRP_ISEQUIV },
+    { "adesc", PRP_ADESC },
+    { "multisdesc", PRP_MULTISDESC },
+    { "anyvalue", PRP_ANYVALUE },
+    { "newNumbered", PRP_NEWNUMOBJ },
+    { "parseUnknownDobj", PRP_PARSEUNKNOWNDOBJ },
+    { "parseUnknownIobj", PRP_PARSEUNKNOWNIOBJ },
+    { "dobjCheck", PRP_DOBJCHECK },
+    { "iobjCheck", PRP_IOBJCHECK },
+    { "verbAction", PRP_VERBACTION },
+    { "disambigDobj", PRP_DISAMBIGDO },
+    { "disambigIobj", PRP_DISAMBIGIO },
+    { "prefixdesc", PRP_PREFIXDESC },
+    { "isThem", PRP_ISTHEM },
+
+    /* still more - TADS/Graphic properties */
+    { "gp_picture", PRP_GP_PIC },
+    { "gp_name", PRP_GP_NAME },
+    { "gp_defverb", PRP_GP_DEFVERB },
+    { "gp_active", PRP_GP_ACTIVE },
+    { "gp_hotlist", PRP_GP_HOTLIST },
+    { "gp_icon", PRP_GP_ICON },
+    { "gp_defverb2", PRP_GP_DEFVERB2 },
+    { "gp_defprep", PRP_GP_DEFPREP },
+    { "gp_hotid", PRP_GP_HOTID },
+    { "gp_overlay", PRP_GP_OVERLAY },
+    { "gp_hotx", PRP_GP_HOTX },
+    { "gp_hoty", PRP_GP_HOTY },
+
+    /* flag end of list with null property name */
+    { (char *)0, 0 }
+};
+
+/* define a built-in symbol */
+static void supaddsym(toktdef *tab, char *txt, int styp, int sval,
+                      int casefold)
+{
+    char buf[40];
+
+    if (casefold)
+    {
+        strcpy(buf, txt);
+        os_strlwr(buf);
+        txt = buf;
+    }
+    (*tab->toktfadd)(tab, txt, (int)strlen(txt), styp, sval, tokhsh(txt));
+}
+
+/* add a built-in function to a symbol table */
+static void supaddbi(void (*bif[])(bifcxdef *, int),
+                     toktdef *tab, char *txt,
+                     void (*fn)(bifcxdef *, int), int num, int casefold)
+{
+    supaddsym(tab, txt, TOKSTBIFN, num, casefold);
+    bif[num] = fn;
+}
+
+/* set up reserved words: built-in functions and properties, keywords, etc */
+void suprsrv(supcxdef *sup, void (*bif[])(bifcxdef *, int),
+             toktdef *tab, int max, int v1compat, char *new_do,
+             int casefold)
+{
+    supkwdef *kw;
+    supbidef *p;
+    int       i;
+    supprdef *pr;
+    extern    supbidef osfar_t supbitab[];
+    int       do_kw = FALSE;
+    char     *kwname;
+    char      buf[40];
+
+    /* add built-in functions */
+    for (p = supbitab, i = 0 ; p->supbinam ; ++i, ++p)
+    {
+        if (i >= max) errsig(sup->supcxerr, ERR_MANYBIF);
+        supaddbi(bif, tab, p->supbinam, p->supbifn, i, casefold);
+    }
+
+    /* add keywords */
+    for (kw = supkwtab ; kw->supkwnam ; ++kw)
+    {
+        if (kw->supkwnam[0] == '\002')
+        {
+            if (v1compat) break;               /* no v2 keywords - quit now */
+            else continue;          /* keep going, but skip this flag entry */
+        }
+        
+        /* if this is the "do" keyword, change to user-supplied value */
+        if (do_kw && new_do)
+            kwname = new_do;
+        else
+            kwname = kw->supkwnam;
+        
+        if (kw->supkwnam[0] == '\001')
+        {
+            do_kw = TRUE;
+            continue;
+        }
+        else
+            do_kw = FALSE;
+
+        if (casefold)
+        {
+            strcpy(buf, kwname);
+            os_strlwr(buf);
+            kwname = buf;
+        }
+        (*tab->toktfadd)(tab, kwname, (int)strlen(kwname),
+                         TOKSTKW, kw->supkwtok, tokhsh(kwname));
+    }
+
+    /* add pseudo-variables */
+    supaddsym(tab, "self", TOKSTSELF, 0, casefold);
+    supaddsym(tab, "inherited", TOKSTINHERIT, 0, casefold);
+    supaddsym(tab, "argcount", TOKSTARGC, 0, casefold);
+    
+    /* add built-in properties */
+    for (pr = supprtab ; pr->supprnam ; ++pr)
+        supaddsym(tab, pr->supprnam, TOKSTPROP, pr->supprval, casefold);
+}
+
+/* get name of an object out of symbol table */
+void supgnam(char *buf, tokthdef *tab, objnum objn)
+{
+    toksdef sym;
+    
+    if (!tab)
+    {
+        strcpy(buf, "<NO SYMBOL TABLE>");
+        return;
+    }
+    
+    if (tokthfind((toktdef *)tab, TOKSTOBJ, (uint)objn, &sym)
+        || tokthfind((toktdef *)tab, TOKSTFWDOBJ, (uint)objn, &sym))
+    {
+        memcpy(buf, sym.toksnam, (size_t)sym.tokslen);
+        buf[sym.tokslen] = '\0';
+        return;
+    }
+
+    strcpy(buf, "<UNKNOWN>");
+}
+
+/* set up inherited vocabulary */
+void supivoc(supcxdef *ctx)
+{
+    vocidef ***vpg;
+    vocidef  **v;
+    voccxdef  *voc = ctx->supcxvoc;
+    int        i;
+    int        j;
+    objnum     obj;
+    
+    /* delete all existing inherited words */
+    vocdelinh(voc);
+    
+    for (vpg = voc->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
+    {
+        if (!*vpg) continue;                     /* no entries on this page */
+        for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
+        {
+            /* if it's not a class, inherit vocabulary for the object */
+            if (!*v) continue;
+            if (!((*v)->vociflg & VOCIFCLASS))
+            {
+                (*v)->vociilc = MCMONINV;      /* no inherited location yet */
+                supivoc1(ctx, ctx->supcxvoc, *v, obj, FALSE, 0);
+            }
+        }
+    }
+}
+
+/* find a single required object, by name */
+static void supfind1(errcxdef *ec, toktdef *tab, char *nam, objnum *objp,
+                     int required, int *errp, int warnlevel, int casefold)
+{
+    toksdef sym;
+    int     namel = strlen(nam);
+    char    buf[40];
+
+    if (casefold)
+    {
+        strcpy(buf, nam);
+        os_strlwr(buf);
+        nam = buf;
+    }
+    if ((*tab->toktfsea)(tab, nam, namel, tokhsh(nam), &sym))
+    {
+        *objp = (objnum)sym.toksval;
+    }
+    else
+    {
+        if (required || warnlevel > 1)
+            errlog1(ec, (required ? ERR_RQOBJNF : ERR_WRNONF),
+                    ERRTSTR, errstr(ec, nam, namel));
+        *objp = MCMONINV;
+        if (required) *errp = 1;
+    }
+}
+
+/* find required objects/functions */
+void supfind(errcxdef *ec, tokthdef *htab, voccxdef *voc,
+             objnum *preinit, int warnlevel, int cf)
+{
+    int      err = 0;
+    toktdef *tab = &htab->tokthsc;
+
+    /* look up the required and optional symbols */
+    supfind1(ec, tab, "Me", &voc->voccxme, TRUE, &err, warnlevel, cf);
+    supfind1(ec, tab, "takeVerb", &voc->voccxvtk, TRUE, &err, warnlevel, cf);
+    supfind1(ec, tab, "strObj", &voc->voccxstr, TRUE, &err, warnlevel, cf);
+    supfind1(ec, tab, "numObj", &voc->voccxnum, TRUE, &err, warnlevel, cf);
+    supfind1(ec, tab, "pardon", &voc->voccxprd, TRUE, &err, warnlevel, cf);
+    supfind1(ec, tab, "againVerb", &voc->voccxvag, TRUE, &err, warnlevel, cf);
+    supfind1(ec, tab, "init", &voc->voccxini, TRUE, &err, warnlevel, cf);
+    supfind1(ec, tab, "preinit", preinit, FALSE, &err, warnlevel, cf);
+    supfind1(ec, tab, "preparse", &voc->voccxpre, FALSE, &err, warnlevel, cf);
+    supfind1(ec, tab, "preparseExt", &voc->voccxpre2, FALSE, &err,
+             warnlevel, cf);
+    supfind1(ec, tab, "parseError", &voc->voccxper, FALSE, &err, warnlevel,
+             cf);
+    supfind1(ec, tab, "commandPrompt", &voc->voccxprom, FALSE, &err,
+             warnlevel, cf);
+    supfind1(ec, tab, "parseDisambig", &voc->voccxpdis, FALSE, &err,
+             warnlevel, cf);
+    supfind1(ec, tab, "parseError2", &voc->voccxper2, FALSE, &err, warnlevel,
+             cf);
+    supfind1(ec, tab, "parseDefault", &voc->voccxpdef, FALSE, &err, warnlevel,
+             cf);
+    supfind1(ec, tab, "parseDefaultExt", &voc->voccxpdef2, FALSE, &err,
+             warnlevel, cf);
+    supfind1(ec, tab, "parseAskobj", &voc->voccxpask, FALSE, &err, warnlevel,
+             cf);
+    supfind1(ec, tab, "preparseCmd", &voc->voccxppc, FALSE, &err, warnlevel,
+             cf);
+    supfind1(ec, tab, "parseAskobjActor", &voc->voccxpask2, FALSE,
+             &err, warnlevel, cf);
+    supfind1(ec, tab, "parseAskobjIndirect", &voc->voccxpask3, FALSE,
+             &err, warnlevel, cf);
+    supfind1(ec, tab, "parseErrorParam", &voc->voccxperp, FALSE, &err,
+             warnlevel, cf);
+    supfind1(ec, tab, "commandAfterRead", &voc->voccxpostprom, FALSE,
+             &err, warnlevel, cf);
+    supfind1(ec, tab, "initRestore", &voc->voccxinitrestore, FALSE,
+             &err, warnlevel, cf);
+    supfind1(ec, tab, "parseUnknownVerb", &voc->voccxpuv, FALSE,
+             &err, warnlevel, cf);
+    supfind1(ec, tab, "parseNounPhrase", &voc->voccxpnp, FALSE,
+             &err, warnlevel, cf);
+    supfind1(ec, tab, "postAction", &voc->voccxpostact, FALSE,
+             &err, warnlevel, cf);
+    supfind1(ec, tab, "endCommand", &voc->voccxendcmd, FALSE,
+             &err, warnlevel, cf);
+    supfind1(ec, tab, "preCommand", &voc->voccxprecmd, FALSE,
+             &err, warnlevel, cf);
+
+    /* "Me" is always the initial Me object */
+    voc->voccxme_init = voc->voccxme;
+
+    /* if we encountered any errors, signal the problem */
+    if (err)
+        errsig(ec, ERR_UNDEF);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index 71b8d78..732cfdf 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -37,7 +37,7 @@
 #include "glk/tads/tads2/object.h"
 #include "glk/tads/tads2/memory_cache.h"
 #include "glk/tads/tads2/memory_cache_swap.h"
-#include "glk/tads/tads2/opcode_defs.h"
+#include "glk/tads/tads2/opcode.h"
 #include "glk/tads/tads2/property.h"
 #include "glk/tads/tads2/text_io.h"
 #include "glk/tads/tads2/tokenizer.h"


Commit: fcb2592ec24f983c49f1e2e1b4f41cc9659a9229
    https://github.com/scummvm/scummvm/commit/fcb2592ec24f983c49f1e2e1b4f41cc9659a9229
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS2: Added built in functions

Changed paths:
  A engines/glk/tads/tads2/built_in.cpp
  A engines/glk/tads/tads2/debug.cpp
  A engines/glk/tads/tads2/string_resources.h
    engines/glk/module.mk
    engines/glk/tads/os_glk.h
    engines/glk/tads/osfrobtads.cpp
    engines/glk/tads/osfrobtads.h
    engines/glk/tads/tads2/error.cpp
    engines/glk/tads/tads2/error_handling.cpp
    engines/glk/tads/tads2/os.cpp


diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index ef5dcb3..810c493 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -94,8 +94,10 @@ MODULE_OBJS := \
 	tads/detection.o \
 	tads/osfrobtads.o \
 	tads/tads.o \
+	tads/tads2/built_in.o \
 	tads/tads2/character_map.o \
 	tads/tads2/data.o \
+	tads/tads2/debug.o \
 	tads/tads2/error.o \
 	tads/tads2/error_handling.o \
 	tads/tads2/file_io.o \
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
index e58868b..75354ea 100644
--- a/engines/glk/tads/os_glk.h
+++ b/engines/glk/tads/os_glk.h
@@ -31,6 +31,9 @@
 namespace Glk {
 namespace TADS {
 
+#define TADS_RUNTIME_VERSION "2.5.17"
+#define OS_SYSTEM_NAME "ScummGlk"
+
 #define OSPATHCHAR '/'
 #define OSPATHALT ""
 #define OSPATHURL "/"
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
index eabd297..6dcaadb 100644
--- a/engines/glk/tads/osfrobtads.cpp
+++ b/engines/glk/tads/osfrobtads.cpp
@@ -81,6 +81,10 @@ char *osfgets(char *buf, size_t count, osfildef *fp) {
 	return buf;
 }
 
+int osfputs(const char *str, osfildef *fp) {
+	return dynamic_cast<Common::WriteStream *>(fp)->write(str, strlen(str)) == strlen(str) ? 0 : -1;
+}
+
 bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) {
 	Common::String name = !flen ? Common::String(fname) : Common::String(fname, fname + flen);
 
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
index 5c9bcd8..d503a7b 100644
--- a/engines/glk/tads/osfrobtads.h
+++ b/engines/glk/tads/osfrobtads.h
@@ -168,7 +168,7 @@ int osfmode( const char* fname, int follow_links, unsigned long* mode,
 #define osrealloc realloc
 
 /* Open text file for reading. */
-#define osfoprt(fname,typ) (fopen((fname),"r"))
+#define osfoprt(fname,typ) osfoprb(fname,typ)
 
 /* Open text file for writing. */
 osfildef *osfopwt(const char *fname, os_filetype_t typ);
@@ -208,7 +208,7 @@ inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
 char *osfgets(char *buf, size_t count, osfildef *fp);
 
 /* Write a line of text to a text file. */
-#define osfputs fputs
+int osfputs(const char *str, osfildef *fp);
 
 /* Write bytes to file. */
 inline bool osfwb(osfildef *fp, const void *buf, size_t count);
diff --git a/engines/glk/tads/tads2/built_in.cpp b/engines/glk/tads/tads2/built_in.cpp
new file mode 100644
index 0000000..30c2772
--- /dev/null
+++ b/engines/glk/tads/tads2/built_in.cpp
@@ -0,0 +1,4333 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/built_in.h"
+#include "glk/tads/tads2/appctx.h"
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/file_io.h"
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/os.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/string_resources.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* yorn - yes or no */
+void bifyon(bifcxdef *ctx, int argc)
+{
+    char     rsp[128];
+    char    *p;
+    runsdef  val;
+    char     yesbuf[64];
+    char     nobuf[64];
+    re_context  rectx;
+    int      match_yes;
+    int      match_no;
+
+    bifcntargs(ctx, 0, argc);            /* check for proper argument count */
+
+    /* load the "yes" and "no" reply patterns */
+    if (os_get_str_rsc(RESID_YORN_YES, yesbuf, sizeof(yesbuf)))
+        strcpy(yesbuf, "[Yy].*");
+    if (os_get_str_rsc(RESID_YORN_NO, nobuf, sizeof(nobuf)))
+        strcpy(nobuf, "[Nn].*");
+    
+    /* if we're in HTML mode, switch to input font */
+    if (tio_is_html_mode())
+        tioputs(ctx->bifcxtio, "<font face='TADS-Input'>");
+
+    /* ensure the prompt is displayed */
+    tioflushn(ctx->bifcxtio, 0);
+
+    /* reset count of lines since the last keyboard input */
+    tioreset(ctx->bifcxtio);
+
+    /* read a line of text */
+    if (tiogets(ctx->bifcxtio, (char *)0, rsp, (int)sizeof(rsp)))
+        runsig(ctx->bifcxrun, ERR_RUNQUIT);
+
+    /* if we're in HTML mode, close the input font tag */
+    if (tio_is_html_mode())
+        tioputs(ctx->bifcxtio, "</font>");
+
+    /* scan off leading spaces */
+    for (p = rsp ; t_isspace(*p) ; ++p) ;
+
+    /* set up our regex context */
+    re_init(&rectx, ctx->bifcxerr);
+
+    /* check for a "yes" response */
+    match_yes = re_compile_and_match(&rectx, yesbuf, strlen(yesbuf),
+                                     p, strlen(p));
+
+    /* check for a "no" response */
+    match_no = re_compile_and_match(&rectx, nobuf, strlen(nobuf),
+                                    p, strlen(p));
+
+    /* check the result */
+    if (match_yes == (int)strlen(p))
+        val.runsv.runsvnum = 1;
+    else if (match_no == (int)strlen(p))
+        val.runsv.runsvnum = 0;
+    else
+        val.runsv.runsvnum = -1;
+
+    /* delete our regex context */
+    re_delete(&rectx);
+
+    /* push the result */
+    runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* setfuse */
+void bifsfs(bifcxdef *ctx, int argc)
+{
+    objnum    func;
+    uint      tm;
+    runsdef   val;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+    
+    bifcntargs(ctx, 3, argc);            /* check for proper argument count */
+    func = runpopfn(ctx->bifcxrun);
+    tm = runpopnum(ctx->bifcxrun);
+    runpop(ctx->bifcxrun, &val);
+    
+    /* limitation:  don't allow string or list for value */
+    if (val.runstyp == DAT_LIST || val.runstyp == DAT_SSTRING)
+        runsig(ctx->bifcxrun, ERR_FUSEVAL);
+    
+    vocsetfd(voc, voc->voccxfus, func, (prpnum)0,
+             tm, &val, ERR_MANYFUS);
+}
+
+/* remfuse */
+void bifrfs(bifcxdef *ctx, int argc)
+{
+    objnum    func;
+    runsdef   val;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+    
+    bifcntargs(ctx, 2, argc);
+    func = runpopfn(ctx->bifcxrun);
+    runpop(ctx->bifcxrun, &val);
+    vocremfd(voc, voc->voccxfus, func, (prpnum)0,
+             &val, ERR_NOFUSE);
+}
+
+/* setdaemon */
+void bifsdm(bifcxdef *ctx, int argc)
+{
+    objnum    func;
+    runsdef   val;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+    
+    bifcntargs(ctx, 2, argc);            /* check for proper argument count */
+    func = runpopfn(ctx->bifcxrun);
+    runpop(ctx->bifcxrun, &val);
+    
+    /* limitation:  don't allow string or list for value */
+    if (val.runstyp == DAT_LIST || val.runstyp == DAT_SSTRING)
+        runsig(ctx->bifcxrun, ERR_FUSEVAL);
+    
+    vocsetfd(voc, voc->voccxdmn, func, (prpnum)0, 0,
+             &val, ERR_MANYDMN);
+}
+
+/* remdaemon */
+void bifrdm(bifcxdef *ctx, int argc)
+{
+    objnum    func;
+    runsdef   val;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+    
+    bifcntargs(ctx, 2, argc);
+    func = runpopfn(ctx->bifcxrun);
+    runpop(ctx->bifcxrun, &val);
+    vocremfd(voc, voc->voccxdmn, func, (prpnum)0,
+             &val, ERR_NODMN);
+}
+
+/* incturn */
+void bifinc(bifcxdef *ctx, int argc)
+{
+    int turncnt;
+    
+    if (argc == 1)
+    {
+        /* get the number of turns to skip */
+        turncnt = runpopnum(ctx->bifcxrun);
+        if (turncnt < 1)
+            runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "incturn");
+    }
+    else
+    {
+        /* no arguments -> increment by one turn */
+        bifcntargs(ctx, 0, argc);
+        turncnt = 1;
+    }
+
+    /* skip the given number of turns */
+    vocturn(ctx->bifcxrun->runcxvoc, turncnt, TRUE);
+}
+
+/* skipturn */
+void bifskt(bifcxdef *ctx, int argc)
+{
+    int turncnt;
+
+    bifcntargs(ctx, 1, argc);
+    turncnt = runpopnum(ctx->bifcxrun);
+    if (turncnt < 1)
+        runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "skipturn");
+    vocturn(ctx->bifcxrun->runcxvoc, turncnt, FALSE);
+}
+
+/* quit */
+void bifqui(bifcxdef *ctx, int argc)
+{
+    /* check for proper argument count */
+    bifcntargs(ctx, 0, argc);
+
+    /* flush output buffer, and signal the end of the game */
+    tioflush(ctx->bifcxtio);
+    errsig(ctx->bifcxerr, ERR_RUNQUIT);
+}
+
+/* internal function to convert a TADS string into a C-string */
+static void bifcstr(bifcxdef *ctx, char *buf, size_t bufsiz, uchar *str)
+{
+    size_t  srcrem;
+    size_t  dstrem;
+    uchar  *src;
+    char   *dst;
+
+    /* get the length and text portion of the string */
+    srcrem = osrp2(str) - 2;
+    str += 2;
+
+    /* scan the string, and convert escapes */
+    for (src = str, dst = buf, dstrem = bufsiz ;
+         srcrem != 0 && dstrem != 0 ; ++src, --srcrem)
+    {
+        /* if we have an escape sequence, convert it */
+        if (*src == '\\')
+        {
+            /* skip the backslash in the input */
+            ++src;
+            --srcrem;
+
+            /* if there's nothing left, store the backslash */
+            if (srcrem == 0)
+            {
+                /* store the backslash */
+                *dst++ = '\\';
+                --dstrem;
+
+                /* there's nothing left to scan */
+                break;
+            }
+
+            /* see what the second half of the escape sequence is */
+            switch(*src)
+            {
+            case 'n':
+                /* store a C-style newline character */
+                *dst++ = '\n';
+                --dstrem;
+                break;
+
+            case 't':
+                /* store a C-style tab */
+                *dst++ = '\t';
+                --dstrem;
+                break;
+
+            case '(':
+            case ')':
+                /* entirely omit the highlighting sequences */
+                break;
+
+            default:
+                /* store everything else unchanged */
+                *dst++ = *src;
+                --dstrem;
+                break;
+            }
+        }
+        else
+        {
+            /* copy this character unchanged */
+            *dst++ = *src;
+            --dstrem;
+        }
+    }
+
+    /* if the buffer wasn't big enough, signal an error */
+    if (dstrem == 0)
+        runsig(ctx->bifcxrun, ERR_BIFCSTR);
+
+    /* null-terminate the result string */
+    *dst = '\0';
+}
+
+/* save */
+void bifsav(bifcxdef *ctx, int argc)
+{
+    uchar   *fn;
+    char     buf[OSFNMAX];
+    int      err;
+    runsdef  val;
+    
+    bifcntargs(ctx, 1, argc);
+    fn = runpopstr(ctx->bifcxrun);
+    bifcstr(ctx, buf, (size_t)sizeof(buf), fn);
+    os_defext(buf, ctx->bifcxsavext != 0 ? ctx->bifcxsavext : "sav");
+    err = fiosav(ctx->bifcxrun->runcxvoc, buf, ctx->bifcxrun->runcxgamename);
+    runpush(ctx->bifcxrun, runclog(err), &val);
+}
+
+/* restore */
+void bifrso(bifcxdef *ctx, int argc)
+{
+    uchar    *fn;
+    char      buf[OSFNMAX];
+    int       err;
+    voccxdef *vctx = ctx->bifcxrun->runcxvoc;
+    
+    bifcntargs(ctx, 1, argc);
+    
+    /* check for special restore(nil) - restore game given as parameter */
+    if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+    {
+        /* get filename from startup parameter, if any */
+        if (!os_paramfile(buf))
+        {
+            /* no startup parameter */
+            runpnum(ctx->bifcxrun, FIORSO_NO_PARAM_FILE);
+            return;
+        }
+    }
+    else
+    {
+        /* get string parameter - it's the filename */
+        fn = runpopstr(ctx->bifcxrun);
+        bifcstr(ctx, buf, (size_t)sizeof(buf), fn);
+        os_defext(buf, ctx->bifcxsavext != 0 ? ctx->bifcxsavext : "sav");
+    }
+
+    /* try restoring the file */
+    err = fiorso(vctx, buf);
+
+    /* blow away all undo records */
+    objulose(vctx->voccxundo);
+
+    /* return the result code from fiorso */
+    runpnum(ctx->bifcxrun, err);
+
+    /* note that the rest of the command line is to be ignored */
+    vctx->voccxflg |= VOCCXFCLEAR;
+}
+
+/* logging */
+void biflog(bifcxdef *ctx, int argc)
+{
+    char   buf[OSFNMAX];
+    uchar *str;
+    
+    bifcntargs(ctx, 1, argc);
+    if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+    {
+        rundisc(ctx->bifcxrun);
+        tiologcls(ctx->bifcxtio);
+    }
+    else
+    {
+        str = runpopstr(ctx->bifcxrun);
+        bifcstr(ctx, buf, (size_t)sizeof(buf), str);
+        tiologopn(ctx->bifcxtio, buf);
+    }
+}
+
+/* restart */
+void bifres(bifcxdef *ctx, int argc)
+{
+    voccxdef *vctx = ctx->bifcxrun->runcxvoc;
+    objnum    fn;
+
+    if (argc == 2)
+        fn = runpopfn(ctx->bifcxrun);            /* get function if present */
+    else
+    {
+        bifcntargs(ctx, 0, argc);        /* check for proper argument count */
+        fn = MCMONINV;                         /* no function was specified */
+    }
+
+    objulose(vctx->voccxundo);                /* blow away all undo records */
+    vocrevert(vctx);                /* revert all objects to original state */
+    vocdmnclr(vctx);                   /* clear out fuses/deamons/notifiers */
+
+    /* restore the original "Me" object */
+    vctx->voccxme = vctx->voccxme_init;
+
+    /* call preinit if necessary (call it before invoking the user callback) */
+    if (vctx->voccxpreinit != MCMONINV)
+        runfn(ctx->bifcxrun, vctx->voccxpreinit, 0);
+
+    /*
+     *   If a restart function was provided, call it.  Note that we left
+     *   the argument for the function on the stack, so there's no need to
+     *   re-push it!  
+     */
+    if (fn != MCMONINV) runfn(ctx->bifcxrun, fn, 1);
+
+    /* restart the game */
+    errsig(ctx->bifcxerr, ERR_RUNRESTART);
+}
+
+/* input - get a line of input from the keyboard */
+void bifinp(bifcxdef *ctx, int argc)
+{
+    char inbuf[128];
+
+    /* check for proper argument count */
+    bifcntargs(ctx, 0, argc);
+
+    /* make sure the prompt is displayed */
+    tioflushn(ctx->bifcxtio, 0);
+
+     /* reset count of lines since the last keyboard input */
+    tioreset(ctx->bifcxtio);
+
+    /* read a line of text */
+    if (tiogets(ctx->bifcxtio, (char *)0, inbuf, (int)sizeof(inbuf)))
+        runsig(ctx->bifcxrun, ERR_RUNQUIT);
+
+    /* push the string, converting escapes */
+    runpushcstr(ctx->bifcxrun, inbuf, strlen(inbuf), 0);
+}
+
+/* notify */
+void bifnfy(bifcxdef *ctx, int argc)
+{
+    objnum    objn;
+    prpnum    prp;
+    uint      tm;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+    
+    bifcntargs(ctx, 3, argc);            /* check for proper argument count */
+    objn = runpopobj(ctx->bifcxrun);
+    prp = runpopprp(ctx->bifcxrun);
+    tm = runpopnum(ctx->bifcxrun);
+
+    /* a time of zero means every turn */
+    if (tm == 0)
+        tm = VOCDTIM_EACH_TURN;
+    
+    vocsetfd(voc, voc->voccxalm, objn, prp, tm,
+             (runsdef *)0, ERR_MANYNFY);
+}
+
+
+/* unnotify */
+void bifunn(bifcxdef *ctx, int argc)
+{
+    objnum    objn;
+    prpnum    prop;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+    
+    bifcntargs(ctx, 2, argc);
+    objn = runpopobj(ctx->bifcxrun);
+    prop = runpopprp(ctx->bifcxrun);
+    vocremfd(voc, voc->voccxalm, objn, prop,
+             (runsdef *)0, ERR_NONFY);
+}
+
+/* trace on/off */
+void biftrc(bifcxdef *ctx, int argc)
+{
+    runsdef val;
+    int     n;
+    int     flag;
+
+    if (argc == 2)
+    {
+        /* get the type indicator and the on/off status */
+        n = runpopnum(ctx->bifcxrun);
+        flag = runpoplog(ctx->bifcxrun);
+
+        /* see what type of debugging they want to turn on or off */
+        switch(n)
+        {
+        case 1:
+            /* turn on parser tracing */
+            if (flag)
+                ctx->bifcxrun->runcxvoc->voccxflg |= VOCCXFDBG;
+            else
+                ctx->bifcxrun->runcxvoc->voccxflg &= ~VOCCXFDBG;
+            break;
+
+        default:
+            /* ignore other requests */
+            runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "debugTrace");
+        }
+    }
+    else
+    {
+        /* break into debugger; return whether debugger is present */
+        bifcntargs(ctx, 0, argc);
+        runpush(ctx->bifcxrun, runclog(dbgstart(ctx->bifcxrun->runcxdbg)),
+                &val);
+    }
+}
+
+/* say */
+void bifsay(bifcxdef *ctx, int argc)
+{
+    uchar *str;
+    long   num;
+    char   numbuf[30];
+
+    if (argc != 2) bifcntargs(ctx, 1, argc);
+    
+    switch(runtostyp(ctx->bifcxrun))
+    {
+    case DAT_NUMBER:
+        num = runpopnum(ctx->bifcxrun);
+        sprintf(numbuf, "%ld", num);
+        tioputs(ctx->bifcxtio, numbuf);
+        break;
+        
+    case DAT_SSTRING:
+        str = runpopstr(ctx->bifcxrun);
+        outfmt(ctx->bifcxtio, str);
+        break;
+        
+    case DAT_NIL:
+        (void)runpoplog(ctx->bifcxrun);
+        break;
+        
+    default:
+        runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "say");
+    }
+}
+
+/* car */
+void bifcar(bifcxdef *ctx, int argc)
+{
+    uchar   *lstp;
+    uint     lstsiz;
+    runsdef  val;
+    
+    bifcntargs(ctx, 1, argc);
+    bifchkarg(ctx, DAT_LIST);
+    
+    lstp = runpoplst(ctx->bifcxrun);
+    
+    /* get list's size, and point to its data string */
+    lstsiz = osrp2(lstp) - 2;
+    lstp += 2;
+    
+    /* push first element if one is present, otherwise push nil */
+    if (lstsiz)
+        runpbuf(ctx->bifcxrun, *lstp, lstp+1);
+    else
+        runpush(ctx->bifcxrun, DAT_NIL, &val);
+}
+
+/* cdr */
+void bifcdr(bifcxdef *ctx, int argc)
+{
+    uchar   *lstp;
+    uint     siz;
+    uint     lstsiz;
+    runsdef  val;
+    runsdef  stkval;
+    
+    bifcntargs(ctx, 1, argc);
+    bifchkarg(ctx, DAT_LIST);
+    
+    lstp = runpoplst(ctx->bifcxrun);
+    stkval.runstyp = DAT_LIST;
+    stkval.runsv.runsvstr = lstp;
+    
+    /* get list's size, and point to its data string */
+    lstsiz = osrp2(lstp) - 2;
+    lstp += 2;
+    
+    if (lstsiz != 0)
+    {
+        /* deduct size of first element from size of list */
+        siz = datsiz(*lstp, lstp+1) + 1;
+        lstsiz -= siz;
+
+        /* add in the size prefix for our new list size */
+        lstsiz += 2;
+
+        /* allocate space for new list containing rest of list */
+        runhres1(ctx->bifcxrun, lstsiz, 1, &stkval);
+        lstp = stkval.runsv.runsvstr + siz + 2;
+
+        /* write out size followed by list value string */
+        oswp2(ctx->bifcxrun->runcxhp, lstsiz);
+        memcpy(ctx->bifcxrun->runcxhp+2, lstp, (size_t)(lstsiz-2));
+        
+        val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+        val.runstyp = DAT_LIST;
+        ctx->bifcxrun->runcxhp += lstsiz;
+        runrepush(ctx->bifcxrun, &val);
+    }
+    else
+        runpush(ctx->bifcxrun, DAT_NIL, &val);   /* empty list - cdr is nil */
+}
+
+/* caps */
+void bifcap(bifcxdef *ctx, int argc)
+{
+    bifcntargs(ctx, 0, argc);
+    tiocaps(ctx->bifxtio);  /* set output driver next-char-capitalized flag */
+}
+
+/* nocaps */
+void bifnoc(bifcxdef *ctx, int argc)
+{
+    bifcntargs(ctx, 0, argc);
+    tionocaps(ctx->bifxtio);               /* set next-not-capitalized flag */
+}
+
+/* length */
+void biflen(bifcxdef *ctx, int argc)
+{
+    uchar   *p;
+    runsdef  val;
+    long     len = 0;
+    int      l;
+    
+    bifcntargs(ctx, 1, argc);
+    switch(runtostyp(ctx->bifcxrun))
+    {
+    case DAT_SSTRING:
+        p = (uchar *)runpopstr(ctx->bifcxrun);
+        len = osrp2(p) - 2;
+        break;
+
+    case DAT_LIST:
+        p = runpoplst(ctx->bifcxrun);
+        l = osrp2(p) - 2;
+        p += 2;
+        
+        /* count all elements in list */
+        for (len = 0 ; l ; ++len)
+        {
+            int cursiz;
+            
+            /* get size of this element, and move past it */
+            cursiz = datsiz(*p, p+1) + 1;
+            l -= cursiz;
+            p += cursiz;
+        }
+        break;
+
+    default:
+        runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "length");
+    }
+    
+    val.runsv.runsvnum = len;
+    runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* find */
+void biffnd(bifcxdef *ctx, int argc)
+{
+    uchar   *p1, *p2;
+    int      len1, len2;
+	int      outv = 0;
+    runsdef  val;
+    int      typ = 0;
+    int      siz;
+    
+    bifcntargs(ctx, 2, argc);
+    switch(runtostyp(ctx->bifcxrun))
+    {
+    case DAT_SSTRING:
+        p1 = runpopstr(ctx->bifcxrun);
+        len1 = osrp2(p1) - 2;
+        p1 += 2;
+        
+        p2 = runpopstr(ctx->bifcxrun);
+        len2 = osrp2(p2) - 2;
+        p2 += 2;
+
+        /* look for p2 within p1 */
+        for (typ = DAT_NIL, outv = 1 ; len1 >= len2 ; ++p1, --len1, ++outv)
+        {
+            if (!memcmp(p1, p2, (size_t)len2))
+            {
+                typ = DAT_NUMBER;           /* use number in outv after all */
+                break;                        /* that's it - we've found it */
+            }
+        }
+        break;
+        
+    case DAT_LIST:
+        p1 = runpoplst(ctx->bifcxrun);
+        len1 = osrp2(p1) - 2;
+        p1 += 2;
+
+        /* get second item:  any old datatype */
+        runpop(ctx->bifcxrun, &val);
+        
+        for (typ = DAT_NIL, outv = 1 ; len1 ; ++outv, p1 += siz, len1 -= siz)
+        {
+            siz = datsiz(*p1, p1 + 1) + 1;      /* get size of this element */
+            if (val.runstyp != *p1) continue;          /* types don't match */
+            
+            switch(val.runstyp)
+            {
+            case DAT_NUMBER:
+                if (val.runsv.runsvnum != osrp4s(p1 + 1)) continue;
+                break;
+                
+            case DAT_SSTRING:
+            case DAT_LIST:
+                if (osrp2(p1 + 1) != osrp2(val.runsv.runsvstr) ||
+                    memcmp(p1 + 3, val.runsv.runsvstr + 2,
+                           (size_t)(osrp2(p1 + 1) - 2)))
+                    continue;
+                break;
+                
+            case DAT_PROPNUM:
+                if (osrp2(p1 + 1) != val.runsv.runsvprp) continue;
+                break;
+                
+            case DAT_OBJECT:
+            case DAT_FNADDR:
+                if (osrp2(p1 + 1) != val.runsv.runsvobj) continue;
+                break;
+                
+            default:
+                break;
+            }
+            
+            /* if we got here, it means we found a match */
+            typ = DAT_NUMBER;                      /* use the value in outv */
+            break;                            /* that's it - we've found it */
+        }
+        break;
+        
+    default:
+        runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "find");
+    }
+    
+    /* push the value given by typ and outv */
+    val.runsv.runsvnum = outv;
+    runpush(ctx->bifcxrun, typ, &val);
+}
+
+/* setit - set current 'it' */
+void bifsit(bifcxdef *ctx, int argc)
+{
+    objnum    obj;
+    int       typ;
+    voccxdef *vcx = ctx->bifcxrun->runcxvoc;
+    
+    /* check for extended version that allows setting him/her */
+    if (argc == 2)
+    {
+        if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+        {
+            rundisc(ctx->bifcxrun);                      /* discard the nil */
+            obj = MCMONINV;                           /* use invalid object */
+        }
+        else
+            obj = runpopobj(ctx->bifcxrun);               /* get the object */
+
+        typ = runpopnum(ctx->bifcxrun);                     /* get the code */
+        vcx->voccxthc = 0;                         /* clear the 'them' list */
+
+        switch(typ)
+        {
+        case 0:                                                 /* set "it" */
+            vcx->voccxit = obj;
+            break;
+
+        case 1:                                                /* set "him" */
+            vcx->voccxhim = obj;
+            break;
+
+        case 2:                                                /* set "her" */
+            vcx->voccxher = obj;
+            break;
+        }
+        return;
+    }
+
+    /* "setit classic" has one argument only */
+    bifcntargs(ctx, 1, argc);
+
+    /* check to see if we're setting 'it' or 'them' */
+    if (runtostyp(ctx->bifcxrun) == DAT_LIST)
+    {
+        uchar *lst;
+        uint   siz;
+        int    cnt;
+
+        lst = runpoplst(ctx->bifcxrun);
+        siz = osrp2(lst);
+        lst += 2;
+        siz -= 2;
+
+        for (cnt = 0 ; siz ; )
+        {
+            /* if this is an object, add to 'them' list (otherwise ignore) */
+            if (*lst == DAT_OBJECT)
+                vcx->voccxthm[cnt++] = osrp2(lst+1);
+
+            lstadv(&lst, &siz);
+        }
+        vcx->voccxthc = cnt;
+        vcx->voccxit = MCMONINV;
+    }
+    else
+    {
+        /* set 'it', and delete 'them' list */
+        if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+        {
+            vcx->voccxit = MCMONINV;
+            rundisc(ctx->bifcxrun);
+        }
+        else
+            vcx->voccxit = runpopobj(ctx->bifcxrun);
+        vcx->voccxthc = 0;
+    }
+}
+
+/* randomize - seed random number generator */
+void bifsrn(bifcxdef *ctx, int argc)
+{
+    bifcntargs(ctx, 0, argc);
+    os_rand(&ctx->bifcxrnd);
+    ctx->bifcxrndset = TRUE;
+}
+
+/* rand - get a random number */
+void bifrnd(bifcxdef *ctx, int argc)
+{
+    unsigned long result, max, randseed;
+    int      tmp;
+    runsdef  val;
+
+    /* get argument - number giving upper bound of generated number */
+    bifcntargs(ctx, 1, argc);
+    bifchkarg(ctx, DAT_NUMBER);
+    max = runpopnum(ctx->bifcxrun);
+
+    /* if the max is zero, just return zero */
+    if (max == 0)
+    {
+        runpnum(ctx->bifcxrun, 0);
+        return;
+    }
+
+    /*
+     *   If the random number generator has been seeded by a call to
+     *   randomize(), use the new, improved random number generator.  If
+     *   not, use the old random number generator to ensure that the same
+     *   sequence of numbers is generated as always (to prevent breaking
+     *   existing test scripts based on the old sequence). 
+     */
+    if (!ctx->bifcxrndset)
+    {
+        /* compute the next number in sequence, using old cheesy generator */
+        randseed = ctx->bifcxrnd;
+        randseed *= 1033;
+        randseed += 5;
+        tmp = randseed / 16384;
+        randseed %= 16384;
+        result = tmp / 7;
+
+        /* adjust the result to be in the requested range */
+        result = ( randseed % max ) + 1;
+        
+        /* save the new seed value, and return the value */
+        ctx->bifcxrnd = randseed;
+        val.runsv.runsvnum = result;
+        runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+    }
+    else
+    {
+#define BIF_RAND_M  ((ulong)2147483647)
+#define BIF_RAND_Q  ((ulong)127773)
+#define BIF_RAND_A  ((ulong)16807)
+#define BIF_RAND_R  ((ulong)2836)
+
+        long lo, hi, test;
+
+        lo = ctx->bifcxrnd / BIF_RAND_Q;
+        hi = ctx->bifcxrnd % BIF_RAND_Q;
+        test = BIF_RAND_A*lo - BIF_RAND_R*hi;
+        ctx->bifcxrnd = test;
+        if (test > 0)
+            ctx->bifcxrnd = test;
+        else
+            ctx->bifcxrnd = test + BIF_RAND_M;
+        runpnum(ctx->bifcxrun, (((ulong)ctx->bifcxrnd) % max) + 1);
+    }
+}
+
+/* 
+ *   case-insensitive substring matching 
+ */
+static char *bif_stristr(const char *s1, const char *s2)
+{
+    size_t s1len;
+    size_t s2len;
+
+    /* scan for a match */
+    for (s1len = strlen(s1), s2len = strlen(s2) ; s1len >= s2len ;
+         ++s1, --s1len)
+    {
+        /* if this is a match, return this substring */
+        if (memicmp(s1, s2, s2len) == 0)
+            return (char *)s1;
+    }
+
+    return 0;
+}
+
+/*
+ *   askfile flags 
+ */
+#define BIF_ASKF_EXT_RET  1                        /* extended return codes */
+
+/* 
+ *   askfile 
+ */
+void bifask(bifcxdef *ctx, int argc)
+{
+    uchar *prompt;
+    char   buf[OSFNMAX + 2];
+    char   pbuf[128];
+    int    err;
+    int    prompt_type;
+    int    file_type;
+    ulong  flags;
+
+    /* make sure we have an acceptable number of arguments */
+    if (argc != 1 && argc != 3 && argc != 4)
+        runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+    /* get the first argument - the prompt string */
+    prompt = runpopstr(ctx->bifcxrun);
+    bifcstr(ctx, pbuf, (size_t)sizeof(pbuf), prompt);
+
+    /* presume we will have no flags */
+    flags = 0;
+
+    /* if we have the prompt type and file type parameters, get them */
+    if (argc >= 3)
+    {
+        /* get the prompt-type and the file-type arguments */
+        prompt_type = (int)runpopnum(ctx->bifcxrun);
+        file_type = (int)runpopnum(ctx->bifcxrun);
+
+        /* if we have a fourth argument, it's the flags */
+        if (argc == 4)
+            flags = runpopnum(ctx->bifcxrun);
+    }
+    else
+    {
+        static const char *save_strs[] =
+        {
+            "save",
+            "write",
+            0
+        };
+        static const char *game_strs[] =
+        {
+            "restore",
+            "game",
+            0
+        };
+        const char **sp;
+
+        /* 
+         *   No prompt type or file type were specified.  Try to infer the
+         *   dialog type and file type from the text of the prompt.  (This
+         *   is mostly to support older games, in particular those based
+         *   on older versions of adv.t, since newer games should always
+         *   provide explicit values for the file type and dialog type.
+         *   We are thus inferring the types based on the prompt strings
+         *   that older adv.t's used when calling askfile.)
+         *   
+         *   If the prompt contains any substring such as "save" or
+         *   "write", specify that we're saving; otherwise, assume that
+         *   we're opening an existing file for reading.
+         *   
+         *   If the prompt contains the substrings "restore" AND "game",
+         *   assume that we're opening a game file; otherwise, don't make
+         *   any assumptions, and use the "unknown" file type.  
+         */
+
+        /* presume we're going to open a saved-game file */
+        prompt_type = OS_AFP_OPEN;
+        file_type = OSFTSAVE;
+
+        /* look for any one of the "save" substrings */
+        for (sp = save_strs ; *sp != 0 ; ++sp)
+        {
+            /* check to see if this substring matches */
+            if (bif_stristr(pbuf, *sp))
+            {
+                /* found it - use the "save" prompt */
+                prompt_type = OS_AFP_SAVE;
+
+                /* no need to look any further */
+                break;
+            }
+        }
+
+        /* 
+         *   look for *all* of the "restore game" strings - if we fail to
+         *   find any of them, be conservative and make no assumptions
+         *   about the file type 
+         */
+        for (sp = game_strs ; *sp != 0 ; ++sp)
+        {
+            if (bif_stristr(pbuf, *sp) == 0)
+            {
+                /* 
+                 *   this one doesn't match - don't make assumptions about
+                 *   the file type 
+                 */
+                file_type = OSFTUNK;
+
+                /* no need to look any further */
+                break;
+            }
+        }
+
+        /* check for a transcript */
+        if (file_type == OSFTUNK
+            && prompt_type == OS_AFP_SAVE
+            && bif_stristr(pbuf, "script") != 0)
+        {
+            /* looks like a log file */
+            file_type = OSFTLOG;
+        }
+    }
+
+    /* ask for a file */
+    err = tio_askfile(pbuf, buf, (int)sizeof(buf), prompt_type, (os_filetype_t)file_type);
+
+    /* 
+     *   if the caller requested extended return codes, return a list
+     *   containing the return code as the first element and, if
+     *   successful, the string as the second element 
+     */
+    if ((flags & BIF_ASKF_EXT_RET) != 0)
+    {
+        ushort len;
+        runsdef val;
+        uchar *p;
+        
+        /* 
+         *   Allocate space for the starter list - if we have a string to
+         *   return, just allocate space for the number element for now;
+         *   otherwise, allocate space for the number plus a nil second
+         *   element (one byte).
+         */
+        len = 2 + (1 + 4);
+        if (err != OS_AFE_SUCCESS)
+            ++len;
+
+        /* allocate the space */
+        runhres(ctx->bifcxrun, len, 0);
+
+        /* set up our list pointer */
+        val.runstyp = DAT_LIST;
+        val.runsv.runsvstr = p = ctx->bifcxrun->runcxhp;
+
+        /* write the length prefix */
+        oswp2(p, len);
+        p += 2;
+
+        /* write the return code as the first element */
+        *p++ = DAT_NUMBER;
+        oswp4s(p, err);
+        p += 4;
+
+        /* write the 'nil' second element if there's an error */
+        if (err != OS_AFE_SUCCESS)
+            *p++ = DAT_NIL;
+
+        /* commit the list's memory */
+        ctx->bifcxrun->runcxhp = p;
+
+        /* push the list */
+        runrepush(ctx->bifcxrun, &val);
+
+        /* if we were successful, add the string to the list */
+        if (err == OS_AFE_SUCCESS)
+        {
+            runsdef val2;
+            
+            /* push the string value, converting to our string format */
+            runpushcstr(ctx->bifcxrun, buf, strlen(buf), 1);
+
+            /* add it to the list already on the stack */
+            runpop(ctx->bifcxrun, &val2);
+            runpop(ctx->bifcxrun, &val);
+            runadd(ctx->bifcxrun, &val, &val2, 2);
+
+            /* re-push the result */
+            runrepush(ctx->bifcxrun, &val);
+        }
+    }
+    else
+    {
+        /* 
+         *   use the traditional return codes - if askfile failed, return
+         *   nil; otherwise, return the filename 
+         */
+        if (err)
+            runpnil(ctx->bifcxrun);
+        else
+            runpushcstr(ctx->bifcxrun, buf, strlen(buf), 0);
+    }
+}
+
+/* setscore */
+void bifssc(bifcxdef *ctx, int argc)
+{
+    int s1, s2;
+
+    /* optional new way - string argument */
+    if (argc == 1 && runtostyp(ctx->bifcxrun) == DAT_SSTRING)
+    {
+        char   buf[80];
+        uchar *p;
+        
+        p = runpopstr(ctx->bifcxrun);
+        bifcstr(ctx, buf, (size_t)sizeof(buf), p);
+        tiostrsc(ctx->bifcxtio, buf);
+    }
+    else
+    {
+        /* old way - two numeric arguments (displays: x/y) */
+        bifcntargs(ctx, 2, argc);
+        s1 = runpopnum(ctx->bifcxrun);
+        s2 = runpopnum(ctx->bifcxrun);
+        tioscore(ctx->bifcxtio, s1, s2);
+    }
+}
+
+/* substr */
+void bifsub(bifcxdef *ctx, int argc)
+{
+    uchar   *p;
+    int      ofs;
+    int      asklen;
+    int      outlen;
+    int      len;
+
+    bifcntargs(ctx, 3, argc);
+
+    /* get the string argument */
+    bifchkarg(ctx, DAT_SSTRING);
+    p = runpopstr(ctx->bifcxrun);
+    len = osrp2(p) - 2;
+    p += 2;
+    
+    /* get the offset argument */
+    bifchkarg(ctx, DAT_NUMBER);
+    ofs = runpopnum(ctx->bifcxrun);
+    if (ofs < 1) runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "substr");
+    
+    /* get the length argument */
+    bifchkarg(ctx, DAT_NUMBER);
+    asklen = runpopnum(ctx->bifcxrun);
+    if (asklen < 0) runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "substr");
+
+    --ofs;          /* convert offset to a zero bias (user provided 1-bias) */
+    p += ofs;                           /* advance string pointer by offset */
+
+    if (ofs >= len)
+        outlen = 0;                         /* offset is past end of string */
+    else if (asklen > len - ofs)
+        outlen = len - ofs;                      /* just use rest of string */
+    else
+        outlen = asklen;                /* requested length can be provided */
+    
+    runpstr(ctx->bifcxrun, (char *)p, outlen, 3);
+}
+
+/* cvtstr - convert value to a string */
+void bifcvs(bifcxdef *ctx, int argc)
+{
+    char *p;
+    int   len;
+    char  buf[30];
+    
+    bifcntargs(ctx, 1, argc);
+    switch(runtostyp(ctx->bifcxrun))
+    {
+    case DAT_NIL:
+        p = "nil";
+        len = 3;
+        (void)runpoplog(ctx->bifcxrun);
+        break;
+        
+    case DAT_TRUE:
+        p = "true";
+        len = 4;
+        (void)runpoplog(ctx->bifcxrun);
+        break;
+        
+    case DAT_NUMBER:
+        sprintf(buf, "%ld", runpopnum(ctx->bifcxrun));
+        p = buf;
+        len = strlen(buf);
+        break;
+
+    case DAT_SSTRING:
+        /* leave the string value on the stack unchanged */
+        return;
+
+    default:
+        /* throw the RUNEXITOBJ error */
+        runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "cvtstr");
+    }
+    
+    runpstr(ctx->bifcxrun, p, len, 0);
+}
+
+/* cvtnum  - convert a value to a number */
+void bifcvn(bifcxdef *ctx, int argc)
+{
+    runsdef  val;
+    uchar   *p;
+    int      len;
+    int      typ;
+    long     acc;
+    int      neg;
+    
+    bifcntargs(ctx, 1, argc);
+    p = runpopstr(ctx->bifcxrun);
+    len = osrp2(p) - 2;
+    p += 2;
+    
+    if (len == 3 && !memcmp(p, "nil", (size_t)3))
+        typ = DAT_NIL;
+    else if (len == 4 && !memcmp(p, "true", (size_t)4))
+        typ = DAT_TRUE;
+    else
+    {
+        typ = DAT_NUMBER;
+        for ( ; len != 0 && t_isspace(*p) ; ++p, --len) ;
+        if (len != 0 && *p == '-')
+        {
+            neg = TRUE;
+            for (++p, --len ; len != 0 && t_isspace(*p) ; ++p, --len) ;
+        }
+        else neg = FALSE;
+
+        /* accumulate the number digit by digit */
+        for (acc = 0 ; len != 0 && Common::isDigit(*p) ; ++p, --len)
+            acc = (acc << 3) + (acc << 1) + ((*p) - '0');
+
+        if (neg) acc = -acc;
+        val.runsv.runsvnum = acc;
+    }
+    
+    runpush(ctx->bifcxrun, typ, &val);
+}
+
+/* general string conversion function */
+static void bifcvtstr(bifcxdef *ctx, void (*cvtfn)(uchar *, int), int argc)
+{
+    uchar   *p;
+    int      len;
+    runsdef  val;
+    runsdef  stkval;
+    
+    bifcntargs(ctx, 1, argc);
+    bifchkarg(ctx, DAT_SSTRING);
+    
+    p = runpopstr(ctx->bifcxrun);
+    stkval.runstyp = DAT_SSTRING;
+    stkval.runsv.runsvstr = p;
+    len = osrp2(p);
+    
+    /* allocate space in heap for the string and convert */
+    runhres1(ctx->bifcxrun, len, 1, &stkval);
+    p = stkval.runsv.runsvstr;
+    memcpy(ctx->bifcxrun->runcxhp, p, (size_t)len);
+    (*cvtfn)(ctx->bifcxrun->runcxhp + 2, len - 2);
+    
+    val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+    val.runstyp = DAT_SSTRING;
+    ctx->bifcxrun->runcxhp += len;
+    runrepush(ctx->bifcxrun, &val);
+}
+
+/* routine to convert a counted-length string to uppercase */
+static void bifstrupr(uchar *str, int len)
+{
+    for ( ; len ; --len, ++str)
+    {
+        if (*str == '\\' && len > 1)
+            --len, ++str;
+        else if (Common::isLower(*str))
+            *str = toupper(*str);
+    }
+}
+
+/* upper */
+void bifupr(bifcxdef *ctx, int argc)
+{
+    bifcvtstr(ctx, bifstrupr, argc);
+}
+
+/* convert a counted-length string to lowercase */
+static void bifstrlwr(uchar *str, int len)
+{
+    for ( ; len ; --len, ++str)
+    {
+        if (*str == '\\' && len > 1)
+            --len, ++str;
+        else if (Common::isUpper(*str))
+            *str = tolower(*str);
+    }
+}
+
+/* lower */
+void biflwr(bifcxdef *ctx, int argc)
+{
+    bifcvtstr(ctx, bifstrlwr, argc);
+}
+
+/* internal check to determine if object is of a class */
+int bifinh(voccxdef *voc, vocidef *v, objnum cls)
+{
+    int     i;
+    objnum *sc;
+
+    if (!v) return(FALSE);
+    for (i = v->vocinsc, sc = v->vocisc ; i ; ++sc, --i)
+    {
+        if (*sc == cls
+            || bifinh(voc, vocinh(voc, *sc), cls))
+            return(TRUE);
+    }
+    return(FALSE);
+}
+
+/* isclass(obj, cls) */
+void bifisc(bifcxdef *ctx, int argc)
+{
+    objnum    obj;
+    objnum    cls;
+    runsdef   val;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+    
+    bifcntargs(ctx, 2, argc);
+
+    /* if checking for nil, return nil */
+    if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+    {
+        rundisc(ctx->bifcxrun);
+        rundisc(ctx->bifcxrun);
+        runpnil(ctx->bifcxrun);
+        return;
+    }
+
+    /* get the arguments:  object, class */
+    obj = runpopobj(ctx->bifcxrun);
+    cls = runpopobj(ctx->bifcxrun);
+
+    /* return the result from bifinh() */
+    runpush(ctx->bifcxrun, runclog(bifinh(voc, vocinh(voc, obj), cls)), &val);
+}
+
+/* firstsc(obj) - get the first superclass of an object */
+void bif1sc(bifcxdef *ctx, int argc)
+{
+    objnum obj;
+    objnum sc;
+
+    bifcntargs(ctx, 1, argc);
+    obj = runpopobj(ctx->bifcxrun);
+    sc = objget1sc(ctx->bifcxrun->runcxmem, obj);
+    runpobj(ctx->bifcxrun, sc);
+}
+
+/* firstobj */
+void biffob(bifcxdef *ctx, int argc)
+{
+    vocidef ***vpg;
+    vocidef  **v;
+    objnum     obj;
+    int        i;
+    int        j;
+    objnum     cls = 0;
+    voccxdef  *voc = ctx->bifcxrun->runcxvoc;
+
+    /* get class to search for, if one is specified */
+    if (argc == 0)
+        cls = MCMONINV;
+    else if (argc == 1)
+        cls = runpopobj(ctx->bifcxrun);
+    else
+        runsig(ctx->bifcxrun, ERR_BIFARGC);
+    
+    for (vpg = voc->voccxinh, i = 0 ; i < VOCINHMAX ; ++vpg, ++i)
+    {
+        if (!*vpg) continue;
+        for (v = *vpg, obj = (i << 8), j = 0 ; j < 256 ; ++v, ++obj, ++j)
+        {
+            if (!*v || ((*v)->vociflg & VOCIFCLASS)
+                || (cls != MCMONINV && !bifinh(voc, *v, cls)))
+                continue;
+            
+            /* this is an object we can use - push it */
+            runpobj(ctx->bifcxrun, obj);
+            return;
+        }
+    }
+    
+    /* no objects found at all - return nil */
+    runpnil(ctx->bifcxrun);
+}
+
+/* nextobj */
+void bifnob(bifcxdef *ctx, int argc)
+{
+    objnum     prv;
+    vocidef ***vpg;
+    vocidef  **v;
+    objnum     obj;
+    int        i;
+    int        j;
+    objnum     cls = 0;
+    voccxdef  *voc = ctx->bifcxrun->runcxvoc;
+
+    /* get last position in search */
+    prv = runpopobj(ctx->bifcxrun);
+    
+    /* get class to search for, if one is specified */
+    if (argc == 1)
+        cls = MCMONINV;
+    else if (argc == 2)
+        cls = runpopobj(ctx->bifcxrun);
+    else
+        runsig(ctx->bifcxrun, ERR_BIFARGC);
+    
+    /* start at previous object plus 1 */
+    i = (prv >> 8);
+    vpg = voc->voccxinh + i;
+    j = (prv & 255);
+    obj = prv;
+    v = (*vpg) + j;
+    
+    for (;;)
+    {
+        ++j;
+        ++obj;
+        ++v;
+        if (j == 256)
+        {
+            j = 0;
+            ++i;
+            ++vpg;
+            if (!*vpg)
+            {
+                obj += 255;
+                j += 255;
+                continue;
+            }
+            v = (*vpg);
+        }
+        if (i >= VOCINHMAX)
+        {
+            runpnil(ctx->bifcxrun);
+            return;
+        }
+        
+        if (!*v || ((*v)->vociflg & VOCIFCLASS)
+            || (cls != MCMONINV && !bifinh(voc, *v, cls)))
+            continue;
+            
+        /* this is an object we can use - push it */
+        runpobj(ctx->bifcxrun, obj);
+        return;
+    }
+}
+
+/* setversion */
+void bifsvn(bifcxdef *ctx, int argc)
+{
+    bifcntargs(ctx, 1, argc);
+    (void)runpopstr(ctx->bifcxrun);
+    /* note - setversion doesn't do anything in v2; uses timestamp instead */
+}
+
+/* getarg */
+void bifarg(bifcxdef *ctx, int argc)
+{
+    int argnum;
+    
+    bifcntargs(ctx, 1, argc);
+    bifchkarg(ctx, DAT_NUMBER);
+    
+    /* get and verify argument number */
+    argnum = runpopnum(ctx->bifcxrun);
+    if (argnum < 1) runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "getarg");
+
+    runrepush(ctx->bifcxrun, ctx->bifcxrun->runcxbp - argnum - 1);
+}
+
+/* datatype */
+void biftyp(bifcxdef *ctx, int argc)
+{
+    runsdef val;
+    
+    bifcntargs(ctx, 1, argc);
+    
+    /* get whatever it is, and push the type */
+    runpop(ctx->bifcxrun, &val);
+    val.runsv.runsvnum = val.runstyp;          /* new value is the datatype */
+    runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* undo */
+void bifund(bifcxdef *ctx, int argc)
+{
+    objucxdef *ucx = ctx->bifcxrun->runcxvoc->voccxundo;
+    mcmcxdef  *mcx = ctx->bifcxrun->runcxmem;
+    errcxdef  *ec  = ctx->bifcxerr;
+    int        err;
+    int        undone;
+    runsdef    val;
+
+    bifcntargs(ctx, 0, argc);                               /* no arguments */
+
+    ERRBEGIN(ec)
+        if (ucx)
+        {
+            objundo(mcx, ucx);         /* try to undo to previous savepoint */
+            undone = TRUE;                       /* looks like we succeeded */
+        }
+        else
+            undone = FALSE;                  /* no undo context; can't undo */
+    ERRCATCH(ec, err)
+        if (err == ERR_NOUNDO || err == ERR_ICUNDO)
+            undone = FALSE;
+        else
+            errrse(ec);            /* don't know how to handle other errors */
+    ERREND(ec)
+
+    /* return a value indicating whether the undo operation succeeded */
+    runpush(ctx->bifcxrun, runclog(undone), &val);
+
+    /* note that the rest of the command line is to be ignored */
+    ctx->bifcxrun->runcxvoc->voccxflg |= VOCCXFCLEAR;
+}
+
+/* flags for defined() function */
+#define BIFDEF_DEFINED_ANY           1
+#define BIFDEF_DEFINED_DIRECTLY      2
+#define BIFDEF_DEFINED_INHERITS      3
+#define BIFDEF_DEFINED_GET_CLASS     4
+
+/* defined */
+void bifdef(bifcxdef *ctx, int argc)
+{
+    prpnum  prpn;
+    objnum  objn;
+    uint    ofs;
+    runsdef val;
+    objnum  def_objn;
+    int     flag;
+
+    /* get object and property arguments */
+    objn = runpopobj(ctx->bifcxrun);
+    prpn = runpopprp(ctx->bifcxrun);
+
+    /* if there's a flag argument, get it as well */
+    if (argc == 3)
+    {
+        /* get the flag */
+        flag = (int)runpopnum(ctx->bifcxrun);
+    }
+    else
+    {
+        /* check the argument count */
+        bifcntargs(ctx, 2, argc);
+
+        /* use the default flag value (DEFINES_OR_INHERITS) */
+        flag = BIFDEF_DEFINED_ANY;
+    }
+
+    /* get the offset of the property and the defining object */
+    ofs = objgetap(ctx->bifcxrun->runcxmem, objn, prpn, &def_objn, FALSE);
+
+    /* determine the type of information they want */
+    switch(flag)
+    {
+    case BIFDEF_DEFINED_ANY:
+        /* if the property is defined, return true, else return nil */
+        runpush(ctx->bifcxrun, runclog(ofs != 0), &val);
+        break;
+
+    case BIFDEF_DEFINED_DIRECTLY:
+        /* if the property is defined directly by the object, return true */
+        runpush(ctx->bifcxrun, runclog(ofs != 0 && def_objn == objn), &val);
+        break;
+        
+    case BIFDEF_DEFINED_INHERITS:
+        /* if the property is inherited, return true */
+        runpush(ctx->bifcxrun, runclog(ofs != 0 && def_objn != objn), &val);
+        break;
+
+    case BIFDEF_DEFINED_GET_CLASS:
+        /* if it's defined, return the defining object, otherwise nil */
+        if (ofs == 0)
+            runpnil(ctx->bifcxrun);
+        else
+            runpobj(ctx->bifcxrun, def_objn);
+        break;
+
+    default:
+        /* invalid flag value */
+        runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "defined");
+    }
+}
+
+/* proptype */
+void bifpty(bifcxdef *ctx, int argc)
+{
+    prpnum   prpn;
+    objnum   objn;
+    uint     ofs;
+    runsdef  val;
+    objnum   orn;
+    objdef  *objptr;
+    prpdef  *propptr;
+
+    bifcntargs(ctx, 2, argc);
+    
+    /* get offset of obj.prop */
+    objn = runpopobj(ctx->bifcxrun);
+    prpn = runpopprp(ctx->bifcxrun);
+    ofs = objgetap(ctx->bifcxrun->runcxmem, objn, prpn, &orn, FALSE);
+    
+    if (ofs)
+    {
+        /* lock the object, read the prpdef, and unlock it */
+        objptr = (objdef *)mcmlck(ctx->bifcxrun->runcxmem, (mcmon)orn);
+        propptr = objofsp(objptr, ofs);
+        val.runsv.runsvnum = prptype(propptr);
+        mcmunlck(ctx->bifcxrun->runcxmem, (mcmon)orn);
+    }
+    else
+    {
+        /* property is not defined by object - indicate that type is nil */
+        val.runsv.runsvnum = DAT_NIL;
+    }
+    
+    /* special case:  DAT_DEMAND -> DAT_LIST (for contents properties) */
+    if (val.runsv.runsvnum == DAT_DEMAND)
+        val.runsv.runsvnum = DAT_LIST;
+
+    /* return the property type as a number */
+    runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+}
+
+/* outhide */
+void bifoph(bifcxdef *ctx, int argc)
+{
+    runsdef val;
+    int     hidden, output_occurred;
+
+    bifcntargs(ctx, 1, argc);
+    outstat(&hidden, &output_occurred);
+    if (runtostyp(ctx->bifcxrun) == DAT_TRUE)
+    {
+        /* throw away the flag */
+        rundisc(ctx->bifcxrun);
+        
+        /* figure out appropriate return value */
+        if (!hidden)
+            val.runsv.runsvnum = 0;
+        else if (!output_occurred)
+            val.runsv.runsvnum = 1;
+        else
+            val.runsv.runsvnum = 2;
+        runpush(ctx->bifcxrun, DAT_NUMBER, &val);
+
+        /* actually hide the output, resetting count flag */
+        outhide();
+    }
+    else if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+    {
+        /* throw away the flag */
+        rundisc(ctx->bifcxrun);
+
+        /* show output, returning status */
+        runpush(ctx->bifcxrun, runclog(outshow()), &val);
+    }
+    else if (runtostyp(ctx->bifcxrun) == DAT_NUMBER)
+    {
+        int n = runpopnum(ctx->bifcxrun);
+
+        if (n == 0)
+        {
+            /* output was not hidden - show output and return status */
+            runpush(ctx->bifcxrun, runclog(outshow()), &val);
+        }
+        else if (n == 1)
+        {
+            /*
+             *   Output was hidden, but no output had occurred yet.
+             *   Leave output hidden and return whether any output has
+             *   occurred.
+             */
+            runpush(ctx->bifcxrun, runclog(output_occurred), &val);
+        }
+        else if (n == 2)
+        {
+            /*
+             *   Output was hidden, and output had already occurred.  If
+             *   more output has occurred, return true, else return nil.
+             *   In either case, set the output_occurred flag back to
+             *   true, since it was true before the outhide(true).  
+             */
+            runpush(ctx->bifcxrun, runclog(output_occurred), &val);
+            outsethidden();
+        }
+        else
+            errsig1(ctx->bifcxerr, ERR_INVVBIF, ERRTSTR, "outhide");
+    }
+    else
+        errsig(ctx->bifcxerr, ERR_REQNUM);
+}
+
+/* put a numeric value in a list */
+static uchar *bifputnum(uchar *lstp, uint val)
+{
+    *lstp++ = DAT_NUMBER;
+    oswp4s(lstp, (long)val);
+    return(lstp + 4);
+}
+
+/* gettime */
+void biftim(bifcxdef *ctx, int argc) {
+	TimeDate tm;
+	uint timer;
+    uchar      ret[80];
+    uchar     *p;
+    runsdef    val;
+    int        typ;
+	int tm_yday;
+	const int MONTH_DAYS[11] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
+
+    if (argc == 1)
+    {
+        /* get the time type */
+        typ = (int)runpopnum(ctx->bifcxrun);
+    }
+    else
+    {
+        /* make sure no arguments are specified */
+        bifcntargs(ctx, 0, argc);
+
+        /* use the default time type */
+        typ = 1;
+    }
+
+    switch(typ)
+    {
+    case 1:
+        /* 
+         *   default information format - list format with current system
+         *   time and date 
+         */
+
+        /* make sure the time zone is set up properly */
+        os_tzset();
+        
+        /* get the local time information */
+		g_system->getTimeAndDate(tm);
+        
+        /* adjust values for return format */
+        tm.tm_wday++;
+
+		// Get number of days since start of year
+		tm_yday = tm.tm_mday;
+		for (int monthNum = 1; monthNum < tm.tm_mon; ++monthNum) {
+			int daysInMonth = MONTH_DAYS[monthNum - 1];
+			if (monthNum == 2)
+				daysInMonth = (tm.tm_year % 4) == 0 && (((tm.tm_year % 100) != 0) || ((tm.tm_year % 400) == 0)) ? 29 : 28;
+
+			tm_yday += daysInMonth;
+		}
+
+		// TODO: Convert dae/tme to Unix style local time
+		timer = 0;
+        
+        /* build return list value */
+        oswp2(ret, 47);
+        p = ret + 2;
+        p = bifputnum(p, tm.tm_year);
+        p = bifputnum(p, tm.tm_mon);
+        p = bifputnum(p, tm.tm_mday);
+        p = bifputnum(p, tm.tm_wday);
+        p = bifputnum(p, tm_yday);
+        p = bifputnum(p, tm.tm_hour);
+        p = bifputnum(p, tm.tm_min);
+        p = bifputnum(p, tm.tm_sec);
+        *p++ = DAT_NUMBER;
+        oswp4s(p, (long)timer);
+        
+        val.runstyp = DAT_LIST;
+        val.runsv.runsvstr = ret;
+        runpush(ctx->bifcxrun, DAT_LIST, &val);
+        break;
+
+    case 2:
+        /*
+         *   High-precision system timer value - returns the system time
+         *   in milliseconds, relative to an arbitrary zero point 
+         */
+        runpnum(ctx->bifcxrun, os_get_sys_clock_ms());
+        break;
+
+    default:
+        /* other types are invalid */
+        runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "gettime");
+        break;
+    }
+}
+
+/* getfuse */
+void bifgfu(bifcxdef *ctx, int argc)
+{
+    vocddef  *daem;
+    objnum    func;
+    runsdef   val;
+    runcxdef *rcx = ctx->bifcxrun;
+    int       slots;
+    prpnum    prop;
+    voccxdef *vcx = ctx->bifcxrun->runcxvoc;
+
+    bifcntargs(ctx, 2, argc);
+ 
+    if (runtostyp(rcx) == DAT_FNADDR)
+    {
+        /* check on a setfuse()-style fuse: get fnaddr, parm */
+        func = runpopfn(rcx);
+        runpop(rcx, &val);
+
+        for (slots = vcx->voccxfuc, daem = vcx->voccxfus ;
+             slots ; ++daem, --slots)
+        {
+            if (daem->vocdfn == func
+                && daem->vocdarg.runstyp == val.runstyp
+                && !memcmp(&val.runsv, &daem->vocdarg.runsv,
+                           (size_t)datsiz(val.runstyp, &val.runsv)))
+                goto ret_num;
+        }
+    }
+    else
+    {
+        /* check on a notify()-style fuse: get object, &message */
+        func = runpopobj(rcx);
+        prop = runpopprp(rcx);
+
+        for (slots = vcx->voccxalc, daem = vcx->voccxalm ;
+             slots ; ++daem, --slots)
+        {
+            if (daem->vocdfn == func && daem->vocdprp == prop)
+                goto ret_num;
+        }
+    }
+    
+    /* didn't find anything - return nil */
+    runpush(rcx, DAT_NIL, &val);
+    return;
+    
+ret_num:
+    /* return current daem->vocdtim */
+    runpnum(rcx, (long)daem->vocdtim);
+    return;
+}
+
+/* runfuses */
+void bifruf(bifcxdef *ctx, int argc)
+{
+    int     ret;
+    runsdef val;
+
+    bifcntargs(ctx, 0, argc);
+    ret = exefuse(ctx->bifcxrun->runcxvoc, TRUE);
+    runpush(ctx->bifcxrun, runclog(ret), &val);
+}
+
+/* rundaemons */
+void bifrud(bifcxdef *ctx, int argc)
+{
+    bifcntargs(ctx, 0, argc);
+    exedaem(ctx->bifcxrun->runcxvoc);
+}
+
+/* intersect */
+void bifsct(bifcxdef *bifctx, int argc)
+{
+    runcxdef *ctx = bifctx->bifcxrun;
+    uchar    *l1;
+    uchar    *l2;
+    uchar    *l3;
+    uint      siz1;
+    uint      siz2;
+    uint      siz3;
+    uchar    *p;
+    uint      l;
+    uint      dsz1;
+    uint      dsz2;
+    runsdef   val;
+    runsdef   stk1, stk2;
+    
+    bifcntargs(bifctx, 2, argc);
+    l1 = runpoplst(ctx);
+    siz1 = osrp2(l1);
+    l2 = runpoplst(ctx);
+    siz2 = osrp2(l2);
+
+    /* make sure the first list is smaller - if not, switch them */
+    if (siz1 > siz2)
+        l3 = l1, l1 = l2, l2 = l3, siz3 = siz1, siz1 = siz2, siz2 = siz3;
+    
+    /* size of result is at most size of smaller list (which is now siz1) */
+    stk1.runstyp = stk2.runstyp = DAT_LIST;
+    stk1.runsv.runsvstr = l1;
+    stk2.runsv.runsvstr = l2;
+    runhres2(ctx, siz1, 2, &stk1, &stk2);
+    l1 = stk1.runsv.runsvstr;
+    l2 = stk2.runsv.runsvstr;
+    l3 = ctx->runcxhp + 2;
+
+    /* go through list1, and copy each element that is found in list2 */
+    for (l1 += 2, l2 += 2, siz1 -= 2, siz2 -= 2 ; siz1 ; lstadv(&l1, &siz1))
+    {
+        dsz1 = datsiz(*l1, l1 + 1) + 1;
+        for (l = siz2, p = l2 ; l ; lstadv(&p, &l))
+        {
+            dsz2 = datsiz(*p, p + 1) + 1;
+#ifndef AMIGA
+            if (dsz1 == dsz2 && !memcmp(l1, p, (size_t)dsz1))
+#else /* AMIGA */
+            if (!memcmp(l1, p, (size_t)dsz1) && (dsz1 == dsz2) )
+#endif /* AMIGA */
+            {
+                memcpy(l3, p, (size_t)dsz1);
+                l3 += dsz1;
+                break;
+            }
+        }
+    }
+    
+    /* set up return value, take it out of the heap, and push value */
+    val.runsv.runsvstr = ctx->runcxhp;
+    val.runstyp = DAT_LIST;
+    oswp2(ctx->runcxhp, (uint)(l3 - ctx->runcxhp));
+    ctx->runcxhp = l3;
+    runrepush(ctx, &val);
+}
+
+/*
+ *   Portable keystroke mappings.  We map the extended key codes to these
+ *   strings, so that the TADS code can access arrow keys and the like.  
+ */
+static char *ext_key_names[] =
+{
+    "[up]",                                                   /* CMD_UP - 1 */
+    "[down]",                                               /* CMD_DOWN - 2 */
+    "[right]",                                             /* CMD_RIGHT - 3 */
+    "[left]",                                               /* CMD_LEFT - 4 */
+    "[end]",                                                 /* CMD_END - 5 */
+    "[home]",                                               /* CMD_HOME - 6 */
+    "[del-eol]",                                            /* CMD_DEOL - 7 */
+    "[del-line]",                                           /* CMD_KILL - 8 */
+    "[del]",                                                 /* CMD_DEL - 9 */
+    "[scroll]",                                             /* CMD_SCR - 10 */
+    "[page up]",                                           /* CMD_PGUP - 11 */
+    "[page down]",                                         /* CMD_PGDN - 12 */
+    "[top]",                                                /* CMD_TOP - 13 */
+    "[bottom]",                                             /* CMD_BOT - 14 */
+    "[f1]",                                                  /* CMD_F1 - 15 */
+    "[f2]",                                                  /* CMD_F2 - 16 */
+    "[f3]",                                                  /* CMD_F3 - 17 */
+    "[f4]",                                                  /* CMD_F4 - 18 */
+    "[f5]",                                                  /* CMD_F5 - 19 */
+    "[f6]",                                                  /* CMD_F6 - 20 */
+    "[f7]",                                                  /* CMD_F7 - 21 */
+    "[f8]",                                                  /* CMD_F8 - 22 */
+    "[f9]",                                                  /* CMD_F9 - 23 */
+    "[f10]",                                                /* CMD_F10 - 24 */
+    "[?]",                                  /* invalid key - CMD_CHOME - 25 */
+    "[tab]",                                                /* CMD_TAB - 26 */
+    "[?]",                                   /* invalid key - shift-F2 - 27 */
+    "[?]",                                      /* not used (obsolete) - 28 */
+    "[word-left]",                                    /* CMD_WORD_LEFT - 29 */
+    "[word-right]",                                  /* CMD_WORD_RIGHT - 30 */
+    "[del-word]",                                      /* CMD_WORDKILL - 31 */
+    "[eof]",                                                /* CMD_EOF - 32 */
+    "[break]"                                             /* CMD_BREAK - 33 */
+};
+
+/*
+ *   Get the name of a keystroke.  Pass in the one or two characters
+ *   returned by os_getc(), and we'll fill in the buffer with the
+ *   inputkey() name of the keystroke.  Returns true if the key was valid,
+ *   false if not.  'c' is the first character returned by os_getc() for
+ *   the keystroke; if 'c' is zero, then 'extc' is the character returned
+ *   by the second call to os_getc() to get the CMD_xxx code for the
+ *   keystroke.
+ *   
+ *   The name buffer should be 20 characters long - this will ensure that
+ *   any name will fit.  
+ *   
+ *   For ordinary, printable characters, we'll simply return the
+ *   character; the letter 'a', for example, is returned as the string "a".
+ *   
+ *   For extended keys, we'll look up the CMD_xxx code and return the name
+ *   of the command, enclosed in square brackets; see the ext_key_names
+ *   table for the mappings.  The left-arrow cursor key, for example,
+ *   returns "[left]".
+ *   
+ *   For control characters, we'll generate a name like "[ctrl-a]", except
+ *   for the following characters:
+ *   
+ *.  ascii 10 returns "\n"
+ *.  ascii 13 returns "\n"
+ *.  ascii 9 returns "\t"
+ *.  ascii 8 returns "[bksp]" 
+ */
+static int get_ext_key_name(char *namebuf, int c, int extc)
+{
+    /* if it's a control character, translate it */
+    if (c >= 1 && c <= 27)
+    {
+        switch(c)
+        {
+        case 10:
+        case 13:
+            /* return '\n' for LF and CR characters */
+            strcpy(namebuf, "\\n");
+            return TRUE;
+
+        case 9:
+            /* return '\t' for TAB characters */
+            strcpy(namebuf, "\\t");
+            return TRUE;
+
+        case 8:
+            /* return '[bksp]' for backspace characters */
+            strcpy(namebuf, "[bksp]");
+            return TRUE;
+
+        case 27:
+            /* return '[esc]' for the escape key */
+            strcpy(namebuf, "[esc]");
+            return TRUE;
+
+        default:
+            /* return '[ctrl-X]' for other control characters */
+            strcpy(namebuf, "[ctrl-X]");
+            namebuf[6] = (char)(c + 'a' - 1);
+            return TRUE;
+        }
+    }
+
+    /* if it's any other non-extended key, return it as-is */
+    if (c != 0)
+    {
+        namebuf[0] = c;
+        namebuf[1] = '\0';
+        return TRUE;
+    }
+    
+    /* if it's in the key name array, use the array entry */
+    if (extc >= 1
+        && extc <= (int)(sizeof(ext_key_names)/sizeof(ext_key_names[0])))
+    {
+        /* use the array name */
+        strcpy(namebuf, ext_key_names[extc - 1]);
+        return TRUE;
+    }
+
+    /* if it's in the ALT key range, generate an ALT key name */
+    if (extc >= CMD_ALT && extc <= CMD_ALT + 25)
+    {
+        /* generate an ALT key name */
+        strcpy(namebuf, "[alt-X]");
+        namebuf[5] = (char)(extc - CMD_ALT + 'a');
+        return TRUE;
+    }
+
+    /* it's not a valid key - use '[?]' as the name */
+    strcpy(namebuf, "[?]");
+    return FALSE;
+}
+
+
+/* inputkey */
+void bifink(bifcxdef *ctx, int argc)
+{
+    int    c;
+    int    extc;
+    char   str[20];
+    size_t len;
+    
+    bifcntargs(ctx, 0, argc);
+    tioflushn(ctx->bifcxtio, 0);
+
+    /* get a key */
+    c = os_getc_raw();
+
+    /* if it's extended, get the second part of the extended sequence */
+    extc = (c == 0 ? os_getc_raw() : 0);
+
+    /* map the extended key name */
+    get_ext_key_name(str, c, extc);
+
+    /* get the length of the name */
+    len = strlen(str);
+
+    /* reset the [more] counter */
+    outreset();
+
+    /* return the string, translating escapes */
+    runpstr(ctx->bifcxrun, str, len, 0);
+}
+
+/* get direct/indirect object word list */
+void bifwrd(bifcxdef *ctx, int argc)
+{
+    int       ob;
+    vocoldef *v;
+    uchar     buf[128];
+    uchar    *dst;
+    uchar    *src;
+    uint      len;
+    runsdef   val;
+    
+    bifcntargs(ctx, 1, argc);
+
+    /* figure out what word list to get */
+    ob = runpopnum(ctx->bifcxrun);
+    switch(ob)
+    {
+    case 1:
+        v = ctx->bifcxrun->runcxvoc->voccxdobj;
+        break;
+
+    case 2:
+        v = ctx->bifcxrun->runcxvoc->voccxiobj;
+        break;
+
+    default:
+        runpnil(ctx->bifcxrun);
+        return;
+    }
+
+    /* now build a list of strings from the words, if there are any */
+    if (v != 0 && voclistlen(v) != 0 && v->vocolfst != 0 && v->vocollst != 0)
+    {
+        for (dst = buf + 2, src = (uchar *)v->vocolfst ;
+             src <= (uchar *)v->vocollst ; src += len + 1)
+        {
+            *dst++ = DAT_SSTRING;
+            len = strlen((char *)src);
+            oswp2(dst, len + 2);
+            strcpy((char *)dst + 2, (char *)src);
+            dst += len + 2;
+        }
+    }
+    else
+        dst = buf + 2;
+
+    /* finish setting up the list length and return it */
+    len = dst - buf;
+    oswp2(buf, len);
+    val.runsv.runsvstr = buf;
+    val.runstyp = DAT_LIST;
+    runpush(ctx->bifcxrun, DAT_LIST, &val);
+}
+
+/* add a vocabulary word to an object */
+void bifadw(bifcxdef *ctx, int argc)
+{
+    uchar    *wrd;
+    objnum    objn;
+    prpnum    prpn;
+    vocidef  *voci;
+    int       classflg;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+    bifcntargs(ctx, 3, argc);
+
+    /* get the arguments */
+    objn = runpopobj(ctx->bifcxrun);
+    prpn = runpopprp(ctx->bifcxrun);
+    wrd = runpopstr(ctx->bifcxrun);
+
+    /* make sure the property is a valid part of speech property */
+    if (!prpisvoc(prpn))
+        runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "addword");
+
+    /* get the vocidef for the object, and see if it's a class object */
+    voci = vocinh(voc, objn);
+
+    classflg = VOCFNEW;
+    if (voci->vociflg & VOCIFCLASS) classflg |= VOCFCLASS;
+
+    /* add the word */
+    vocadd(voc, prpn, objn, classflg, (char *)wrd);
+
+    /* generate undo for the operation */
+    vocdusave_addwrd(voc, objn, prpn, classflg, (char *)wrd);
+}
+
+/* delete a vocabulary word from an object */
+void bifdlw(bifcxdef *ctx, int argc)
+{
+    uchar    *wrd;
+    objnum    objn;
+    prpnum    prpn;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+
+    bifcntargs(ctx, 3, argc);
+
+    /* get the arguments */
+    objn = runpopobj(ctx->bifcxrun);
+    prpn = runpopprp(ctx->bifcxrun);
+    wrd = runpopstr(ctx->bifcxrun);
+
+    /* make sure the property is a valid part of speech property */
+    if (!prpisvoc(prpn))
+        runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "delword");
+
+    /* delete the word */
+    vocdel1(voc, objn, (char *)wrd, prpn, FALSE, FALSE, TRUE);
+}
+
+/* callback context for word list builder */
+struct bifgtw_cb_ctx
+{
+    uchar *p;
+    int    typ;
+};
+
+/* callback for word list builder */
+static void bifgtw_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+    struct bifgtw_cb_ctx *ctx = (struct bifgtw_cb_ctx *)ctx0;
+
+    /* ignore deleted objects */
+    if (vocw->vocwflg & VOCFDEL)
+        return;
+
+    /* ignore objects of the inappropriate type */
+    if (vocw->vocwtyp != ctx->typ)
+        return;
+    
+    /* the datatype is string */
+    *ctx->p = DAT_SSTRING;
+
+    /* copy the first word */
+    memcpy(ctx->p + 3, voc->voctxt, (size_t)voc->voclen);
+
+    /* if there are two words, add a space and the second word */
+    if (voc->vocln2)
+    {
+        *(ctx->p + 3 + voc->voclen) = ' ';
+        memcpy(ctx->p + 4 + voc->voclen, voc->voctxt + voc->voclen,
+               (size_t)voc->vocln2);
+        oswp2(ctx->p + 1, voc->voclen + voc->vocln2 + 3);
+        ctx->p += voc->voclen + voc->vocln2 + 4;
+    }
+    else
+    {
+        oswp2(ctx->p + 1, voc->voclen+2);
+        ctx->p += voc->voclen + 3;
+    }
+}
+
+/* get the list of words for an object for a particular part of speech */
+void bifgtw(bifcxdef *ctx, int argc)
+{
+    objnum    objn;
+    prpnum    prpn;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+    int       cnt;
+    int       siz;
+    runsdef   val;
+    struct bifgtw_cb_ctx fnctx;
+
+    bifcntargs(ctx, 2, argc);
+
+    /* get the arguments */
+    objn = runpopobj(ctx->bifcxrun);
+    prpn = runpopprp(ctx->bifcxrun);
+    
+    /* make sure the property is a valid part of speech property */
+    if (!prpisvoc(prpn))
+        runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "delword");
+
+    /* get the size of the list we'll need to build */
+    voc_count(voc, objn, prpn, &cnt, &siz);
+
+    /*
+     *   calculate how much space it will take to make a list out of all
+     *   these words: 2 bytes for the list length header; plus, for each
+     *   entry, 1 byte for the type header, 2 bytes for the string size
+     *   header, and possibly one extra byte for the two-word separator --
+     *   a total of 4 bytes extra per word.  
+     */
+    siz += 2 + 4*cnt;
+
+    /* reserve the space */
+    runhres(ctx->bifcxrun, siz, 0);
+
+    /* set up our callback context, and build the list */
+    fnctx.p = ctx->bifcxrun->runcxhp + 2;
+    fnctx.typ = prpn;
+    voc_iterate(voc, objn, bifgtw_cb, &fnctx);
+
+    /* set up the return value */
+    val.runstyp = DAT_LIST;
+    val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+    /* write the list length, and advance past the space we used */
+    oswp2(ctx->bifcxrun->runcxhp, fnctx.p - ctx->bifcxrun->runcxhp);
+    ctx->bifcxrun->runcxhp = fnctx.p;
+
+    /* return the list */
+    runrepush(ctx->bifcxrun, &val);
+}
+
+/* verbinfo service routine - add an object to the output list */
+static uchar *bifvin_putprpn(uchar *p, prpnum prpn)
+{
+    *p++ = DAT_PROPNUM;
+    oswp2(p, prpn);
+    return p + 2;
+}
+
+/* verbinfo */
+void bifvin(bifcxdef *ctx, int argc)
+{
+    objnum  verb;
+    objnum  prep;
+    uchar   tplbuf[VOCTPL2SIZ];
+    int     newstyle;
+
+    /* get the verb */
+    verb = runpopobj(ctx->bifcxrun);
+    
+    /* check for the presence of a preposition */
+    if (argc == 1)
+    {
+        /* no preposition */
+        prep = MCMONINV;
+    }
+    else
+    {
+        /* the second argument is the preposition */
+        bifcntargs(ctx, 2, argc);
+        prep = runpopobj(ctx->bifcxrun);
+    }
+
+    /* look up the template */
+    if (voctplfnd(ctx->bifcxrun->runcxvoc, verb, prep, tplbuf, &newstyle))
+    {
+        prpnum   prp_do, prp_verdo, prp_io, prp_verio;
+        int      flg_dis_do;
+        ushort   siz;
+        uchar   *p;
+        runsdef  val;
+
+        /* get the information from the template */
+        prp_do     = voctpldo(tplbuf);
+        prp_verdo  = voctplvd(tplbuf);
+        prp_io     = voctplio(tplbuf);
+        prp_verio  = voctplvi(tplbuf);
+        flg_dis_do = (voctplflg(tplbuf) & VOCTPLFLG_DOBJ_FIRST) != 0;
+
+        /*
+         *   figure space for the return value: if there's a prep, three
+         *   property pointers plus a boolean, otherwise just two property
+         *   pointers 
+         */
+        siz = 2 + 2*(2+1);
+        if (prep != MCMONINV)
+            siz += (2+1) + 1;
+
+        /* reserve the space */
+        runhres(ctx->bifcxrun, siz, 0);
+
+        /* build the output list */
+        p = ctx->bifcxrun->runcxhp;
+        oswp2(p, siz);
+        p += 2;
+
+        p = bifvin_putprpn(p, prp_verdo);
+        if (prep == MCMONINV)
+        {
+            p = bifvin_putprpn(p, prp_do);
+        }
+        else
+        {
+            p = bifvin_putprpn(p, prp_verio);
+            p = bifvin_putprpn(p, prp_io);
+            *p++ = runclog(flg_dis_do);
+        }
+
+        /* build the return value */
+        val.runstyp = DAT_LIST;
+        val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+        /* consume the space */
+        ctx->bifcxrun->runcxhp += siz;
+
+        /* return the list */
+        runrepush(ctx->bifcxrun, &val);
+    }
+    else
+    {
+        /* no template for this verb - return nil */
+        runpnil(ctx->bifcxrun);
+    }
+}
+
+
+/* clearscreen */
+void bifcls(bifcxdef *ctx, int argc)
+{
+    /* this takes no arguments */
+    bifcntargs(ctx, 0, argc);
+
+    /* flush any pending output */
+    tioflushn(ctx->bifcxtio, 0);
+    
+    /* clear the screen */
+    oscls();
+}
+
+/*
+ *   File operations 
+ */
+
+/*
+ *   fopen(file, mode).
+ *   
+ *   Operations are allowed only if they conform to the current I/O safety
+ *   level.  The safety level can be set by the user on the command line
+ *   when running the game, and some implementations may allow the setting
+ *   to be saved as a preference.  The possible levels are:
+ *   
+ *.  0 - minimum safety - read and write in any directory
+ *.  1 - read in any directory, write in current directory
+ *.  2 - read/write access in current directory only
+ *.  3 - read-only access in current directory only
+ *.  4 - maximum safety - no file I/O allowed
+ *   
+ *   When operations are allowed only in the current directory, the
+ *   operations will fail if the filename contains any sort of path
+ *   specifier (for example, on Unix, any file that contains a '/' is
+ *   considered to have a path specifier, and will always fail if
+ *   operations are only allowed in the current directory).  
+ */
+void biffopen(bifcxdef *ctx, int argc)
+{
+    char      fname[OSFNMAX];
+    uchar    *p;
+    uchar    *mode;
+    int       modelen;
+    int       fnum;
+    osfildef *fp;
+    int       bin_mode = TRUE;   /* flag: mode is binary (rather than text) */
+    int       rw_mode = FALSE;     /* flag: both read and write are allowed */
+    char      main_mode;                     /* 'r' for read, 'w' for write */
+    int       in_same_dir;            /* flag: file is in current directory */
+    appctxdef *appctx;
+    
+    bifcntargs(ctx, 2, argc);
+
+    /* get the filename */
+    p = runpopstr(ctx->bifcxrun);
+    bifcstr(ctx, fname, (size_t)sizeof(fname), p);
+
+    /* 
+     *   If it's a relative path, combine it with the game file path to form
+     *   the absolute path.  This ensures that relative paths are always
+     *   relative to the original working directory if the OS-level working
+     *   directory has changed. 
+     */
+    if (!os_is_file_absolute(fname))
+    {
+        /* combine the game file path with the relative filename */
+        char newname[OSFNMAX];
+        os_build_full_path(newname, sizeof(newname),
+                           ctx->bifcxrun->runcxgamepath, fname);
+
+        /* replace the original filename with the full path */
+        strcpy(fname, newname);
+    }
+
+    /* get the mode string */
+    mode = runpopstr(ctx->bifcxrun);
+    modelen = osrp2(mode) - 2;
+    mode += 2;
+    if (modelen < 1)
+        goto bad_mode;
+
+    /* allocate a filenum for the file */
+    for (fnum = 0 ; fnum < BIFFILMAX ; ++fnum)
+    {
+        if (ctx->bifcxfile[fnum].fp == 0)
+            break;
+    }
+    if (fnum == BIFFILMAX)
+    {
+        /* return nil to indicate failure */
+        runpnil(ctx->bifcxrun);
+        return;
+    }
+
+    /* parse the main mode */
+    switch(*mode)
+    {
+    case 'w':
+    case 'W':
+        main_mode = 'w';
+        break;
+
+    case 'r':
+    case 'R':
+        main_mode = 'r';
+        break;
+
+    default:
+        goto bad_mode;
+    }
+
+    /* skip the main mode, and check for a '+' flag */
+    ++mode;
+    --modelen;
+    if (modelen > 0 && *mode == '+')
+    {
+        /* note the read/write mode */
+        rw_mode = TRUE;
+
+        /* skip the speciifer */
+        ++mode;
+        --modelen;
+    }
+
+    /* check for a binary/text specifier */
+    if (modelen > 0)
+    {
+        switch(*mode)
+        {
+        case 'b':
+        case 'B':
+            bin_mode = TRUE;
+            break;
+
+        case 't':
+        case 'T':
+            bin_mode = FALSE;
+            break;
+
+        default:
+            goto bad_mode;
+        }
+
+        /* skip the binary/text specifier */
+        ++mode;
+        --modelen;
+    }
+
+    /* it's an error if there's anything left unparsed */
+    if (modelen > 0)
+        goto bad_mode;
+
+    /*
+     *   If we have a host application context, and it provides a file
+     *   safety level callback function, ask the host system for its
+     *   current file safety level, which overrides our current setting.  
+     */
+    appctx = ctx->bifcxappctx;
+    if (appctx != 0 && appctx->get_io_safety_level != 0)
+    {
+        /* 
+         *   ask the host system for the current level, and override any
+         *   setting we previously had 
+         */
+        (*appctx->get_io_safety_level)(
+            appctx->io_safety_level_ctx,
+            &ctx->bifcxsafetyr, &ctx->bifcxsafetyw);
+    }
+
+    /* 
+     *   Check to see if the file is in the current working directory - if
+     *   not, we may have to disallow the operation based on safety level
+     *   settings.
+     */
+    in_same_dir = os_is_file_in_dir(
+        fname, ctx->bifcxrun->runcxgamepath, TRUE, FALSE);
+
+    /* check file safety settings */
+    switch(main_mode)
+    {
+    case 'w':
+        /* 
+         *   writing - we must be at a safety level no higher than 2
+         *   (read/write current directory) to write at all, and we must be
+         *   level 0 to write a file that's not in the current directory 
+         */
+        if (ctx->bifcxsafetyw > 2
+            || (!in_same_dir && ctx->bifcxsafetyw > 0))
+        {
+            /* this operation is not allowed - return failure */
+            runpnil(ctx->bifcxrun);
+            return;
+        }
+        break;
+
+    case 'r':
+        /*
+         *   reading - we must be at a safety level no higher than 3 (read
+         *   current directory) to read at all, and we must be at safety
+         *   level 1 (read any directory) or lower to read a file that's not
+         *   in the current directory 
+         */
+        if (ctx->bifcxsafetyr > 3
+            || (!in_same_dir && ctx->bifcxsafetyr > 1))
+        {
+            /* this operation is not allowed - return failure */
+            runpnil(ctx->bifcxrun);
+            return;
+        }
+        break;
+
+    default:
+        /* 
+         *   fail the operation, as a code maintenance measure to make
+         *   sure that we add appropriate cases to this switch (even if
+         *   merely to allow the operation unconditionally) in the event
+         *   that more modes are added in the future 
+         */
+        goto bad_mode;
+    }
+
+    /* try opening the file */
+    switch(main_mode)
+    {
+    case 'w':
+        /* check for binary vs text mode */
+        if (bin_mode)
+        {
+            /* 
+             *   binary mode -- allow read/write or just writing, but in
+             *   either case truncate the file if it already exists, and
+             *   create a new file if it doesn't exist 
+             */
+            if (rw_mode)
+                fp = osfoprwtb(fname, OSFTDATA);
+            else
+                fp = osfopwb(fname, OSFTDATA);
+        }
+        else
+        {
+            /* text mode - don't allow read/write on a text file */
+            if (rw_mode)
+                goto bad_mode;
+
+            /* open the file */
+            fp = osfopwt(fname, OSFTTEXT);
+        }
+        break;
+
+    case 'r':
+        /* check for binary vs text mode */
+        if (bin_mode)
+        {
+            /*
+             *   Binary mode -- allow read/write or just reading; leave
+             *   any existing file intact.
+             */
+            if (rw_mode)
+            {
+                /* open for reading and writing, keeping existing data */
+                fp = osfoprwb(fname, OSFTDATA);
+            }
+            else
+            {
+                /* open for read-only */
+                fp = osfoprb(fname, OSFTDATA);
+            }
+        }
+        else
+        {
+            /* text mode -- only allow reading */
+            if (rw_mode)
+                goto bad_mode;
+
+            /* open the file */
+            fp = osfoprt(fname, OSFTTEXT);
+        }
+        break;
+
+    default:
+        goto bad_mode;
+    }
+
+    /* if we couldn't open it, return nil */
+    if (fp == 0)
+    {
+        runpnil(ctx->bifcxrun);
+        return;
+    }
+
+    /* store the flags */
+    ctx->bifcxfile[fnum].flags = 0;
+    if (bin_mode)
+        ctx->bifcxfile[fnum].flags |= BIFFIL_F_BINARY;
+
+    /* remember the file handle */
+    ctx->bifcxfile[fnum].fp = fp;
+
+    /* return the file number (i.e., the slot number) */
+    runpnum(ctx->bifcxrun, (long)fnum);
+    return;
+
+
+    /* come here on a mode error */
+bad_mode:
+    runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "fopen");
+}
+              
+/* service routine for file routines - get and validate a file number */
+static osfildef *bif_get_file(bifcxdef *ctx, int *fnump, int *bin_modep)
+{
+    long fnum;
+
+    /* get the file number and make sure it's valid */
+    fnum = runpopnum(ctx->bifcxrun);
+    if (fnum < 0 || fnum >= BIFFILMAX || ctx->bifcxfile[fnum].fp == 0)
+        runsig(ctx->bifcxrun, ERR_BADFILE);
+
+    /* put the validated file number, if the caller wants it */
+    if (fnump != 0)
+        *fnump = (int)fnum;
+
+    /* set the binary-mode flag, if the caller wants it */
+    if (bin_modep != 0)
+        *bin_modep = ((ctx->bifcxfile[fnum].flags & BIFFIL_F_BINARY) != 0);
+
+    /* return the file array pointer */
+    return ctx->bifcxfile[fnum].fp;
+}
+
+void biffclose(bifcxdef *ctx, int argc)
+{
+    int       fnum;
+    osfildef *fp;
+
+    /* get the file number */
+    bifcntargs(ctx, 1, argc);
+    fp = bif_get_file(ctx, &fnum, 0);
+
+    /* close the file and release the slot */
+    osfcls(fp);
+    ctx->bifcxfile[fnum].fp = 0;
+}
+              
+void bifftell(bifcxdef *ctx, int argc)
+{
+    osfildef *fp;
+
+    /* get the file number */
+    bifcntargs(ctx, 1, argc);
+    fp = bif_get_file(ctx, (int *)0, 0);
+
+    /* return the seek position */
+    runpnum(ctx->bifcxrun, osfpos(fp));
+}
+              
+void biffseek(bifcxdef *ctx, int argc)
+{
+    osfildef *fp;
+    long      pos;
+
+    /* get the file pointer */
+    bifcntargs(ctx, 2, argc);
+    fp = bif_get_file(ctx, (int *)0, 0);
+
+    /* get the seek position, and seek there */
+    pos = runpopnum(ctx->bifcxrun);
+    osfseek(fp, pos, OSFSK_SET);
+}
+              
+void biffseekeof(bifcxdef *ctx, int argc)
+{
+    osfildef *fp;
+
+    /* get the file pointer */
+    bifcntargs(ctx, 1, argc);
+    fp = bif_get_file(ctx, (int *)0, 0);
+
+    /* seek to the end */
+    osfseek(fp, 0L, OSFSK_END);
+}
+              
+void biffwrite(bifcxdef *ctx, int argc)
+{
+    osfildef *fp;
+    char      typ;
+    char      buf[32];
+    runsdef   val;
+    int       bin_mode;
+    
+    /* get the file */
+    bifcntargs(ctx, 2, argc);
+    fp = bif_get_file(ctx, (int *)0, &bin_mode);
+
+    /* get the value to write */
+    runpop(ctx->bifcxrun, &val);
+    typ = val.runstyp;
+
+    if (bin_mode)
+    {
+        /* put a byte indicating the type */
+        if (osfwb(fp, &typ, 1))
+            goto ret_error;
+        
+        /* see what type of data we want to put */
+        switch(typ)
+        {
+        case DAT_NUMBER:
+            oswp4s(buf, val.runsv.runsvnum);
+            if (osfwb(fp, buf, 4))
+                goto ret_error;
+            break;
+            
+        case DAT_SSTRING:
+            /* write the string, including the length prefix */
+            if (osfwb(fp, val.runsv.runsvstr, osrp2(val.runsv.runsvstr)))
+                goto ret_error;
+            break;
+            
+        case DAT_TRUE:
+            /* all we need for this is the type prefix */
+            break;
+            
+        default:
+            /* other types are not acceptable */
+            runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "fwrite");
+        }
+    }
+    else
+    {
+        uint rem;
+        uchar *p;
+        
+        switch(typ)
+        {
+        case DAT_SSTRING:
+            /*
+             *   Copy and translate the string to our buffer, in pieces if
+             *   the size of the string exceeds that of our buffer.  If we
+             *   encounter any escape codes, translate them.  
+             */
+            rem = osrp2(val.runsv.runsvstr) - 2;
+            p = val.runsv.runsvstr + 2;
+            while (rem > 0)
+            {
+                uchar *dst;
+                uchar dbuf[256];
+
+                /* fill up the buffer */
+                for (dst = dbuf ;
+                     rem != 0 && (size_t)(dst - dbuf) < sizeof(dbuf) - 1 ;
+                     ++p, --rem)
+                {
+                    /* if we have an escape character, translate it */
+                    if (*p == '\\' && rem > 1)
+                    {
+                        /* skip the opening slash */
+                        ++p;
+                        --rem;
+
+                        /* translate it */
+                        switch(*p)
+                        {
+                        case 'n':
+                            *dst++ = '\n';
+                            break;
+
+                        case 't':
+                            *dst++ = '\t';
+                            break;
+
+                        default:
+                            *dst++ = *p;
+                            break;
+                        }
+                    }
+                    else
+                    {
+                        /* copy this character directly */
+                        *dst++ = *p;
+                    }
+                }
+
+                /* null-terminate the buffer */
+                *dst = '\0';
+
+                /* write it out */
+                if (osfputs((char *)dbuf, fp) == EOF)
+                    goto ret_error;
+            }
+
+            /* done */
+            break;
+
+        default:
+            /* other types are not allowed */
+            runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "fwrite");
+        }
+    }
+
+    /* success */
+    runpnil(ctx->bifcxrun);
+    return;
+
+ret_error:
+    val.runstyp = DAT_TRUE;
+    runpush(ctx->bifcxrun, DAT_TRUE, &val);
+}
+              
+void biffread(bifcxdef *ctx, int argc)
+{
+    osfildef *fp;
+    char      typ;
+    char      buf[32];
+    runsdef   val;
+    ushort    len;
+    int       bin_mode;
+
+    /* get the file pointer */
+    bifcntargs(ctx, 1, argc);
+    fp = bif_get_file(ctx, (int *)0, &bin_mode);
+
+    if (bin_mode)
+    {
+        /* binary file - read the type byte */
+        if (osfrb(fp, &typ, 1))
+            goto ret_error;
+        
+        /* read the data according to the type */
+        switch(typ)
+        {
+        case DAT_NUMBER:
+            if (osfrb(fp, buf, 4))
+                goto ret_error;
+            runpnum(ctx->bifcxrun, osrp4s(buf));
+            break;
+            
+        case DAT_SSTRING:
+            /* get the size */
+            if (osfrb(fp, buf, 2))
+                goto ret_error;
+            len = osrp2(buf);
+            
+            /* reserve space */
+            runhres(ctx->bifcxrun, len, 0);
+            
+            /* read the string into the reserved space */
+            if (osfrb(fp, ctx->bifcxrun->runcxhp + 2, len - 2))
+                goto ret_error;
+            
+            /* set up the string */
+            oswp2(ctx->bifcxrun->runcxhp, len);
+            val.runstyp = DAT_SSTRING;
+            val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+            
+            /* consume the space */
+            ctx->bifcxrun->runcxhp += len;
+            
+            /* push the value */
+            runrepush(ctx->bifcxrun, &val);
+            break;
+            
+        case DAT_TRUE:
+            val.runstyp = DAT_TRUE;
+            runpush(ctx->bifcxrun, DAT_TRUE, &val);
+            break;
+            
+        default:
+            goto ret_error;
+        }
+    }
+    else
+    {
+        uchar  dbuf[257];
+        uchar *dst;
+        uchar *src;
+        uint   dlen;
+        uint   res_total;
+        int    found_nl;
+
+        /* 
+         *   reserve some space in the heap - we'll initially reserve
+         *   space for twice our buffer, in case every single character
+         *   needs to be expanded into an escape sequence
+         */
+        res_total = sizeof(dbuf) * 2;
+        runhres(ctx->bifcxrun, res_total, 0);
+
+        /* set up our output value */
+        val.runstyp = DAT_SSTRING;
+        val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+        dst = ctx->bifcxrun->runcxhp + 2;
+
+        /* keep going until we find a newline or run out of data */
+        for (found_nl = FALSE ; !found_nl ; )
+        {
+            /* text-mode - read the result into our buffer */
+            if (!osfgets((char *)dbuf, sizeof(dbuf) - 1, fp))
+            {
+                /* 
+                 *   if we found a newline, return what we have;
+                 *   otherwise, return an error 
+                 */
+                if (found_nl)
+                    break;
+                else
+                    goto ret_error;
+            }
+            
+            /* 
+             *   make sure it's null-terminated, in case the buffer was
+             *   full 
+             */
+            dbuf[256] = '\0';
+            
+            /* translate into the heap area we've reserved */
+            for (src = dbuf ; *src != '\0' ; ++src, ++dst)
+            {
+                /* determine if we need translations */
+                switch(*src)
+                {
+                case '\n':
+                case '\r':
+                    /* translate to a newline sequence */
+                    *dst++ = '\\';
+                    *dst = 'n';
+                    
+                    /* note that we've found our newline */
+                    found_nl = TRUE;
+                    break;
+                    
+                case '\t':
+                    /* translate to a tab sequence */
+                    *dst++ = '\\';
+                    *dst = 't';
+                    break;
+                    
+                case '\\':
+                    /* expand to a double-backslash sequence */
+                    *dst++ = '\\';
+                    *dst = '\\';
+                    break;
+
+                default:
+                    /* leave other characters intact */
+                    *dst = *src;
+                    break;
+                }
+            }
+
+            /*
+             *   If we didn't find the newline, we'll need more space.
+             *   This is a bit tricky, because the space we've already set
+             *   up may move if we compact the heap while asking for more
+             *   space.  So, remember our current length, reserve another
+             *   buffer-full of space, and set everything up at the new
+             *   output location if necessary.  
+             */
+            if (!found_nl)
+            {
+                /* reserve another buffer-full (double for expansion) */
+                res_total += sizeof(dbuf) * 2;
+
+                /* note our current offset */
+                dlen = dst - val.runsv.runsvstr;
+                oswp2(val.runsv.runsvstr, dlen);
+
+                /* ask for the space */
+                runhres(ctx->bifcxrun, res_total, 0);
+
+                /* 
+                 *   Since we were at the top of the heap before, we
+                 *   should still be at the top of the heap.  If not,
+                 *   we'll have to copy from our old location to the new
+                 *   top of the heap.  
+                 */
+                if (val.runsv.runsvstr != ctx->bifcxrun->runcxhp)
+                {
+                    /* copy our existing text to our new location */
+                    memmove(ctx->bifcxrun->runcxhp, val.runsv.runsvstr, dlen);
+
+                    /* fix up our pointer */
+                    val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+                }
+
+                /* re-establish our output pointer at our new location */
+                dst = val.runsv.runsvstr + dlen;
+            }
+        }
+
+        /* finish setting up the string */
+        dlen = dst - val.runsv.runsvstr;
+        oswp2(val.runsv.runsvstr, dlen);
+
+        /* consume the space */
+        ctx->bifcxrun->runcxhp += dlen;
+
+        /* push the value */
+        runrepush(ctx->bifcxrun, &val);
+    }
+
+    /* success - we've already pushed the return value */
+    return;
+
+ret_error:
+    runpnil(ctx->bifcxrun);
+}
+
+void bifcapture(bifcxdef *ctx, int argc)
+{
+    mcmcxdef *mcx = ctx->bifcxrun->runcxmem;
+    mcmon     obj;
+    uint      siz;
+    uint      ofs;
+    uchar    *p;
+
+    /* get the capture on/off flag */
+    bifcntargs(ctx, 1, argc);
+    switch(runtostyp(ctx->bifcxrun))
+    {
+    case DAT_TRUE:
+        /* turn on capturing */
+        tiocapture(ctx->bifcxtio, mcx, TRUE);
+
+        /*
+         *   The return value is a status code used to restore the
+         *   original status on the bracketing call to turn off output.
+         *   The only status necessary is the current output size. 
+         */
+        siz = tiocapturesize(ctx->bifcxtio);
+        runpnum(ctx->bifcxrun, (long)siz);
+        break;
+
+    case DAT_NUMBER:
+        /* get the original offset */
+        ofs = runpopnum(ctx->bifcxrun);
+
+        /* get the capture object and size */
+        obj = tiogetcapture(ctx->bifcxtio);
+        siz = tiocapturesize(ctx->bifcxtio);
+        if (obj == MCMONINV)
+        {
+            runpnil(ctx->bifcxrun);
+            return;
+        }
+
+        /* turn off capturing and reset the buffer on the outermost call */
+        if (ofs == 0)
+        {
+            tiocapture(ctx->bifcxtio, mcx, FALSE);
+            tioclrcapture(ctx->bifcxtio);
+        }
+
+        /* lock the object */
+        p = mcmlck(mcx, obj);
+
+        /* include only the part that happened after the matching call */
+        p += ofs;
+        siz = (ofs > siz) ? 0 : siz - ofs;
+
+        ERRBEGIN(ctx->bifcxerr)
+
+        /* push the string onto the stack */
+        runpstr(ctx->bifcxrun, (char *)p, siz, 0);
+        
+        ERRCLEAN(ctx->bifcxerr)
+            /* done with the object - unlock it */
+            mcmunlck(mcx, obj);
+        ERRENDCLN(ctx->bifcxerr)
+
+        /* done with the object - unlock it */
+        mcmunlck(mcx, obj);
+        break;
+
+    default:
+        runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "outcapture");
+    }
+}
+
+/*
+ *   systemInfo 
+ */
+void bifsysinfo(bifcxdef *ctx, int argc)
+{
+    runsdef val;
+    int id;
+    long result;
+
+    /* see what we have */
+    switch(id = (int)runpopnum(ctx->bifcxrun))
+    {
+    case SYSINFO_SYSINFO:
+        /* systemInfo call is supported in this version - return true */
+        bifcntargs(ctx, 1, argc);
+        val.runstyp = DAT_TRUE;
+        runpush(ctx->bifcxrun, DAT_TRUE, &val);
+        break;
+
+    case SYSINFO_VERSION:
+        /* get the run-time version string */
+        bifcntargs(ctx, 1, argc);
+        runpushcstr(ctx->bifcxrun, TADS_RUNTIME_VERSION,
+                    strlen(TADS_RUNTIME_VERSION), 0);
+        break;
+
+    case SYSINFO_OS_NAME:
+        /* get the operating system name */
+        bifcntargs(ctx, 1, argc);
+        runpushcstr(ctx->bifcxrun, OS_SYSTEM_NAME, strlen(OS_SYSTEM_NAME), 0);
+        break;
+        
+    case SYSINFO_HTML:
+    case SYSINFO_JPEG:
+    case SYSINFO_PNG:
+    case SYSINFO_WAV:
+    case SYSINFO_MIDI:
+    case SYSINFO_WAV_MIDI_OVL:
+    case SYSINFO_WAV_OVL:
+    case SYSINFO_PREF_IMAGES:
+    case SYSINFO_PREF_SOUNDS:
+    case SYSINFO_PREF_MUSIC:
+    case SYSINFO_PREF_LINKS:
+    case SYSINFO_MPEG:
+    case SYSINFO_MPEG1:
+    case SYSINFO_MPEG2:
+    case SYSINFO_MPEG3:
+    case SYSINFO_LINKS_HTTP:
+    case SYSINFO_LINKS_FTP:
+    case SYSINFO_LINKS_NEWS:
+    case SYSINFO_LINKS_MAILTO:
+    case SYSINFO_LINKS_TELNET:
+    case SYSINFO_PNG_TRANS:
+    case SYSINFO_PNG_ALPHA:
+    case SYSINFO_OGG:
+    case SYSINFO_MNG:
+    case SYSINFO_MNG_TRANS:
+    case SYSINFO_MNG_ALPHA:
+    case SYSINFO_TEXT_HILITE:
+    case SYSINFO_INTERP_CLASS:
+        /* 
+         *   these information types are all handled by the OS layer, and
+         *   take no additional arguments 
+         */
+        bifcntargs(ctx, 1, argc);
+        if (os_get_sysinfo(id, 0, &result))
+        {
+            /* we got a valid result - return it */
+            runpnum(ctx->bifcxrun, result);
+        }
+        else
+        {
+            /* the code was unknown - return nil */
+            runpnil(ctx->bifcxrun);
+        }
+        break;
+
+    case SYSINFO_HTML_MODE:
+        /* ask the output formatter for its current HTML setting */
+        bifcntargs(ctx, 1, argc);
+        val.runstyp = runclog(tio_is_html_mode());
+        runpush(ctx->bifcxrun, val.runstyp, &val);
+        break;
+
+    case SYSINFO_TEXT_COLORS:
+        /* 
+         *   Text colors are only supported in full HTML interpreters.  If
+         *   this is an HTML interpreter, ask the underlying OS layer about
+         *   color support; otherwise, colors are not available, since we
+         *   don't handle colors in our text-only HTML subset.
+         *   
+         *   Colors are NOT supported in the HTML mini-parser in text-only
+         *   interpreters in TADS 2.  So, even if we're running in HTML
+         *   mode, if this is a text-only interpreter, we can't display text
+         *   colors.  
+         */
+        bifcntargs(ctx, 1, argc);
+        if (os_get_sysinfo(SYSINFO_HTML, 0, &result) && result != 0)
+        {
+            /* 
+             *   we're in HTML mode, so ask the underlying HTML OS
+             *   implementation for its level of text color support 
+             */
+            if (os_get_sysinfo(id, 0, &result))
+            {
+                /* push the OS-level result */
+                runpnum(ctx->bifcxrun, result);
+            }
+            else
+            {
+                /* the OS code doesn't recognize it; assume no support */
+                runpnum(ctx->bifcxrun, SYSINFO_TXC_NONE);
+            }
+        }
+        else
+        {
+            /* we're a text-only interpreter - no color support */
+            runpnum(ctx->bifcxrun, SYSINFO_TXC_NONE);
+        }
+        break;
+
+    case SYSINFO_BANNERS:
+        /* TADS 2 does not offer banner support */
+        bifcntargs(ctx, 1, argc);
+        runpnum(ctx->bifcxrun, 0);
+        break;
+        
+    default:
+        /* 
+         *   Other codes fail harmlessly with a nil return value.  Pop all
+         *   remaining arguments and return nil. 
+         */
+        for ( ; argc > 1 ; --argc)
+            rundisc(ctx->bifcxrun);
+        runpnil(ctx->bifcxrun);
+        break;
+    }
+}
+
+/*
+ *   morePrompt - display the more prompt and wait for the user to respond 
+ */
+void bifmore(bifcxdef *ctx, int argc)
+{
+    /* this function takes no arguments */
+    bifcntargs(ctx, 0, argc);
+
+    /* display the MORE prompt */
+    tioflushn(ctx->bifcxtio, 1);
+    out_more_prompt();
+}
+
+/*
+ *   parserSetMe 
+ */
+void bifsetme(bifcxdef *ctx, int argc)
+{
+    objnum new_me;
+    
+    /* this function takes one argument */
+    bifcntargs(ctx, 1, argc);
+
+    /* get the new "Me" object */
+    new_me = runpopobj(ctx->bifcxrun);
+
+    /* "Me" cannot be nil */
+    if (new_me == MCMONINV)
+        runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "parserSetMe");
+
+    /* set the current "Me" object in the parser */
+    voc_set_me(ctx->bifcxrun->runcxvoc, new_me);
+}
+
+/*
+ *   parserGetMe 
+ */
+void bifgetme(bifcxdef *ctx, int argc)
+{
+    /* this function takes no arguments */
+    bifcntargs(ctx, 0, argc);
+
+    /* return the current Me object */
+    runpobj(ctx->bifcxrun, ctx->bifcxrun->runcxvoc->voccxme);
+}
+
+/*
+ *   reSearch 
+ */
+void bifresearch(bifcxdef *ctx, int argc)
+{
+    uchar  *patstr;
+    size_t  patlen;
+    uchar  *searchstr;
+    size_t  searchlen;
+    int     result_len;
+    int     match_ofs;
+    
+    /* this function takes two parameters: pattern, string */
+    bifcntargs(ctx, 2, argc);
+
+    /* get the pattern string */
+    patstr = runpopstr(ctx->bifcxrun);
+    patlen = osrp2(patstr) - 2;
+    patstr += 2;
+
+    /* get the search string */
+    searchstr = runpopstr(ctx->bifcxrun);
+    searchlen = osrp2(searchstr) - 2;
+    searchstr += 2;
+
+    /* search for the pattern in the string */
+    match_ofs = re_compile_and_search(&ctx->bifcxregex,
+                                      (char *)patstr, patlen,
+                                      (char *)searchstr, searchlen,
+                                      &result_len);
+
+    /* 
+     *   if we didn't match, return nil; otherwise, return a list with the
+     *   match offset and length 
+     */
+    if (match_ofs < 0)
+    {
+        /* no match - return nil */
+        runpnil(ctx->bifcxrun);
+    }
+    else
+    {
+        ushort listsiz;
+        runsdef val;
+        uchar *p;
+        
+        /* 
+         *   build a list consisting of two numbers and a string: two
+         *   bytes for the list header, then two elements at (one byte for
+         *   the datatype header, four bytes for the number), then the
+         *   string element with (one byte for the datatype, two bytes for
+         *   the string length prefix, and the bytes of the string) 
+         */
+        listsiz = 2 + (1+4)*2 + (1 + 2 + (ushort)(result_len));
+
+        /* allocate the space */
+        runhres(ctx->bifcxrun, listsiz, 0);
+
+        /* set up the list stack item */
+        val.runstyp = DAT_LIST;
+        p = val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+        /* set the list's length */
+        oswp2(p, listsiz);
+        p += 2;
+
+        /* 
+         *   Add the offset element.  For consistency with TADS
+         *   conventions, use 1 as the offset of the first character in
+         *   the string - this makes it easy to use the offset value with
+         *   substr(). 
+         */
+        *p++ = DAT_NUMBER;
+        oswp4s(p, match_ofs + 1);
+        p += 4;
+
+        /* add the length element */
+        *p++ = DAT_NUMBER;
+        oswp4s(p, result_len);
+        p += 4;
+
+        /* add the result string */
+        *p++ = DAT_SSTRING;
+        oswp2(p, result_len + 2);
+        p += 2;
+        memcpy(p, ctx->bifcxregex.strbuf + match_ofs, result_len);
+
+        /* reserve the space in the heap */
+        ctx->bifcxrun->runcxhp += listsiz;
+
+        /* return the list */
+        runrepush(ctx->bifcxrun, &val);
+    }
+}
+
+/* reGetGroup */
+void bifregroup(bifcxdef *ctx, int argc)
+{
+    int grp;
+    size_t len;
+    re_group_register *reg;
+    ushort hplen;
+    runsdef val;
+    uchar *p;
+    long numval;
+    
+    /* this function takes one parameter: the group number to retrieve */
+    bifcntargs(ctx, 1, argc);
+
+    /* get the group number */
+    grp = (int)runpopnum(ctx->bifcxrun);
+
+    /* make sure it's within range */
+    if (grp < 1 || grp > RE_GROUP_REG_CNT)
+        runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "reGetGroup");
+
+    /* adjust from a 1-bias to an array index */
+    --grp;
+
+    /* if the group was never set, return nil */
+    if (grp >= ctx->bifcxregex.cur_group)
+    {
+        runpnil(ctx->bifcxrun);
+        return;
+    }
+
+    /* get the register */
+    reg = &ctx->bifcxregex.regs[grp];
+
+    /* if the group wasn't set, return nil */
+    if (reg->start_ofs == 0 || reg->end_ofs == 0)
+    {
+        runpnil(ctx->bifcxrun);
+        return;
+    }
+
+    /* calculate the length of the string in this group */
+    len = reg->end_ofs - reg->start_ofs;
+
+    /* 
+     *   reserve the necessary heap space: two bytes for the list length
+     *   prefix, two number elements (one byte each for the type, four
+     *   bytes each for the value), and the string element (one byte for
+     *   the type, two bytes for the length prefix, plus the string
+     *   itself).  
+     */
+    hplen = (ushort)(2 + 2*(1+4) + (1 + 2 + len));
+    runhres(ctx->bifcxrun, hplen, 0);
+
+    /* set up the stack value */
+    val.runstyp = DAT_LIST;
+    p = val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+    /* put in the list length prefix */
+    oswp2(p, hplen);
+    p += 2;
+
+    /* add the starting character position of the group - adjust to 1-bias */
+    *p++ = DAT_NUMBER;
+    numval = (long)(reg->start_ofs - ctx->bifcxregex.strbuf) + 1;
+    oswp4s(p, numval);
+    p += 4;
+
+    /* add the length of the group */
+    *p++ = DAT_NUMBER;
+    numval = (long)(reg->end_ofs - reg->start_ofs);
+    oswp4s(p, numval);
+    p += 4;
+
+    /* set up the string */
+    *p++ = DAT_SSTRING;
+    oswp2(p, len+2);
+    p += 2;
+    memcpy(p, reg->start_ofs, len);
+
+    /* consume the heap space */
+    ctx->bifcxrun->runcxhp += hplen;
+
+    /* push the result */
+    runrepush(ctx->bifcxrun, &val);
+}
+
+
+/*
+ *   inputevent 
+ */
+void bifinpevt(bifcxdef *ctx, int argc)
+{
+	unsigned long timeout = 0;
+    int use_timeout = FALSE;
+    os_event_info_t info;
+    int evt;
+    uchar *p;
+    ushort lstsiz;
+    runsdef val;
+    size_t paramlen = 0;
+    char keyname[20];
+    
+    /* check for a timeout value */
+    if (argc == 0)
+    {
+        /* there's no timeout */
+        use_timeout = FALSE;
+        timeout = 0;
+    }
+    else if (argc >= 1)
+    {
+        /* get the timeout value */
+        use_timeout = TRUE;
+        timeout = (unsigned long)runpopnum(ctx->bifcxrun);
+    }
+
+    /* ensure we don't have too many arguments */
+    if (argc > 1)
+        runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+    /* flush any pending output */
+    tioflushn(ctx->bifcxtio, 0);
+
+    /* reset count of lines since keyboard input */
+    tioreset(ctx->bifcxtio);
+
+    /* ask the OS code for an event */
+    evt = os_get_event(timeout, use_timeout, &info);
+
+    /* 
+     *   the list always minimally needs two bytes of length prefix plus a
+     *   number with the event code (one byte for the type, four bytes for
+     *   the value) 
+     */
+    lstsiz = 2 + (1 + 4);
+
+    /* figure out how much space we'll need based on the event type */
+    switch(evt)
+    {
+    case OS_EVT_KEY:
+        /* 
+         *   we need space for a string with one or two bytes (depending
+         *   on whether or not we have an extended key code) - 1 byte for
+         *   type code, 2 for length prefix, and 1 or 2 for the string's
+         *   contents 
+         */
+        paramlen = (info.key[0] == 0 ? 2 : 1);
+
+        /* map the extended key */
+        get_ext_key_name(keyname, info.key[0], info.key[1]);
+
+        /* determine the length we need for the string */
+        paramlen = strlen(keyname);
+
+        /* add it into the list */
+        lstsiz += 1 + 2 + paramlen;
+        break;
+
+    case OS_EVT_HREF:
+        /* 
+         *   we need space for the href string - 1 byte for type code, 2
+         *   for length prefix, plus the string's contents 
+         */
+        paramlen = strlen(info.href);
+        lstsiz += 1 + 2 + (ushort)paramlen;
+        break;
+
+    default:
+        /* other event types have no extra data */
+        break;
+    }
+
+    /* allocate space for the list */
+    runhres(ctx->bifcxrun, lstsiz, 0);
+
+    /* set up the stack value */
+    val.runstyp = DAT_LIST;
+    p = val.runsv.runsvstr = ctx->bifcxrun->runcxhp;
+
+    /* set up the list length prefix */
+    oswp2(p, lstsiz);
+    p += 2;
+
+    /* set up the event type element */
+    *p++ = DAT_NUMBER;
+    oswp4s(p, evt);
+    p += 4;
+
+    /* add the event parameters, if any */
+    switch(evt)
+    {
+    case OS_EVT_KEY:
+        /* set up the string for the key */
+        *p++ = DAT_SSTRING;
+        oswp2(p, paramlen + 2);
+        p += 2;
+
+        /* add the characters to the string */
+        memcpy(p, keyname, paramlen);
+        p += paramlen;
+        break;
+
+    case OS_EVT_HREF:
+        /* add the string for the href */
+        *p++ = DAT_SSTRING;
+        oswp2(p, paramlen + 2);
+        memcpy(p + 2, info.href, paramlen);
+        break;
+    }
+
+    /* consume the heap space */
+    ctx->bifcxrun->runcxhp += lstsiz;
+
+    /* push the result */
+    runrepush(ctx->bifcxrun, &val);
+}
+
+/* timeDelay */
+void bifdelay(bifcxdef *ctx, int argc)
+{
+    long delay;
+    
+    /* ensure we have the right number of arguments */
+    bifcntargs(ctx, 1, argc);
+
+    /* flush any pending output */
+    tioflushn(ctx->bifcxtio, 0);
+
+    /* get the delay time */
+    delay = runpopnum(ctx->bifcxrun);
+
+    /* let the system perform the delay */
+    os_sleep_ms(delay);
+}
+
+/* setOutputFilter */
+void bifsetoutfilter(bifcxdef *ctx, int argc)
+{
+    /* ensure we have the right number of arguments */
+    bifcntargs(ctx, 1, argc);
+
+    /* see what we have */
+    switch(runtostyp(ctx->bifcxrun))
+    {
+    case DAT_NIL:
+        /* remove the current filter */
+        out_set_filter(MCMONINV);
+
+        /* discard the argument */
+        rundisc(ctx->bifcxrun);
+        break;
+
+    case DAT_FNADDR:
+        /* set the filter to the given function */
+        out_set_filter(runpopfn(ctx->bifcxrun));
+        break;
+
+    default:
+        /* anything else is invalid */
+        runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "setOutputFilter");
+    }
+}
+
+/*
+ *   Get an optional object argument.  If the next argument is not an
+ *   object value, or we're out of arguments, we'll return MCMONINV.
+ *   Otherwise, we'll pop the object value and return it, decrementing the
+ *   remaining argument counter provided. 
+ */
+static objnum bif_get_optional_obj_arg(bifcxdef *ctx, int *rem_argc)
+{
+    /* if we're out of arguments, there's no object value */
+    if (*rem_argc == 0)
+        return MCMONINV;
+
+    /* 
+     *   if the next argument is not an object or nil, we're out of object
+     *   arguments 
+     */
+    if (runtostyp(ctx->bifcxrun) != DAT_OBJECT
+        && runtostyp(ctx->bifcxrun) != DAT_NIL)
+        return MCMONINV;
+
+    /* we have an object - remove it from the remaining argument count */
+    --(*rem_argc);
+
+    /* pop and return the object value */
+    return runpopobjnil(ctx->bifcxrun);
+}
+
+/*
+ *   execCommand flag values 
+ */
+#define EC_HIDE_SUCCESS     0x00000001
+#define EC_HIDE_ERROR       0x00000002
+#define EC_SKIP_VALIDDO     0x00000004
+#define EC_SKIP_VALIDIO     0x00000008
+
+/*
+ *   execCommand - execute a recursive command 
+ */
+void bifexec(bifcxdef *ctx, int argc)
+{
+    objnum actor;
+    objnum verb;
+    objnum dobj;
+    objnum prep;
+    objnum iobj;
+    int    err;
+	uint   capture_start = 0;
+    uint   capture_end;
+    objnum capture_obj;
+    ulong  flags;
+    int    hide_any;
+    int    rem_argc;
+    
+    /* 
+     *   Check for the correct argument count.  The first two arguments
+     *   are required; additional arguments are optional. 
+     */
+    if (argc < 2 || argc > 6)
+        runsig(ctx->bifcxrun, ERR_BIFARGC);
+
+    /* pop the arguments - actor, verb, dobj, prep, iobj */
+    actor = runpopobjnil(ctx->bifcxrun);
+    verb = runpopobjnil(ctx->bifcxrun);
+
+    /* 
+     *   The other object arguments are optional.  If we run into a
+     *   numeric argument, it's the flags value, in which case we're out
+     *   of objects. 
+     */
+    rem_argc = argc - 2;
+    dobj = bif_get_optional_obj_arg(ctx, &rem_argc);
+    prep = bif_get_optional_obj_arg(ctx, &rem_argc);
+    iobj = bif_get_optional_obj_arg(ctx, &rem_argc);
+
+    /* if we have a flags argument, pop it */
+    if (rem_argc > 0)
+    {
+        /* the last argument is the flags - pop the numeric value */
+        flags = runpopnum(ctx->bifcxrun);
+
+        /* remove it from the remaining argument counter */
+        --rem_argc;
+    }
+    else
+    {
+        /* no flags specified - use zero by default */
+        flags = 0;
+    }
+
+    /* 
+     *   make sure we don't have any arguments left - if we do, then it
+     *   means that we got an incorrect type and skipped an argument when
+     *   we were trying to sense the meanings of the arguments from their
+     *   types 
+     */
+    if (rem_argc != 0)
+        runsig1(ctx->bifcxrun, ERR_INVTBIF, ERRTSTR, "execCommand");
+
+    /* if we're hiding any output, start output capture */
+    hide_any = ((flags & (EC_HIDE_SUCCESS | EC_HIDE_ERROR)) != 0);
+    if (hide_any)
+    {
+        /* start capturing */
+        tiocapture(ctx->bifcxtio, ctx->bifcxrun->runcxmem, TRUE);
+
+        /* note the current output position */
+        capture_start = tiocapturesize(ctx->bifcxtio);
+    }
+
+    /* execute the command */
+    err = execmd_recurs(ctx->bifcxrun->runcxvoc,
+                        actor, verb, dobj, prep, iobj,
+                        (flags & EC_SKIP_VALIDDO) == 0,
+                        (flags & EC_SKIP_VALIDIO) == 0);
+
+    /* if we're hiding any output, end hiding */
+    if (hide_any)
+    {
+        uchar *p;
+        int hide;
+        
+        /* get the capture buffer size */
+        capture_end = tiocapturesize(ctx->bifcxtio);
+
+        /* turn off capture if it wasn't already on when we started */
+        if (capture_start == 0)
+            tiocapture(ctx->bifcxtio, ctx->bifcxrun->runcxmem, FALSE);
+
+        /* determine whether we're hiding or showing the result */
+        if (err == 0)
+            hide = ((flags & EC_HIDE_SUCCESS) != 0);
+        else
+            hide = ((flags & EC_HIDE_ERROR) != 0);
+
+        /* show or hide the result, as appropriate */
+        if (hide)
+        {
+            /* 
+             *   We're hiding this result, so do not display the string.
+             *   If there's an enclosing capture, remove the string from
+             *   the enclosing capture.  
+             */
+            if (capture_start != 0)
+                tiopopcapture(ctx->bifcxtio, capture_start);
+        }
+        else
+        {
+            /* 
+             *   We're showing the text.  If we're in an enclosing
+             *   capture, do nothing - simply leave the string in the
+             *   enclosing capture buffer; otherwise, actually display it 
+             */
+            if (capture_start == 0)
+            {
+                /* lock the capture object */
+                capture_obj = tiogetcapture(ctx->bifcxtio);
+                p = mcmlck(ctx->bifcxrun->runcxmem, capture_obj);
+                
+                ERRBEGIN(ctx->bifcxerr)
+                {
+                    /* display the string */
+                    outformatlen((char *)p + capture_start,
+                                 capture_end - capture_start);
+                }
+                ERRCLEAN(ctx->bifcxerr)
+                {
+                    /* unlock the capture object before signalling out */
+                    mcmunlck(ctx->bifcxrun->runcxmem, capture_obj);
+                }
+                ERRENDCLN(ctx->bifcxerr);
+
+                /* unlock the capture object */
+                mcmunlck(ctx->bifcxrun->runcxmem, capture_obj);
+            }
+        }
+        
+        /* clear the capture buffer if it wasn't on when we started */
+        if (capture_start == 0)
+            tioclrcapture(ctx->bifcxtio);
+    }
+
+    /* push the result code */
+    runpnum(ctx->bifcxrun, err);
+}
+
+/* 
+ *   parserGetObj - get one of the objects associated with the command 
+ */
+void bifgetobj(bifcxdef *ctx, int argc)
+{
+    int id;
+	objnum obj = 0;
+    voccxdef *voc = ctx->bifcxrun->runcxvoc;
+    
+    /* check the argument count */
+    bifcntargs(ctx, 1, argc);
+
+    /* get the argument */
+    id = (int)runpopnum(ctx->bifcxrun);
+
+    /* get the appropriate object */
+    switch(id)
+    {
+    case 1:
+        /* get the current actor */
+        obj = voc->voccxactor;
+
+        /* if there's no current actor, use the current 'me' by default */
+        if (obj == MCMONINV)
+            obj = voc->voccxme;
+
+        /* done */
+        break;
+
+    case 2:
+        /* verb */
+        obj = voc->voccxverb;
+        break;
+
+    case 3:
+        /* direct object */
+        obj = (voc->voccxdobj == 0 ? MCMONINV : voc->voccxdobj->vocolobj);
+        break;
+
+    case 4:
+        /* preposition */
+        obj = voc->voccxprep;
+        break;
+
+    case 5:
+        /* indirect object */
+        obj = (voc->voccxiobj == 0 ? MCMONINV : voc->voccxiobj->vocolobj);
+        break;
+
+    case 6:
+        /* "it" */
+        obj = voc->voccxit;
+        break;
+
+    case 7:
+        /* "him" */
+        obj = voc->voccxhim;
+        break;
+
+    case 8:
+        /* "her" */
+        obj = voc->voccxher;
+        break;
+
+    case 9:
+        /* them */
+        voc_push_objlist(voc, voc->voccxthm, voc->voccxthc);
+
+        /* 
+         *   return directly, since we've already pushed the result (it's
+         *   a list, not an object) 
+         */
+        return;
+
+    default:
+        /* invalid argument */
+        runsig1(ctx->bifcxrun, ERR_INVVBIF, ERRTSTR, "parserGetObj");
+        break;
+    }
+
+    /* return the object */
+    runpobj(ctx->bifcxrun, obj);
+}
+
+/*
+ *   parseNounList - parse a noun list.  Call like this:
+ *   
+ *   parserParseNounList(wordlist, typelist, starting_index, complain,
+ *   multi, check_actor); 
+ */
+void bifparsenl(bifcxdef *ctx, int argc)
+{
+    /* check the argument count */
+    bifcntargs(ctx, 6, argc);
+
+    /* call the parser */
+    voc_parse_np(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ *   parserTokenize - given a string, produce a list of tokens.  Returns
+ *   nil on error, or a list of token strings.
+ *   
+ *   parserTokenize(commandString); 
+ */
+void bifprstok(bifcxdef *ctx, int argc)
+{
+    /* check arguments */
+    bifcntargs(ctx, 1, argc);
+
+    /* call the parser */
+    voc_parse_tok(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ *   parserGetTokTypes - given a list of tokens (represented as strings),
+ *   get a corresponding list of token types.
+ *   
+ *   parserGetTokTypes(tokenList); 
+ */
+void bifprstoktyp(bifcxdef *ctx, int argc)
+{
+    /* check arguments */
+    bifcntargs(ctx, 1, argc);
+
+    /* call the parser */
+    voc_parse_types(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ *   parserDictLookup - given a list of tokens and their types, produce a
+ *   list of all of the objects that match all of the words.
+ *   
+ *   parserDictLookup(tokenList, typeList); 
+ */
+void bifprsdict(bifcxdef *ctx, int argc)
+{
+    /* check arguments */
+    bifcntargs(ctx, 2, argc);
+
+    /* call the parser */
+    voc_parse_dict_lookup(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ *   parserResolveObjects - resolve an object list of the sort returned by
+ *   parseNounList.  Validates and disambiguates the objects.
+ *   
+ *   parserResolveObjects(actor, verb, prep, otherobj, usageType,
+ *   verprop, tokenList, objList, silent) 
+ */
+void bifprsrslv(bifcxdef *ctx, int argc)
+{
+    /* check arguments */
+    bifcntargs(ctx, 9, argc);
+
+    /* call the parser */
+    voc_parse_disambig(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ *   parserReplaceCommand - replace the current command line with a new
+ *   string.  Aborts the current command. 
+ */
+void bifprsrplcmd(bifcxdef *ctx, int argc)
+{
+    /* check arguments */
+    bifcntargs(ctx, 1, argc);
+
+    /* call the parser */
+    voc_parse_replace_cmd(ctx->bifcxrun->runcxvoc);
+}
+
+/*
+ *   exitobj - throw a RUNEXITOBJ error 
+ */
+void bifexitobj(bifcxdef *ctx, int argc)
+{
+    /* no arguments are allowed */
+    bifcntargs(ctx, 0, argc);
+
+    /* throw the RUNEXITOBJ error */
+    errsig(ctx->bifcxerr, ERR_RUNEXITOBJ);
+}
+
+/*
+ *   Standard system button labels for bifinpdlg() 
+ */
+#define BIFINPDLG_LBL_OK      1
+#define BIFINPDLG_LBL_CANCEL  2
+#define BIFINPDLG_LBL_YES     3
+#define BIFINPDLG_LBL_NO      4
+
+/*
+ *   inputdialog 
+ */
+void bifinpdlg(bifcxdef *ctx, int argc)
+{
+    uchar *p;
+    char prompt[256];
+    char lblbuf[256];
+    char *labels[10];
+    char *dst;
+    char *xp;
+    uint len;
+    size_t bcnt;
+    int default_resp, cancel_resp;
+    int resp;
+    int std_btns;
+    int icon_id;
+
+    /* check for proper arguments */
+    bifcntargs(ctx, 5, argc);
+
+    /* get the icon number */
+    icon_id = runpopnum(ctx->bifcxrun);
+
+    /* get the prompt string */
+    p = runpopstr(ctx->bifcxrun);
+    bifcstr(ctx, prompt, (size_t)sizeof(prompt), p);
+
+    /* translate from internal to local characters */
+    for (xp = prompt ; *xp != '\0' ; xp++)
+        *xp = (char)cmap_i2n(*xp);
+
+    /* check for a standard button set selection */
+    if (runtostyp(ctx->bifcxrun) == DAT_NUMBER)
+    {
+        /* get the standard button set ID */
+        std_btns = runpopnum(ctx->bifcxrun);
+
+        /* there are no actual buttons */
+        bcnt = 0;
+    }
+    else
+    {
+        /* we're not using standard buttons */
+        std_btns = 0;
+        
+        /* get the response string list */
+        p = runpoplst(ctx->bifcxrun);
+        len = osrp2(p);
+        p += 2;
+        
+        /* build our internal button list */
+        for (bcnt = 0, dst = lblbuf ; len != 0 ; lstadv(&p, &len))
+        {
+            /* see what we have */
+            if (*p == DAT_SSTRING)
+            {
+                /* it's a label string - convert to a C string */
+                bifcstr(ctx, dst, sizeof(lblbuf) - (dst - lblbuf), p + 1);
+
+                /* translate from internal to local characters */
+                for (xp = dst ; *xp != '\0' ; xp++)
+                    *xp = (char)cmap_i2n(*xp);
+                
+                /* set this button to point to the converted text */
+                labels[bcnt++] = dst;
+                
+                /* move past this label in the button buffer */
+                dst += strlen(dst) + 1;
+            }
+            else if (*p == DAT_NUMBER)
+            {
+                int id;
+                int resid;
+                
+                /* it's a standard system label ID - get the ID */
+                id = (int)osrp4s(p + 1);
+
+                /* translate it to the appropriate string resource */
+                switch(id)
+                {
+                case BIFINPDLG_LBL_OK:
+                    resid = RESID_BTN_OK;
+                    break;
+                    
+                case BIFINPDLG_LBL_CANCEL:
+                    resid = RESID_BTN_CANCEL;
+                    break;
+                    
+                case BIFINPDLG_LBL_YES:
+                    resid = RESID_BTN_YES;
+                    break;
+                    
+                case BIFINPDLG_LBL_NO:
+                    resid = RESID_BTN_NO;
+                    break;
+
+                default:
+                    resid = 0;
+                    break;
+                }
+
+                /* 
+                 *   if we got a valid resource ID, load the resource;
+                 *   otherwise, skip this button
+                 */
+                if (resid != 0
+                    && !os_get_str_rsc(resid, dst,
+                                       sizeof(lblbuf) - (dst - lblbuf)))
+                {
+                    /* set this button to point to the converted text */
+                    labels[bcnt++] = dst;
+
+                    /* move past this label in the button buffer */
+                    dst += strlen(dst) + 1;
+                }
+            }
+            
+            /* if we have exhausted our label array, stop now */
+            if (bcnt >= sizeof(labels)/sizeof(labels[0])
+                || dst >= lblbuf + sizeof(lblbuf))
+                break;
+        }
+    }
+
+    /* get the default response */
+    if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+    {
+        rundisc(ctx->bifcxrun);
+        default_resp = 0;
+    }
+    else
+        default_resp = runpopnum(ctx->bifcxrun);
+
+    /* get the cancel response */
+    if (runtostyp(ctx->bifcxrun) == DAT_NIL)
+    {
+        rundisc(ctx->bifcxrun);
+        cancel_resp = 0;
+    }
+    else
+        cancel_resp = runpopnum(ctx->bifcxrun);
+
+    /* flush output before showing the dialog */
+    tioflushn(ctx->bifcxtio, 0);
+
+    /* show the dialog */
+    resp = tio_input_dialog(icon_id, prompt, std_btns,
+                            (const char **)labels, bcnt,
+                            default_resp, cancel_resp);
+
+    /* return the result */
+    runpnum(ctx->bifcxrun, resp);
+}
+
+/*
+ *   Determine if a resource exists 
+ */
+void bifresexists(bifcxdef *ctx, int argc)
+{
+    uchar *p;
+    char resname[OSFNMAX];
+    appctxdef *appctx;
+    int found;
+    runsdef val;
+    
+    /* check for proper arguments */
+    bifcntargs(ctx, 1, argc);
+
+    /* get the resource name string */
+    p = runpopstr(ctx->bifcxrun);
+    bifcstr(ctx, resname, (size_t)sizeof(resname), p);
+
+    /* 
+     *   if we have a host application context, and it provides a resource
+     *   finder function, ask the resource finder if the resource is
+     *   available; otherwise, report that the resource is not loadable,
+     *   since we must not be running a version of the interpreter that
+     *   supports external resource loading 
+     */
+    appctx = ctx->bifcxappctx;
+    found = (appctx != 0
+             && appctx->resfile_exists != 0
+             && (*appctx->resfile_exists)(appctx->resfile_exists_ctx,
+                                          resname, strlen(resname)));
+
+    /* push the result */
+    runpush(ctx->bifcxrun, runclog(found), &val);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/debug.cpp b/engines/glk/tads/tads2/debug.cpp
new file mode 100644
index 0000000..94e0b37
--- /dev/null
+++ b/engines/glk/tads/tads2/debug.cpp
@@ -0,0 +1,522 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/debug.h"
+#include "glk/tads/tads2/list.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/* add a string to the history buffer */
+void dbgaddhist(dbgcxdef *ctx, char *buf, int l)
+{
+    char *p;
+    int   dell;
+
+    if (ctx->dbgcxhstf + l + 1 >= ctx->dbgcxhstl)
+    {
+        /* delete first lines until we have enough space */
+        for (dell = 0, p = ctx->dbgcxhstp ; *p || dell < l ; ++p, ++dell) ;
+        if (*p) ++p;
+        memmove(ctx->dbgcxhstp, ctx->dbgcxhstp + dell,
+                (size_t)(ctx->dbgcxhstf - dell));
+        ctx->dbgcxhstf -= dell;
+    }
+    memcpy(ctx->dbgcxhstp + ctx->dbgcxhstf, buf, (size_t)l);
+    ctx->dbgcxhstf += l;
+}
+
+/* callback for dbgent - saves history line to a char buffer */
+static void dbgent1(void *ctx0, const char *str, int strl)
+{
+    char **ctx = (char **)ctx0;
+    memcpy(*ctx, str, (size_t)strl);
+    *ctx += strl;
+}
+
+void dbgent(dbgcxdef *ctx, runsdef *bp, objnum self, objnum target,
+            prpnum prop, int binum, int argc)
+{
+    dbgfdef *p;
+    
+    ++(ctx->dbgcxdep);                            /* increment actual depth */
+    if (ctx->dbgcxfcn == DBGMAXFRAME)
+    {
+        --(ctx->dbgcxfcn);                             /* back to top frame */
+        memmove(ctx->dbgcxfrm, ctx->dbgcxfrm + 1,
+                (size_t)((DBGMAXFRAME - 1) * sizeof(dbgfdef)));
+    }
+    p = &ctx->dbgcxfrm[ctx->dbgcxfcn];
+    ++(ctx->dbgcxfcn);                           /* increment frame pointer */
+
+    p->dbgfbp = bp;
+    p->dbgfself = self;
+    p->dbgftarg = target;
+    p->dbgfprop = prop;
+    p->dbgfbif  = binum;
+    p->dbgfargc = argc;
+    p->dbgffr = 0;                        /* no frame has yet been recorded */
+    p->dbgflin = 0;
+
+    /* save call history */
+    if (ctx->dbgcxflg & DBGCXFTRC)
+    {
+        char  buf[128];
+        char *tmp;
+        int   l;
+
+        tmp = buf;
+        dbgstktr(ctx, dbgent1, &tmp, -1, TRUE, FALSE);
+        if ((l = (tmp - buf)) > 0 && buf[l-1] == '\n') --l;
+        buf[l++] = '\0';
+        dbgaddhist(ctx, buf, l);
+    }
+}
+
+void dbglv(dbgcxdef *ctx, int exittype)
+{
+    --(ctx->dbgcxdep);                            /* decrement actual depth */
+    if (ctx->dbgcxfcn) --(ctx->dbgcxfcn);        /* decrement frame pointer */
+
+    /* 
+     *   if we're in STEP OUT/OVER mode, and the target context is level
+     *   0, and we're now at level 0, it means that we are stepping out of
+     *   a routine called directly by the system and the debugger is
+     *   supposed to break when that happens -- return to single-stepping
+     *   mode so that we break into the debugger the next time the system
+     *   calls a method 
+     */
+    if ((ctx->dbgcxflg & DBGCXFSS) != 0
+        && (ctx->dbgcxflg & DBGCXFSO) != 0
+        && ctx->dbgcxsof == 0 && ctx->dbgcxdep == 0)
+    {
+        /* 
+         *   stepping out/over at level 0 - go to normal single-step mode
+         *   (clear the out/over flag) 
+         */
+        ctx->dbgcxflg &= ~DBGCXFSO;
+    }
+
+    /* record exit in call history if appropriate */
+    if (ctx->dbgcxflg & DBGCXFTRC)
+    {
+        char   buf[128];
+        char  *p;
+
+        switch(exittype)
+        {
+        case DBGEXVAL:
+            if (ctx->dbgcxfcn > 1)
+            {
+                memset(buf, ' ', (size_t)(ctx->dbgcxfcn - 1));
+                dbgaddhist(ctx, buf, (int)ctx->dbgcxfcn - 1);
+            }
+            memcpy(buf, " => ", (size_t)4);
+            p = buf + 4;
+            dbgpval(ctx, ctx->dbgcxrun->runcxsp - 1,
+                    dbgent1, &p, TRUE);
+            *p++ = '\0';
+            dbgaddhist(ctx, buf, (int)(p - buf));
+            break;
+
+        case DBGEXPASS:
+            memcpy(buf, " [pass]", (size_t)8);
+            dbgaddhist(ctx, buf, 8);
+            break;
+        }
+    }
+}
+
+/* get a symbol name; returns length of name */
+int dbgnam(dbgcxdef *ctx, char *outbuf, int typ, int val)
+{
+    toksdef  sym;
+
+    if (!ctx->dbgcxtab || !ctx->dbgcxtab->tokthhsh)
+    {
+        memcpy(outbuf, "<NO SYMBOL TABLE>", (size_t)17);
+        return(17);
+    }
+    
+    if (tokthfind((toktdef *)ctx->dbgcxtab, typ, val, &sym))
+    {
+        memcpy(outbuf, sym.toksnam, (size_t)sym.tokslen);
+        return(sym.tokslen);
+    }
+    else if (typ == TOKSTOBJ)
+    {
+        if ((mcmon)val == MCMONINV)
+        {
+            memcpy(outbuf, "<invalid object>", 16);
+            return 16;
+        }
+        else
+        {
+            sprintf(outbuf, "<object#%u>", val);
+            return strlen(outbuf);
+        }
+    }
+    else
+    {
+        memcpy(outbuf, "<UNKNOWN>", (size_t)9);
+        return(9);
+    }
+}
+
+/* send a buffer value (as from a list) to ui callback for display */
+static void dbgpbval(dbgcxdef *ctx, dattyp typ, uchar *val,
+                     void (*dispfn)(void *, const char *, int),
+                     void *dispctx)
+{
+    char  buf[TOKNAMMAX + 1];
+    char *p = buf;
+    uint  len;
+
+    switch(typ)
+    {
+    case DAT_NUMBER:
+        sprintf(buf, "%ld", (long)osrp4s(val));
+        len = strlen(buf);
+        break;
+        
+    case DAT_OBJECT:
+        len = dbgnam(ctx, buf, TOKSTOBJ, osrp2(val));
+        break;
+        
+    case DAT_SSTRING:
+        len = osrp2(val) - 2;
+        p = (char *)val + 2;
+        break;
+        
+    case DAT_NIL:
+        p = "nil";
+        len = 3;
+        break;
+
+    case DAT_LIST:
+        (*dispfn)(dispctx, "[", 1);
+        len = osrp2(val) - 2;
+        p = (char *)val + 2;
+        while (len)
+        {
+            dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
+            lstadv((uchar **)&p, &len);
+            if (len) (*dispfn)(dispctx, " ", 1);
+        }
+        (*dispfn)(dispctx, "]", 1);
+        len = 0;
+        break;
+        
+    case DAT_TRUE:
+        p = "true";
+        len = 4;
+        break;
+        
+    case DAT_FNADDR:
+        (*dispfn)(dispctx, "&", 1);
+        len = dbgnam(ctx, buf, TOKSTFUNC, osrp2(val));
+        break;
+        
+    case DAT_PROPNUM:
+        (*dispfn)(dispctx, "&", 1);
+        len = dbgnam(ctx, buf, TOKSTPROP, osrp2(val));
+        break;
+        
+    default:
+        p = "[unknown type]";
+        len = 14;
+        break;
+    }
+
+    if (typ == DAT_SSTRING) (*dispfn)(dispctx, "'", 1);
+    if (len) (*dispfn)(dispctx, p, len);
+    if (typ == DAT_SSTRING) (*dispfn)(dispctx, "'", 1);
+}
+
+/* send a value to ui callback for display */
+void dbgpval(dbgcxdef *ctx, runsdef *val,
+             void (*dispfn)(void *ctx, const char *str, int strl),
+             void *dispctx,
+             int showtype)
+{
+    uchar   buf[TOKNAMMAX + 1];
+    uint    len;
+    uchar  *p = buf;
+    char   *typ = 0;
+    
+    switch(val->runstyp)
+    {
+    case DAT_NUMBER:
+        sprintf((char *)buf, "%ld", val->runsv.runsvnum);
+        len = strlen((char *)buf);
+        typ = "number";
+        break;
+        
+    case DAT_OBJECT:
+        len = dbgnam(ctx, (char *)buf, TOKSTOBJ, val->runsv.runsvobj);
+        typ = "object";
+        break;
+        
+    case DAT_SSTRING:
+        len = osrp2(val->runsv.runsvstr) - 2;
+        p = val->runsv.runsvstr + 2;
+        typ = "string";
+        break;
+        
+    case DAT_NIL:
+        p = (uchar *)"nil";
+        len = 3;
+        break;
+
+    case DAT_LIST:
+        if (showtype) (*dispfn)(dispctx, "list: ", 6);
+        (*dispfn)(dispctx, "[", 1);
+        len = osrp2(val->runsv.runsvstr) - 2;
+        p = val->runsv.runsvstr + 2;
+        while (len)
+        {
+            dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
+            lstadv(&p, &len);
+            if (len) (*dispfn)(dispctx, " ", 1);
+        }
+        (*dispfn)(dispctx, "]", 1);
+        len = 0;
+        break;
+        
+    case DAT_TRUE:
+        p = (uchar *)"true";
+        len = 4;
+        break;
+        
+    case DAT_FNADDR:
+        len = dbgnam(ctx, (char *)buf, TOKSTFUNC, val->runsv.runsvobj);
+        typ = "function pointer";
+        break;
+        
+    case DAT_PROPNUM:
+        len = dbgnam(ctx, (char *)buf, TOKSTPROP, val->runsv.runsvprp);
+        typ = "property pointer";
+        break;
+        
+    default:
+        p = (uchar *)"[unknown type]";
+        len = 14;
+        break;
+    }
+
+    /* show the type prefix if desired, or add a quote if it's a string */
+    if (typ && showtype)
+    {
+        /* show the type prefix */
+        (*dispfn)(dispctx, typ, (int)strlen(typ));
+        (*dispfn)(dispctx, ": ", 2);
+    }
+    else if (val->runstyp == DAT_SSTRING)
+    {
+        /* it's a string, and we're not showing a type - add a quote */
+        (*dispfn)(dispctx, "'", 1);
+    }
+
+    /* 
+     *   if possible, null-terminate the buffer - do this only if the
+     *   length is actually within the buffer, which won't be the case if
+     *   the text comes from someplace outside the buffer (which is the
+     *   case if it's a string, for example) 
+     */
+    if (len < sizeof(buf))
+        buf[len] = '\0';
+
+    /* display a "&" prefix if it's an address of some kind */
+    if (val->runstyp == DAT_PROPNUM || val->runstyp == DAT_FNADDR)
+        (*dispfn)(dispctx, "&", 1);
+
+    /* display the text */
+    if (len != 0)
+        (*dispfn)(dispctx, (char *)p, len);
+
+    /* add a closing quote if it's a string and we showed an open quote */
+    if (val->runstyp == DAT_SSTRING && !(typ && showtype))
+        (*dispfn)(dispctx, "'", 1);
+}
+
+void dbgstktr(dbgcxdef *ctx,
+              void (*dispfn)(void *ctx, const char *str, int strl),
+              void *dispctx,
+              int level, int toponly, int include_markers)
+{
+    dbgfdef *f;
+    int      i;
+    int      j;
+    int      k;
+    char     buf[128];
+    char    *p;
+    char     c;
+
+    for (i = ctx->dbgcxfcn, j = ctx->dbgcxdep, f = &ctx->dbgcxfrm[i-1]
+         ; i ; --f, --j, --i)
+    {
+        p = buf;
+        if (toponly)
+        {
+            int v = (i < 50 ? i : 50);
+
+            if (v > 1)
+            {
+                memset(buf, ' ', (size_t)(v - 1));
+                dbgaddhist(ctx, buf, v-1);
+            }
+        }
+        else if (include_markers)
+        {
+            c = (i == level + 1 ? '*' : ' ');
+            sprintf(buf, "%3d%c  ", j, c);
+            p += 4;
+        }
+
+        if (f->dbgftarg == MCMONINV)
+            p += dbgnam(ctx, p, TOKSTBIFN, f->dbgfbif);
+        else
+            p += dbgnam(ctx, p,
+                        (f->dbgfself == MCMONINV ? TOKSTFUNC : TOKSTOBJ),
+                        (int)f->dbgftarg);
+
+        if (f->dbgfself != MCMONINV && f->dbgfself != f->dbgftarg)
+        {
+            memcpy(p, "<self=", (size_t)6);
+            p += 6;
+            p += dbgnam(ctx, p, TOKSTOBJ, (int)f->dbgfself);
+            *p++ = '>';
+        }
+        if (f->dbgfprop)
+        {
+            *p++ = '.';
+            p += dbgnam(ctx, p, TOKSTPROP, (int)f->dbgfprop);
+        }
+        
+        /* display what we have so far */
+        *p++ = '\0';
+        (*dispfn)(dispctx, buf, (int)strlen(buf));
+
+        /* display arguments if there are any */
+        if (f->dbgfself == MCMONINV || f->dbgfargc != 0)
+        {
+            (*dispfn)(dispctx, "(", 1);
+            for (k = 0 ; k < f->dbgfargc ; ++k)
+            {
+                dbgpval(ctx, f->dbgfbp - k - 2, dispfn, dispctx, FALSE);
+                if (k + 1 < f->dbgfargc) (*dispfn)(dispctx, ", ", 2);
+            }
+            (*dispfn)(dispctx, ")", 1);
+        }
+        
+        /* send out a newline, then move on to next frame */
+        (*dispfn)(dispctx, "\n", 1);
+
+        /* we're done if doing one function only */
+        if (toponly) break;
+    }
+}
+
+static void dbgdsdisp(void *ctx, const char *buf, int bufl)
+{
+    if (buf[0] == '\n')
+        tioflush((tiocxdef *)ctx);
+    else
+        tioputslen((tiocxdef *)ctx, (char *)buf, bufl);
+}
+
+/* dump the stack */
+void dbgds(dbgcxdef *ctx)
+{
+    /* don't do stack dumps if we're running from the debugger command line */
+    if (ctx->dbgcxflg & DBGCXFIND) return;
+
+    tioflush(ctx->dbgcxtio);
+    tioshow(ctx->dbgcxtio);
+    dbgstktr(ctx, dbgdsdisp, ctx->dbgcxtio, -1, FALSE, TRUE);
+    tioflush(ctx->dbgcxtio);
+    ctx->dbgcxfcn = ctx->dbgcxdep = 0;
+}
+
+/* get information about the currently executing source line */
+void dbglget(dbgcxdef *ctx, uchar *buf)
+{
+    dbglgetlvl(ctx, buf, 0);
+}
+
+/* 
+ *   Get information about a source line at a particular stack level into
+ *   the buffer; leaves out frame info.  level 0 is the currently
+ *   executing line, 1 is the first enclosing level, and so on.  
+ */
+int dbglgetlvl(dbgcxdef *ctx, uchar *buf, int level)
+{
+    uchar   *linrec;
+    uchar   *obj;
+    dbgfdef *fr;
+
+    /* make sure the level is valid */
+    if (level > ctx->dbgcxfcn - 1)
+        return 1;
+
+    /* get the frame at the given level */
+    fr = &ctx->dbgcxfrm[ctx->dbgcxfcn - level - 1];
+
+    /* if we're in an intrinsic, go to enclosing frame */
+    if (fr->dbgftarg == MCMONINV) --fr;
+    
+    /* make sure we've encountered an OPCLINE in this frame */
+    if (fr->dbgflin == 0)
+        return 1;
+
+    /* we need to read from the target object - lock it */
+    obj = mcmlck(ctx->dbgcxmem, (mcmon)fr->dbgftarg);
+
+    linrec = obj + fr->dbgflin;
+    memcpy(buf, linrec + 3, (size_t)(*linrec - 3));
+    
+    /* no longer need the target object locked */
+    mcmunlck(ctx->dbgcxmem, (mcmon)fr->dbgftarg);
+
+    /* success */
+    return 0;
+}
+
+/* tell the line source the location of the current line being compiled */
+void dbgclin(tokcxdef *tokctx, objnum objn, uint ofs)
+{
+    uchar buf[4];
+    
+    /* package the information and send it to the line source */
+    oswp2(buf, objn);
+    oswp2(buf + 2, ofs);
+    lincmpinf(tokctx->tokcxlin, buf);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp
index 793ddcc..eeb59cd 100644
--- a/engines/glk/tads/tads2/error.cpp
+++ b/engines/glk/tads/tads2/error.cpp
@@ -21,11 +21,185 @@
  */
 
 #include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/tads2/tokenizer.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
+/* format an error message, sprintf-style, using an erradef array */
+int errfmt(char *outbuf, int outbufl, const char *fmt, int argc, const erradef *argv) {
+    int    outlen = 0;
+    int    argi   = 0;
+    int    len = 0;
+    char   buf[20];
+	const char  *p = nullptr;
+    char   fmtchar;
+
+    while (*fmt)
+    {
+        switch(*fmt)
+        {
+        case '\\':
+            ++fmt;
+            len = 1;
+            switch(*fmt)
+            {
+            case '\0':
+                --fmt;
+                break;
+            case '\n':
+                p = "\n";
+                break;
+            case '\t':
+                p = "\t";
+                break;
+            default:
+                p = fmt;
+                break;
+            }
+            break;
+            
+        case '%':
+            ++fmt;
+            fmtchar = *fmt;
+            if (argi >= argc) fmtchar = 1;          /* too many - ignore it */
+            switch(fmtchar)
+            {
+            case '\0':
+                --fmt;
+                break;
+                
+            case '%':
+                p = "%";
+                len = 1;
+                break;
+                
+            case 'd':
+                sprintf(buf, "%d", argv[argi].erraint);
+                len = strlen(buf);
+                p = buf;
+                break;
+                
+            case 's':
+                p = argv[argi].errastr;
+                len = strlen(p);
+                break;
+                
+            case 't':
+                {
+                    int i;
+                    static struct
+                    {
+                        int   tokid;
+                        char *toknam;
+                    } toklist[] =
+                    {
+                        { TOKTSEM, "semicolon" },
+                        { TOKTCOLON, "colon" },
+                        { TOKTFUNCTION, "\"function\"" },
+                        { TOKTCOMMA, "comma" },
+                        { TOKTLBRACE, "left brace ('{')" },
+                        { TOKTRPAR, "right paren (')')" },
+                        { TOKTRBRACK, "right square bracket (']')" },
+                        { TOKTWHILE, "\"while\"" },
+                        { TOKTLPAR, "left paren ('(')" },
+                        { TOKTEQ, "'='" },
+                        { 0, (char *)0 }
+                    };
+                    
+                    for (i = 0 ; toklist[i].toknam ; ++i)
+                    {
+                        if (toklist[i].tokid == argv[argi].erraint)
+                        {
+                            p = toklist[i].toknam;
+                            break;
+                        }
+                    }
+                    if (!toklist[i].toknam)
+                        p = "<unknown token>";
+                    len = strlen(p);
+                    break;
+                }
+                
+            default:
+                p = "";
+                len = 0;
+                --argi;
+                break;
+            }
+            ++argi;
+            break;
+            
+        default:
+            p = fmt;
+            len = 1;
+            break;
+        }
+
+        /* copy output that was set up above */
+        if (len)
+        {
+            if (outbufl >= len)
+            {
+                memcpy(outbuf, p, (size_t)len);
+                outbufl -= len;
+                outbuf += len;
+            }
+            else if (outbufl)
+            {
+                memcpy(outbuf, p, (size_t)outbufl);
+                outbufl = 0;
+            }
+            outlen += len;
+        }
+        ++fmt;
+    }
+    
+    if (outbufl) *outbuf++ = '\0';
+    return(outlen);
+}
+
+#ifdef DEBUG
+void errjmp(buf, e)
+jmp_buf buf;
+int     e;
+{
+    longjmp(buf, e);
+}
+#endif /* DEBUG */
+
+
+
+#ifdef ERR_NO_MACRO
+
+/* base error signal function */
+void errsign(errcxdef *ctx, int e) {
+    ctx->errcxofs = 0;
+#if 0
+	longjmp(ctx->errcxptr->errbuf, e);
+#else
+	error("errsign");
+#endif
+}
+
+/* enter a string argument */
+char *errstr(errcxdef *ctx, char *str, int len) {
+    char *ret = &ctx->errcxbuf[ctx->errcxofs];
+    
+    memcpy(ret, str, (size_t)len);
+    ret[len] = '\0';
+    ctx->errcxofs += len + 1;
+    return(ret);
+}
+
+/* log an error: base function */
+void errlogn(errcxdef *ctx, int err) {
+	error("errlogn");
+}
+
+#endif /* ERR_NO_MACRO */
 
 } // End of namespace TADS2
 } // End of namespace TADS
diff --git a/engines/glk/tads/tads2/error_handling.cpp b/engines/glk/tads/tads2/error_handling.cpp
index a2300ee..b71fc8c 100644
--- a/engines/glk/tads/tads2/error_handling.cpp
+++ b/engines/glk/tads/tads2/error_handling.cpp
@@ -20,13 +20,190 @@
  *
  */
 
-#include "error_handling.h"
+#include "glk/tads/tads2/error_handling.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
 
+/* format an error message, sprintf-style, using an erradef array */
+int errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv)
+{
+    int    outlen = 0;
+    int    argi   = 0;
+    int    len;
+    char   buf[20];
+	char  *p = nullptr;
+    char   fmtchar;
+
+    while (*fmt != '\0' && outbufl > 1)
+    {
+        switch(*fmt)
+        {
+        case '\\':
+            ++fmt;
+            len = 1;
+            switch(*fmt)
+            {
+            case '\0':
+                --fmt;
+                break;
+            case '\n':
+                p = "\n";
+                break;
+            case '\t':
+                p = "\t";
+                break;
+            default:
+                p = fmt;
+                break;
+            }
+            break;
+            
+        case '%':
+            ++fmt;
+            fmtchar = *fmt;
+            if (argi >= argc) fmtchar = 1;          /* too many - ignore it */
+            switch(fmtchar)
+            {
+            case '\0':
+                --fmt;
+                len = 0;
+                break;
+                
+            case '%':
+                p = "%";
+                len = 1;
+                break;
+                
+            case 'd':
+                sprintf(buf, "%d", argv[argi].erraint);
+                len = strlen(buf);
+                p = buf;
+                break;
+                
+            case 'u':
+                sprintf(buf, "%u", argv[argi].erraint);
+                len = strlen(buf);
+                p = buf;
+                break;
+                
+            case 's':
+                p = argv[argi].errastr;
+                len = strlen(p);
+                break;
+                
+            default:
+                p = "";
+                len = 0;
+                --argi;
+                break;
+            }
+            ++argi;
+            break;
+            
+        default:
+            p = fmt;
+            len = 1;
+            break;
+        }
+
+        /* copy output that was set up above */
+        if (len != 0)
+        {
+            if (outbufl >= len)
+            {
+                memcpy(outbuf, p, (size_t)len);
+                outbufl -= len;
+                outbuf += len;
+            }
+            else if (outbufl > 1)
+            {
+                memcpy(outbuf, p, (size_t)outbufl - 1);
+                outbufl = 1;
+            }
+            outlen += len;
+        }
+        ++fmt;
+    }
+
+    /* add a null terminator */
+    if (outbufl != 0)
+        *outbuf++ = '\0';
+
+    /* return the length */
+    return outlen;
+}
+
+#if defined(DEBUG) && !defined(ERR_NO_MACRO)
+void errjmp(jmp_buf buf, int e)
+{
+    longjmp(buf, e);
+}
+#endif /* DEBUG */
+
+
+
+#ifdef ERR_NO_MACRO
+
+/* base error signal function */
+void errsign(errcxdef *ctx, int e, char *facility)
+{
+    strncpy(ctx->errcxptr->errfac, facility, ERRFACMAX);
+    ctx->errcxptr->errfac[ERRFACMAX] = '\0';
+    ctx->errcxofs = 0;
+#if 0
+	longjmp(ctx->errcxptr->errbuf, e);
+#else
+	error("Error - %s", facility);
+#endif
+}
+
+/* signal an error with no arguments */
+void errsigf(errcxdef *ctx, char *facility, int e)
+{
+    errargc(ctx, 0);
+    errsign(ctx, e, facility);
+}
+
+/* enter a string argument */
+char *errstr(errcxdef *ctx, const char *str, int len)
+{
+    char *ret = &ctx->errcxbuf[ctx->errcxofs];
+    
+    memcpy(ret, str, (size_t)len);
+    ret[len] = '\0';
+    ctx->errcxofs += len + 1;
+    return(ret);
+}
+
+/* resignal current error */
+void errrse1(errcxdef *ctx, errdef *fr)
+{
+    errargc(ctx, fr->erraac);
+    memcpy(ctx->errcxptr->erraav, fr->erraav,
+           (size_t)(fr->erraac * sizeof(erradef)));
+    errsign(ctx, fr->errcode, fr->errfac);
+}
+
+/* log an error: base function */
+void errlogn(errcxdef *ctx, int err, char *facility)
+{
+    ctx->errcxofs = 0;
+    (*ctx->errcxlog)(ctx->errcxlgc, facility, err, ctx->errcxptr->erraac,
+                     ctx->errcxptr->erraav);
+}
+
+/* log an error with no arguments */
+void errlogf(errcxdef *ctx, char *facility, int err)
+{
+    errargc(ctx, 0);
+    errlogn(ctx, err, facility);
+}
+
+#endif /* ERR_NO_MACRO */
+
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/tads2/os.cpp b/engines/glk/tads/tads2/os.cpp
index e51a4d9..219bd3b 100644
--- a/engines/glk/tads/tads2/os.cpp
+++ b/engines/glk/tads/tads2/os.cpp
@@ -21,333 +21,16 @@
  */
 
 #include "glk/tads/tads2/os.h"
+#include "common/system.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-#if 0
-OS::OS(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gameDesc),
-		status_mode(0) {
-	Common::fill(&status_left[0], &status_left[OSS_STATUS_STRING_LEN], '\0');
-	Common::fill(&status_right[0], &status_right[OSS_STATUS_STRING_LEN], '\0');
+long os_get_sys_clock_ms() {
+	return g_system->getMillis();
 }
 
-void OS::os_terminate(int rc) {
-	glk_exit();
-}
-
-uint OS::oss_convert_prompt_type(int type) {
-	if (type == OS_AFP_OPEN)
-		return filemode_Read;
-	return filemode_ReadWrite;
-}
-
-uint OS::oss_convert_file_type(int type) {
-	if (type == OSFTSAVE)
-		return fileusage_SavedGame;
-	if (type == OSFTLOG || type == OSFTTEXT)
-		return fileusage_Transcript;
-	return fileusage_Data;
-}
-
-uint OS::oss_convert_fileref_to_string(frefid_t file_to_convert, char *buffer, int buf_len) {
-#ifdef TODO
-	char   temp_string[32];
-	uint value, i = 0, digit,
-		digit_flag = false,     // Have we put a digit in the string yet?
-		divisor = 1e9;          // The max 32-bit integer is 4294967295
-
-	// This could probably be done by using sprintf("%s%ld%s") but I don't want to risk it
-	value = (uint)file_to_convert;
-	while (divisor != 1) {
-		digit = (char)(value / divisor);
-		if (digit != 0 || digit_flag) {     // This lets us handle, eg, 102
-			temp_string[i++] = digit + '0';
-			digit_flag = true;
-		}
-		value = value % divisor;
-		divisor /= 10;
-	}
-	temp_string[i++] = (char)value + '0';
-	temp_string[i] = 0;
-	if (strlen(temp_string) + strlen(OSS_FILEREF_STRING_PREFIX) +
-		strlen(OSS_FILEREF_STRING_SUFFIX) > (size_t)buf_len)
-		return false;
-	sprintf(buffer, "%s%s%s", OSS_FILEREF_STRING_PREFIX,
-		temp_string, OSS_FILEREF_STRING_SUFFIX);
-#endif
-	return true;
-}
-
-frefid_t OS::oss_convert_string_to_fileref(char *buffer, uint usage) {
-#ifdef TODO
-	char temp_string[32];
-	uint value = 0, i, multiplier = 1;
-
-	// Does the buffer contain a hashed fileref?
-	if (oss_is_string_a_fileref(buffer)) {
-		// If so, we need only decode the string in the middle and return its value
-		strcpy(temp_string, buffer + strlen(OSS_FILEREF_STRING_PREFIX));
-		i = strlen(temp_string) - strlen(OSS_FILEREF_STRING_SUFFIX);
-		temp_string[i] = 0;
-		while (i != 0) {
-			i--;
-			value += ((uint)(temp_string[i] - '0') * multiplier);
-			multiplier *= 10;
-		}
-		return ((frefid_t)value);
-	}
-
-	// If not, return the new fileref
-	return (glk_fileref_create_by_name(usage, os_get_root_name(buffer), 0));
-#else
-	return nullptr;
-#endif
-}
-
-bool OS::oss_is_string_a_fileref(char *buffer) {
-	if ((strncmp(buffer, OSS_FILEREF_STRING_PREFIX,
-		strlen(OSS_FILEREF_STRING_PREFIX)) == 0) &&
-		(strncmp(buffer + strlen(buffer) - strlen(OSS_FILEREF_STRING_SUFFIX),
-			OSS_FILEREF_STRING_SUFFIX,
-			strlen(OSS_FILEREF_STRING_SUFFIX)) == 0))
-		return true;
-	return false;
-}
-
-unsigned char OS::oss_convert_keystroke_to_tads(uint key) {
-	// Characters 0 - 255 we return per normal */
-	if (key <= 255)
-		return ((unsigned char)key);
-	switch (key) {
-	case keycode_Up:
-		return CMD_UP;
-	case keycode_Down:
-		return CMD_DOWN;
-	case keycode_Left:
-		return CMD_LEFT;
-	case keycode_Right:
-		return CMD_RIGHT;
-	case keycode_PageUp:
-		return CMD_PGUP;
-	case keycode_PageDown:
-		return CMD_PGDN;
-	case keycode_Home:
-		return CMD_HOME;
-	case keycode_End:
-		return CMD_END;
-	case keycode_Func1:
-		return CMD_F1;
-	case keycode_Func2:
-		return CMD_F2;
-	case keycode_Func3:
-		return CMD_F3;
-	case keycode_Func4:
-		return CMD_F4;
-	case keycode_Func5:
-		return CMD_F5;
-	case keycode_Func6:
-		return CMD_F6;
-	case keycode_Func7:
-		return CMD_F7;
-	case keycode_Func8:
-		return CMD_F8;
-	case keycode_Func9:
-		return CMD_F9;
-	case keycode_Func10:
-		return CMD_F10;
-	default:
-		return 0;
-	}
-}
-
-bool OS::oss_check_path(char *filename) {
-	return false;
-}
-
-void OS::oss_revert_path() {
-	// No implementation
-}
-
-osfildef *OS::oss_open_stream(char *buffer, uint tadsusage, uint tbusage,
-		uint fmode, uint rock) {
-	frefid_t fileref;
-	strid_t osf;
-	int      changed_dirs;
-
-	fileref = oss_convert_string_to_fileref(buffer,
-		oss_convert_file_type(tadsusage) | tbusage);
-	changed_dirs = oss_check_path(buffer);
-	osf = glk_stream_open_file(fileref, (FileMode)fmode, rock);
-	if (changed_dirs)
-		oss_revert_path();
-	return *osf;
-}
-
-osfildef *OS::osfoprb(const char *fname, uint typ) {
-	Common::File *f = new Common::File();
-	if (f->open(fname))
-		return f;
-
-	f->close();
-	delete f;
-	return nullptr;
-}
-
-void OS::os_gen_charmap_filename(char *filename, const char *internal_id,
-		const char *argv0) {
-	const char *p;
-	const char *rootname;
-	size_t pathlen;
-
-	// find the path prefix of the original executable filename
-	for (p = rootname = argv0; *p != '\0'; ++p) {
-		if (*p == '/' || *p == '\\' || *p == ':')
-			rootname = p + 1;
-	}
-
-	// copy the path prefix
-	pathlen = rootname - argv0;
-	memcpy(filename, argv0, pathlen);
-
-	// if there's no trailing backslash, add one
-	if (pathlen == 0 || filename[pathlen - 1] != '\\')
-		filename[pathlen++] = '\\';
-
-	// add "win_", plus the character set ID, plus the extension
-	strcpy(filename + pathlen, "win_");
-	strcat(filename + pathlen, internal_id);
-	strcat(filename + pathlen, ".tcp");
-}
-
-void OS::oss_put_string_with_hilite(winid_t win, const char *str, size_t len) {
-	glk_set_window(win);
-	glk_put_buffer(str, len);
-}
-
-void OS::oss_draw_status_line(void) {
-	uint width, height, division;
-
-	if (status_win == nullptr) return;  // In case this is a CheapGlk port
-
-	glk_window_get_size(status_win, &width, &height);
-	if (height == 0) return;  // Don't bother if status is invisible
-	division = width - strlen(status_right) - 1;
-	glk_set_window(status_win);
-	glk_window_clear(status_win);
-	oss_put_string_with_hilite(status_win, status_left, strlen(status_left));
-	glk_window_move_cursor(status_win, division, 0);
-	glk_put_string(status_right);
-}
-
-void OS::oss_change_status_string(char *dest, const char *src, size_t len) {
-	if (len > OSS_STATUS_STRING_LEN - 1)
-		len = OSS_STATUS_STRING_LEN - 1;
-	memcpy(dest, src, len);
-	dest[len] = '\0';
-}
-
-void OS::oss_change_status_left(const char *str, size_t len) {
-	oss_change_status_string(status_left, str, len);
-	oss_draw_status_line();
-}
-
-void OS::oss_change_status_right(const char *str) {
-	oss_change_status_string(status_right, str, strlen(str));
-	oss_draw_status_line();
-}
-
-int OS::memicmp(const char *s1, const char *s2, int len) {
-	char *x1, *x2;
-	int i, result;
-
-	x1 = (char *)malloc(len); x2 = (char *)malloc(len);
-
-	if (!x1 || !x2) {
-		glk_set_window(story_win);
-		glk_put_string("memicmp has run out of memory. Quitting.\n");
-		os_waitc();
-		glk_exit();
-	}
-
-	for (i = 0; i < len; i++) {
-		if (Common::isUpper(s1[i]))
-			x1[i] = glk_char_to_lower((unsigned char)s1[i]);
-		else x1[i] = s1[i];
-
-		if (Common::isUpper(s2[i]))
-			x2[i] = glk_char_to_lower((unsigned char)s2[i]);
-		else x2[i] = s2[i];
-	}
-
-	result = memcmp(x1, x2, len);
-	free(x1);
-	free(x2);
-	return result;
-}
-
-void OS::os_flush() {
-	glk_tick();
-}
-
-void OS::os_print(const char *str, size_t len) {
-	int current_status_mode;
-
-	// Decide what to do based on our status mode
-	current_status_mode = os_get_status();
-	if (current_status_mode == OSS_STATUS_MODE_STORY) {
-		oss_put_string_with_hilite(story_win, str, len);
-	} else if (current_status_mode == OSS_STATUS_MODE_STATUS) {
-		const char *p;
-		size_t      rem;
-
-		// The string requires some fiddling for the status window
-		for (p = str, rem = len; rem != 0 && *p == '\n'; p++, --rem);
-		if (rem != 0 && p[rem - 1] == '\n')
-			--rem;
-
-		// if that leaves anything, update the statusline
-		if (rem != 0)
-			oss_change_status_left(p, rem);
-	}
-}
-
-void OS::os_expause() {
-	os_printz("(Strike any key to exit...)");
-	os_flush();
-	os_waitc();
-}
-
-int OS::oss_getc_from_window(winid_t win) {
-	static unsigned char buffered_char = 0;
-	int i;
-	event_t ev;
-
-	if (buffered_char != 0) {
-		i = (int)buffered_char;
-		buffered_char = 0;
-		return i;
-	}
-	glk_request_char_event(win);
-	do {
-		glk_select(&ev);
-		if (ev.type == evtype_Arrange)
-			oss_draw_status_line();
-	} while (ev.type != evtype_CharInput);
-	if (ev.val1 == keycode_Return)
-		ev.val1 = '\n';
-	else if (ev.val1 == keycode_Tab)
-		ev.val1 = '\t';
-	if (ev.val1 <= 255)
-		return ((int)ev.val1);
-
-	// We got a special character, so handle it appropriately
-	buffered_char = oss_convert_keystroke_to_tads(ev.val1);
-	return 0;
-}
-#endif
-
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/tads2/string_resources.h b/engines/glk/tads/tads2/string_resources.h
new file mode 100644
index 0000000..76f245e
--- /dev/null
+++ b/engines/glk/tads/tads2/string_resources.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GLK_TADS_TADS2_STRING_RESOURCES
+#define GLK_TADS_TADS2_STRING_RESOURCES
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/*
+ *   Dialog buttons.  These provide the text for standard buttons in
+ *   dialogs created with os_input_dialog().
+ *   
+ *   These labels can use "&" to indicate a shortcut letter, per the
+ *   normal os_input_dialog() interface; for example, if the Yes button
+ *   label is "&Yes", the button has the shortcut letter "Y".
+ *   
+ *   The text of these buttons may vary by system, since these should
+ *   conform to local conventions where there are local conventions.  In
+ *   addition, of course, these strings will vary by language.  
+ */
+
+/* OK and Cancel buttons */
+#define RESID_BTN_OK       1
+#define RESID_BTN_CANCEL   2
+
+/* "Yes" and "No" buttons */
+#define RESID_BTN_YES      3
+#define RESID_BTN_NO       4
+
+/*
+ *   Reply strings for the yorn() built-in function.  These strings are
+ *   regular expressions as matched by the regex.h functions.  For
+ *   English, for example, the "yes" string would be "[Yy].*" and the "no"
+ *   string would be "[Nn].*".  For German, it might be desirable to
+ *   accept both "Ja" and "Yes", so the "Yes" string might be "[JjYy].*".
+ *   
+ *   It's not necessary in these patterns to consider leading spaces,
+ *   since the yorn() function will skip any leading spaces before
+ *   performing the pattern match.  
+ */
+#define RESID_YORN_YES     5
+#define RESID_YORN_NO      6
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif


Commit: 3d9e03af554814bee10f112c2efa0b7f0b722489
    https://github.com/scummvm/scummvm/commit/3d9e03af554814bee10f112c2efa0b7f0b722489
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS2: Soooo much more implementation

Changed paths:
  A engines/glk/tads/os_buffer.cpp
  A engines/glk/tads/os_buffer.h
  A engines/glk/tads/os_frob_tads.cpp
  A engines/glk/tads/os_frob_tads.h
  A engines/glk/tads/os_glk.cpp
  A engines/glk/tads/tads2/command_line.cpp
  A engines/glk/tads/tads2/command_line.h
  A engines/glk/tads/tads2/error_message.cpp
  A engines/glk/tads/tads2/execute_command.cpp
  A engines/glk/tads/tads2/ltk.cpp
  A engines/glk/tads/tads2/ltk.h
  A engines/glk/tads/tads2/play.cpp
  A engines/glk/tads/tads2/play.h
  A engines/glk/tads/tads2/qa_scriptor.cpp
  A engines/glk/tads/tads2/runstat.cpp
  A engines/glk/tads/tads2/runtime_app.cpp
  A engines/glk/tads/tads2/runtime_app.h
  A engines/glk/tads/tads2/runtime_driver.cpp
  A engines/glk/tads/tads2/tokenizer_hash.cpp
  A engines/glk/tads/tads2/vocabulary_parser.cpp
  R engines/glk/tads/osfrobtads.cpp
  R engines/glk/tads/osfrobtads.h
    engines/glk/glk.h
    engines/glk/glulxe/glulxe.cpp
    engines/glk/glulxe/glulxe.h
    engines/glk/module.mk
    engines/glk/tads/os_glk.h
    engines/glk/tads/tads.cpp
    engines/glk/tads/tads.h
    engines/glk/tads/tads2/appctx.h
    engines/glk/tads/tads2/built_in.h
    engines/glk/tads/tads2/character_map.cpp
    engines/glk/tads/tads2/error_handling.h
    engines/glk/tads/tads2/file_io.cpp
    engines/glk/tads/tads2/file_io.h
    engines/glk/tads/tads2/lib.h
    engines/glk/tads/tads2/memory_cache.h
    engines/glk/tads/tads2/memory_cache_swap.h
    engines/glk/tads/tads2/os.cpp
    engines/glk/tads/tads2/os.h
    engines/glk/tads/tads2/post_compilation.cpp
    engines/glk/tads/tads2/post_compilation.h
    engines/glk/tads/tads2/run.h
    engines/glk/tads/tads2/tads2.cpp
    engines/glk/tads/tads2/tads2.h
    engines/glk/tads/tads2/tokenizer.cpp


diff --git a/engines/glk/glk.h b/engines/glk/glk.h
index bebb482..6b95eb2 100644
--- a/engines/glk/glk.h
+++ b/engines/glk/glk.h
@@ -221,6 +221,11 @@ public:
 	 * Generate a beep
 	 */
 	void beep();
+
+	/**
+	 * Get a random number
+	 */
+	uint getRandomNumber(uint max) { return _random.getRandomNumber(max); }
 };
 
 extern GlkEngine *g_vm;
diff --git a/engines/glk/glulxe/glulxe.cpp b/engines/glk/glulxe/glulxe.cpp
index cbe09ce..ba67dea 100644
--- a/engines/glk/glulxe/glulxe.cpp
+++ b/engines/glk/glulxe/glulxe.cpp
@@ -29,7 +29,7 @@ namespace Glulxe {
 
 Glulxe *g_vm;
 
-Glulxe::Glulxe(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), _random("glulxe"),
+Glulxe::Glulxe(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
 		vm_exited_cleanly(false), gamefile_start(0), gamefile_len(0), memmap(nullptr), stack(nullptr),
 		ramstart(0), endgamefile(0), origendmem(0),  stacksize(0), startfuncaddr(0), checksum(0),
 		stackptr(0), frameptr(0), pc(0), prevpc(0), origstringtable(0), stringtable(0), valstackbase(0),
diff --git a/engines/glk/glulxe/glulxe.h b/engines/glk/glulxe/glulxe.h
index c0008e8..56a912e 100644
--- a/engines/glk/glulxe/glulxe.h
+++ b/engines/glk/glulxe/glulxe.h
@@ -82,7 +82,6 @@ private:
 	 * to autorestore an initial game state, if the library has that capability. (Currently, only iosglk does.)
 	 */
 	void(*library_autorestore_hook)(void);
-	Common::RandomSource _random;
 
 	/**@}*/
 
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 810c493..1ce2458 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -92,29 +92,42 @@ MODULE_OBJS := \
 	scott/detection.o \
 	scott/scott.o \
 	tads/detection.o \
-	tads/osfrobtads.o \
+	tads/os_buffer.o \
+	tads/os_glk.o \
+	tads/os_frob_tads.o \
 	tads/tads.o \
 	tads/tads2/built_in.o \
 	tads/tads2/character_map.o \
+	tads/tads2/command_line.o \
 	tads/tads2/data.o \
 	tads/tads2/debug.o \
 	tads/tads2/error.o \
 	tads/tads2/error_handling.o \
+	tads/tads2/error_message.o \
+	tads/tads2/execute_command.o \
 	tads/tads2/file_io.o \
 	tads/tads2/line_source_file.o \
 	tads/tads2/list.o \
+	tads/tads2/ltk.o \
 	tads/tads2/memory_cache.o \
 	tads/tads2/memory_cache_heap.o \
 	tads/tads2/memory_cache_swap.o \
 	tads/tads2/object.o \
 	tads/tads2/os.o \
 	tads/tads2/output.o \
+	tads/tads2/play.o \
 	tads/tads2/post_compilation.o \
+	tads/tads2/qa_scriptor.o \
 	tads/tads2/regex.o \
 	tads/tads2/run.o \
+	tads/tads2/runstat.o \
+	tads/tads2/runtime_app.o \
+	tads/tads2/runtime_driver.o \
 	tads/tads2/tads2.o \
 	tads/tads2/tokenizer.o \
+	tads/tads2/tokenizer_hash.o \
 	tads/tads2/vocabulary.o \
+	tads/tads2/vocabulary_parser.o \
 	tads/tads3/tads3.o
 
 # This module can be built as a plugin
diff --git a/engines/glk/tads/os_buffer.cpp b/engines/glk/tads/os_buffer.cpp
new file mode 100644
index 0000000..0f00e9d
--- /dev/null
+++ b/engines/glk/tads/os_buffer.cpp
@@ -0,0 +1,103 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/os_buffer.h"
+#include "glk/tads/tads.h"
+
+namespace Glk {
+namespace TADS {
+
+extern winid_t mainwin;
+
+#ifndef GLK_MODULE_UNICODE
+
+void os_put_buffer(const char *buf, size_t len) {
+    g_vm->glk_put_buffer(buf, len);
+}
+
+void os_get_buffer (char *buf, size_t len, size_t init)
+{
+	g_vm->glk_request_line_event(mainwin, buf, len - 1, init);
+}
+
+char *os_fill_buffer(char *buf, size_t len)
+{
+    buf[len] = '\0';
+    return buf;
+}
+
+#else
+
+static uint32 *input = 0;
+static uint max = 0;
+
+extern uint os_parse_chars(const char *buf, uint buflen, uint32 *out, uint outlen);
+
+extern uint os_prepare_chars(uint32 *buf, uint buflen, char *out, uint outlen);
+
+void os_put_buffer(const char *buf, size_t len) {
+    uint *out;
+    uint outlen;
+
+    if (!len)
+        return;
+
+    out = new uint32[len + 1];
+    if (!out)
+        return;
+    outlen = len;
+
+    outlen = os_parse_chars(buf, len, out, outlen);
+
+    if (outlen)
+        g_vm->glk_put_buffer_uni(out, outlen);
+    else
+		g_vm->glk_put_buffer(buf, len);
+
+	delete[] out;
+}
+
+void os_get_buffer(char *buf, size_t len, size_t init) {
+    input = (uint32 *)malloc(sizeof(uint) * (len + 1));
+    max = len;
+
+    if (init)
+        os_parse_chars(buf, init + 1, input, len);
+
+    g_vm->glk_request_line_event_uni(mainwin, input, len - 1, init);
+}
+
+char *os_fill_buffer(char *buf, size_t len) {
+    uint res = os_prepare_chars(input, len, buf, max);
+    buf[res] = '\0';
+
+    free(input);
+    input = nullptr;
+    max = 0;
+
+    return buf;
+}
+
+#endif /* GLK_MODULE_UNICODE */
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_buffer.h b/engines/glk/tads/os_buffer.h
new file mode 100644
index 0000000..3fc2209
--- /dev/null
+++ b/engines/glk/tads/os_buffer.h
@@ -0,0 +1,48 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* OS-layer functions and macros.
+ *
+ * This file does not introduce any curses (or other screen-API)
+ * dependencies; it can be used for both the interpreter as well as the
+ * compiler.
+ */
+
+/* Text IO Buffering
+ */
+
+#ifndef GLK_TADS_OS_BUFFER
+#define GLK_TADS_OS_BUFFER
+
+namespace Glk {
+namespace TADS {
+
+extern void os_put_buffer(const char *buf, size_t len);
+
+extern void os_get_buffer(char *buf, size_t len, size_t init);
+
+extern char *os_fill_buffer(char *buf, size_t len);
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/os_frob_tads.cpp b/engines/glk/tads/os_frob_tads.cpp
new file mode 100644
index 0000000..8c8d3f1
--- /dev/null
+++ b/engines/glk/tads/os_frob_tads.cpp
@@ -0,0 +1,476 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/os_frob_tads.h"
+#include "common/file.h"
+#include "common/memstream.h"
+
+namespace Glk {
+namespace TADS {
+
+static osfildef *openForReading(const char *fname) {
+	Common::File f;
+	if (f.open(fname))
+		return f.readStream(f.size());
+	
+	Common::InSaveFile *save = g_system->getSavefileManager()->openForLoading(fname);
+	return save;
+}
+
+static osfildef *openForWriting(const char *fname) {
+	return g_system->getSavefileManager()->openForSaving(fname, false);
+}
+
+int osfacc(const char *fname) {
+	return Common::File::exists(fname) ? 1 : 0;
+}
+
+osfildef *osfoprt(const char *fname, os_filetype_t typ) {
+	return openForReading(fname);
+}
+
+osfildef *osfoprtv(const char *fname, os_filetype_t typ) {
+	return openForReading(fname);
+}
+
+osfildef *osfopwt(const char *fname, os_filetype_t typ) {
+	return openForWriting(fname);
+}
+
+osfildef *osfoprwt(const char *fname, os_filetype_t typ) {
+	warning("ScummVM files can't be opened for both reading and writing simultaneously");
+	return openForWriting(fname);
+}
+
+osfildef *osfoprwtt(const char *fname, os_filetype_t typ) {
+	warning("ScummVM files can't be opened for both reading and writing simultaneously");
+	return openForWriting(fname);
+}
+
+osfildef *osfopwb(const char *fname, os_filetype_t typ) {
+	return openForWriting(fname);
+}
+
+osfildef *osfoprs(const char *fname, os_filetype_t typ) {
+	return openForReading(fname);
+}
+
+osfildef *osfoprb(const char *fname, os_filetype_t typ) {
+	return openForReading(fname);
+}
+
+osfildef *osfoprbv(const char *fname, os_filetype_t typ) {
+	return openForReading(fname);
+}
+
+osfildef *osfoprwb(const char *fname, os_filetype_t typ) {
+	warning("ScummVM files can't be opened for both reading and writing simultaneously");
+	return openForWriting(fname);
+}
+
+osfildef *osfoprwtb(const char *fname, os_filetype_t typ) {
+	warning("ScummVM files can't be opened for both reading and writing simultaneously");
+	return openForWriting(fname);
+}
+
+osfildef *osfdup(osfildef *orig, const char *mode) {
+	Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(orig);
+	int32 currPos = rs->pos();
+
+	rs->seek(0);
+	osfildef *result = rs->readStream(rs->size());
+	rs->seek(currPos);
+
+	return result;
+}
+
+void os_settype(const char *f, os_filetype_t typ) {
+	// No implementation
+}
+
+char *osfgets(char *buf, size_t count, osfildef *fp) {
+	Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(fp);
+	char *ptr = buf;
+	char c;
+	while (!rs->eos() && --count > 0) {
+		c = rs->readByte();
+		if (c == '\n' || c == '\0')
+			break;
+		*ptr++ = c;
+	}
+
+	*ptr++ = '\0';
+	return buf;
+}
+
+int osfputs(const char *str, osfildef *fp) {
+	return dynamic_cast<Common::WriteStream *>(fp)->write(str, strlen(str)) == strlen(str) ? 0 : -1;
+}
+
+void os_fprintz(osfildef *fp, const char *str) {
+	dynamic_cast<Common::WriteStream *>(fp)->write(str, strlen(str));
+}
+
+void os_fprint(osfildef *fp, const char *str, size_t len) {
+	Common::String s(str, str + MIN(len, strlen(str)));
+	dynamic_cast<Common::WriteStream *>(fp)->write(s.c_str(), s.size());
+}
+
+int osfwb(osfildef *fp, const void *buf, size_t bufl) {
+	return dynamic_cast<Common::WriteStream *>(fp)->write(buf, bufl) == bufl ? 0 : 1;
+}
+
+int osfflush(osfildef *fp) {
+	return dynamic_cast<Common::WriteStream *>(fp)->flush() ? 0 : 1;
+}
+
+int osfgetc(osfildef *fp) {
+	return dynamic_cast<Common::ReadStream *>(fp)->readByte();
+}
+
+int osfrb(osfildef *fp, void *buf, size_t bufl) {
+	return dynamic_cast<Common::ReadStream *>(fp)->read(buf, bufl) == bufl ? 0 : 1;
+}
+
+size_t osfrbc(osfildef *fp, void *buf, size_t bufl) {
+	return dynamic_cast<Common::ReadStream *>(fp)->read(buf, bufl);
+}
+
+long osfpos(osfildef *fp) {
+	return dynamic_cast<Common::SeekableReadStream *>(fp)->pos();
+}
+
+int osfseek(osfildef *fp, long pos, int mode) {
+	return dynamic_cast<Common::SeekableReadStream *>(fp)->seek(pos, mode);
+}
+
+void osfcls(osfildef *fp) {
+	delete fp;
+}
+
+int osfdel(const char *fname) {
+	return g_system->getSavefileManager()->removeSavefile(fname) ? 0 : 1;
+}
+
+int os_rename_file(const char *oldname, const char *newname) {
+	return g_system->getSavefileManager()->renameSavefile(oldname, newname);
+}
+
+
+bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) {
+	Common::String name = !flen ? Common::String(fname) : Common::String(fname, fname + flen);
+
+	if (!Common::File::exists(fname))
+		return false;
+
+	strncpy(buf, name.c_str(), bufsiz - 1);
+	buf[bufsiz - 1] = '\0';
+	return true;
+}
+
+osfildef *os_create_tempfile(const char *fname, char *buf) {
+	strcpy(buf, "tmpfile");
+	return new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
+}
+
+int osfdel_temp(const char *fname) {
+	// Temporary files in ScummVM are just memory streams, so there isn't a file to delete
+	return 0;
+}
+
+void os_get_tmp_path(char *buf) {
+	strcpy(buf, "");
+}
+
+int os_gen_temp_filename(char *buf, size_t buflen) {
+	error("TODO: If results from this are being passed to file open methods, will need to do further work");
+}
+
+/* ------------------------------------------------------------------------ */
+
+void os_set_pwd(const char *dir) {
+	// No implementation
+}
+
+void os_set_pwd_file(const char *filename) {
+	// No implementation
+}
+
+bool os_mkdir(const char *dir, int create_parents) {
+	// Unsupported
+	return false;
+}
+
+bool os_rmdir(const char *dir) {
+	// Unsupported
+	return false;
+}
+
+/* ------------------------------------------------------------------------ */
+
+void os_defext(char *fname, const char *ext) {
+	if (!strchr(fname, '.'))
+		strcat(fname, ext);
+}
+
+void os_addext(char *fname, const char *ext) {
+	strcat(fname, ext);
+}
+
+void os_remext(char *fname) {
+	char *p = strchr(fname, '.');
+	if (p)
+		*p = '\0';
+}
+
+bool os_file_names_equal(const char *a, const char *b) {
+	return !strcmp(a, b);
+}
+
+const char *os_get_root_name(const char *buf) {
+	return buf;
+}
+
+bool os_is_file_absolute(const char *fname) {
+	return false;
+}
+
+void os_get_path_name(char *pathbuf, size_t pathbuflen, const char *fname) {
+	strcpy(pathbuf, "");
+}
+
+void os_build_full_path(char *fullpathbuf, size_t fullpathbuflen,
+	const char *path, const char *filename) {
+	strcpy(fullpathbuf, filename);
+}
+
+void os_combine_paths(char *fullpathbuf, size_t pathbuflen,
+	const char *path, const char *filename) {
+	strcpy(fullpathbuf, filename);
+}
+
+bool os_get_abs_filename(char *result_buf, size_t result_buf_size,
+	const char *filename) {
+	strcpy(result_buf, filename);
+	return true;
+}
+
+bool os_get_rel_path(char *result_buf, size_t result_buf_size,
+	const char *basepath, const char *filename) {
+	strcpy(result_buf, filename);
+	return true;
+}
+
+bool os_is_file_in_dir(const char *filename, const char *path,
+	bool include_subdirs, bool match_self) {
+	assert(!include_subdirs && !match_self);
+
+	return Common::File::exists(filename);
+}
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+*   Convert an OS filename path to URL-style format.  This isn't a true URL
+*   conversion; rather, it simply expresses a filename in Unix-style
+*   notation, as a series of path elements separated by '/' characters.
+*   Unlike true URLs, we don't use % encoding or a scheme prefix (file://,
+*   etc).
+*
+*   The result path never ends in a trailing '/', unless the entire result
+*   path is "/".  This is for consistency; even if the source path ends with
+*   a local path separator, the result doesn't.
+*
+*   If the local file system syntax uses '/' characters as ordinary filename
+*   characters, these must be replaced with some other suitable character in
+*   the result, since otherwise they'd be taken as path separators when the
+*   URL is parsed.  If possible, the substitution should be reversible with
+*   respect to os_cvt_dir_url(), so that the same URL read back in on this
+*   same platform will produce the same original filename.  One particular
+*   suggestion is that if the local system uses '/' to delimit what would be
+*   a filename extension on other platforms, replace '/' with '.', since
+*   this will provide reversibility as well as a good mapping if the URL is
+*   read back in on another platform.
+*
+*   The local equivalents of "." and "..", if they exist, are converted to
+*   "." and ".." in the URL notation.
+*
+*   Examples:
+*
+*.   Windows: images\rooms\startroom.jpg -> images/rooms/startroom.jpg
+*.   Windows: ..\startroom.jpg -> ../startroom.jpg
+*.   Mac:     :images:rooms:startroom.jpg -> images/rooms/startroom.jpg
+*.   Mac:     ::startroom.jpg -> ../startroom.jpg
+*.   VMS:     [.images.rooms]startroom.jpg -> images/rooms/startroom.jpg
+*.   VMS:     [-.images]startroom.jpg -> ../images/startroom.jpg
+*.   Unix:    images/rooms/startroom.jpg -> images/rooms/startroom.jpg
+*.   Unix:    ../images/startroom.jpg -> ../images/startroom.jpg
+*
+*   If the local name is an absolute path in the local file system (e.g.,
+*   Unix /file, Windows C:\file), translate as follows.  If the local
+*   operating system uses a volume or device designator (Windows C:, VMS
+*   SYS$DISK:, etc), make the first element of the path the exact local
+*   syntax for the device designator: /C:/ on Windows, /SYS$DISK:/ on VMS,
+*   etc.  Include the local syntax for the device prefix.  For a system like
+*   Unix with a unified file system root ("/"), simply start with the root
+*   directory.  Examples:
+*
+*.    Windows:  C:\games\deep.gam         -> /C:/games/deep.gam
+*.    Windows:  C:games\deep.gam          -> /C:./games/deep.gam
+*.    Windows:  \\SERVER\DISK\games\deep.gam -> /\\SERVER/DISK/games/deep.gam
+*.    Mac OS 9: Hard Disk:games:deep.gam  -> /Hard Disk:/games/deep.gam
+*.    VMS:      SYS$DISK:[games]deep.gam  -> /SYS$DISK:/games/deep.gam
+*.    Unix:     /games/deep.gam           -> /games/deep.gam
+*
+*   Rationale: it's effectively impossible to create a truly portable
+*   representation of an absolute path.  Operating systems are too different
+*   in the way they represent root paths, and even if that were solvable, a
+*   root path is essentially unusable across machines anyway because it
+*   creates a dependency on the contents of a particular machine's disk.  So
+*   if we're called upon to translate an absolute path, we can forget about
+*   trying to be truly portable and instead focus on round-trip fidelity -
+*   i.e., making sure that applying os_cvt_url_dir() to our result recovers
+*   the exact original path, assuming it's done on the same operating
+*   system.  The approach outlined above should achieve round-trip fidelity
+*   when a local path is converted to a URL and back on the same machine,
+*   since the local URL-to-path converter should recognize its own special
+*   type of local absolute path prefix.  It also produces reasonable results
+*   on other platforms - see the os_cvt_url_dir() comments below for
+*   examples of the decoding results for absolute paths moved to new
+*   platforms.  The result when a device-rooted absolute path is encoded on
+*   one machine and then decoded on another will generally be a local path
+*   with a root on the default device/volume and an outermost directory with
+*   a name based on the original machine's device/volume name.  This
+*   obviously won't reproduce the exact original path, but since that's
+*   impossible anyway, this is probably as good an approximation as we can
+*   create.
+*
+*   Character sets: the input could be in local or UTF-8 character sets.
+*   The implementation shouldn't care, though - just treat bytes in the
+*   range 0-127 as plain ASCII, and everything else as opaque.  I.e., do not
+*   quote or otherwise modify characters outside the 0-127 range.
+*/
+void os_cvt_dir_url(char *result_buf, size_t result_buf_size,
+	const char *src_path);
+
+/*
+*   Convert a URL-style path into a filename path expressed in the local
+*   file system's syntax.  Fills in result_buf with a file path, constructed
+*   using the local file system syntax, that corresponds to the path in
+*   src_url expressed in URL-style syntax.  Examples:
+*
+*   images/rooms/startroom.jpg ->
+*.   Windows   -> images\rooms\startroom.jpg
+*.   Mac OS 9  -> :images:rooms:startroom.jpg
+*.   VMS       -> [.images.rooms]startroom.jpg
+*
+*   The source format isn't a true URL; it's simply a series of path
+*   elements separated by '/' characters.  Unlike true URLs, our input
+*   format doesn't use % encoding and doesn't have a scheme (file://, etc).
+*   (Any % in the source is treated as an ordinary character and left as-is,
+*   even if it looks like a %XX sequence.  Anything that looks like a scheme
+*   prefix is left as-is, with any // treated as path separators.
+*
+*   images/file%20name.jpg ->
+*.   Windows   -> images\file%20name.jpg
+*
+*   file://images/file.jpg ->
+*.   Windows   -> file_\\images\file.jpg
+*
+*   Any characters in the path that are invalid in the local file system
+*   naming rules are converted to "_", unless "_" is itself invalid, in
+*   which case they're converted to "X".  One exception is that if '/' is a
+*   valid local filename character (rather than a path separator as it is on
+*   Unix and Windows), it can be used as the replacement for the character
+*   that os_cvt_dir_url uses as its replacement for '/', so that this
+*   substitution is reversible when a URL is generated and then read back in
+*   on this same platform.
+*
+*   images/file:name.jpg ->
+*.   Windows   -> images\file_name.jpg
+*.   Mac OS 9  -> :images:file_name.jpg
+*.   Unix      -> images/file:name.jpg
+*
+*   The path elements "." and ".." are specifically defined as having their
+*   Unix meanings: "." is an alias for the preceding path element, or the
+*   working directory if it's the first element, and ".." is an alias for
+*   the parent of the preceding element.  When these appear as path
+*   elements, this routine translates them to the appropriate local
+*   conventions.  "." may be translated simply by removing it from the path,
+*   since it reiterates the previous path element.  ".." may be translated
+*   by removing the previous element - HOWEVER, if ".." appears as the first
+*   element, it has to be retained and translated to the equivalent local
+*   notation, since it will have to be applied later, when the result_buf
+*   path is actually used to open a file, at which point it will combined
+*   with the working directory or another base path.
+*
+*.  /images/../file.jpg -> [Windows] file.jpg
+*.  ../images/file.jpg ->
+*.   Windows  -> ..\images\file.jpg
+*.   Mac OS 9 -> ::images:file.jpg
+*.   VMS      -> [-.images]file.jpg
+*
+*   If the URL path is absolute (starts with a '/'), the routine inspects
+*   the path to see if it was created by the same OS, according to the local
+*   rules for converting absolute paths in os_cvt_dir_url() (see).  If so,
+*   we reverse the encoding done there.  If it doesn't appear that the name
+*   was created by the same operating system - that is, if reversing the
+*   encoding doesn't produce a valid local filename - then we create a local
+*   absolute path as follows.  If the local system uses device/volume
+*   designators, we start with the current working device/volume or some
+*   other suitable default volume.  We then add the first element of the
+*   path, if any, as the root directory name, applying the usual "_" or "X"
+*   substitution for any characters that aren't allowed in local names.  The
+*   rest of the path is handled in the usual fashion.
+*
+*.  /images/file.jpg ->
+*.    Windows -> \images\file.jpg
+*.    Unix    -> /images/file.jpg
+*
+*.  /c:/images/file.jpg ->
+*.    Windows -> c:\images\file.jpg
+*.    Unix    -> /c:/images/file.jpg
+*.    VMS     -> SYS$DISK:[c__.images]file.jpg
+*
+*.  /Hard Disk:/images/file.jpg ->
+*.    Windows -> \Hard Disk_\images\file.jpg
+*.    Unix    -> SYS$DISK:[Hard_Disk_.images]file.jpg
+*
+*   Note how the device/volume prefix becomes the top-level directory when
+*   moving a path across machines.  It's simply not possible to reconstruct
+*   the exact original path in such cases, since device/volume syntax rules
+*   have little in common across systems.  But this seems like a good
+*   approximation in that (a) it produces a valid local path, and (b) it
+*   gives the user a reasonable basis for creating a set of folders to mimic
+*   the original source system, if they want to use that approach to port
+*   the data rather than just changing the paths internally in the source
+*   material.
+*
+*   Character sets: use the same rules as for os_cvt_dir_url().
+*/
+void os_cvt_url_dir(char *result_buf, size_t result_buf_size,
+	const char *src_url);
+
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_frob_tads.h b/engines/glk/tads/os_frob_tads.h
new file mode 100644
index 0000000..e8c0845
--- /dev/null
+++ b/engines/glk/tads/os_frob_tads.h
@@ -0,0 +1,1201 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* OS-layer functions and macros.
+ *
+ * This file does not introduce any curses (or other screen-API)
+ * dependencies; it can be used for both the interpreter as well as the
+ * compiler.
+ */
+
+#ifndef GLK_TADS_OS_FROB_TADS
+#define GLK_TADS_OS_FROB_TADS
+
+#include "common/fs.h"
+#include "common/stream.h"
+#include "glk/glk_api.h"
+#include "glk/tads/os_filetype.h"
+
+namespace Glk {
+namespace TADS {
+
+/* Defined for Gargoyle. */
+#define HAVE_STDINT_
+
+#if 0
+#include "common.h"
+#endif
+
+/* Used by the base code to inhibit "unused parameter" compiler warnings. */
+#ifndef VARUSED
+#define VARUSED(var) (void)var
+#endif
+
+/* We assume that the C-compiler is mostly ANSI compatible. */
+#define OSANSI
+
+/* Special function qualifier needed for certain types of callback
+ * functions.  This is for old 16-bit systems; we don't need it and
+ * define it to nothing. */
+#define OS_LOADDS
+
+/* Unices don't suffer the near/far pointers brain damage (thank God) so
+ * we make this a do-nothing macro. */
+#define osfar_t
+
+/* This is used to explicitly discard computed values (some compilers
+ * would otherwise give a warning like "computed value not used" in some
+ * cases).  Casting to void should work on every ANSI-Compiler. */
+#define DISCARD (void)
+
+/* Copies a struct into another.  ANSI C allows the assignment operator
+ * to be used with structs. */
+#define OSCPYSTRUCT(x,y) ((x)=(y))
+
+/* Link error messages into the application. */
+#define ERR_LINK_MESSAGES
+
+/* Program Exit Codes. */
+#define OSEXSUCC 0 /* Successful completion. */
+#define OSEXFAIL 1 /* Failure. */
+
+/* Here we configure the osgen layer; refer to tads2/osgen3.c for more
+ * information about the meaning of these macros. */
+#define USE_DOSEXT
+#define USE_NULLSTYPE
+
+/* Theoretical maximum osmalloc() size.
+ * Unix systems have at least a 32-bit memory space.  Even on 64-bit
+ * systems, 2^32 is a good value, so we don't bother trying to find out
+ * an exact value. */
+#define OSMALMAX 0xffffffffL
+
+#define OSFNMAX 255
+
+/**
+ * File handle structure for osfxxx functions
+ * Note that we need to define it as a Common::Stream since the type is used by
+ * TADS for both reading and writing files
+ */
+typedef Common::Stream osfildef;
+
+/* Directory handle for searches via os_open_dir() et al. */
+typedef Common::FSNode *osdirhdl_t;
+
+/* file type/mode bits */
+#define OSFMODE_FILE    S_IFREG
+#define OSFMODE_DIR     S_IFDIR
+#define OSFMODE_CHAR    S_IFCHR
+#define OSFMODE_BLK     S_IFBLK
+#define OSFMODE_PIPE    S_IFIFO
+#ifdef S_IFLNK
+#define OSFMODE_LINK    S_IFLNK
+#else
+#define OSFMODE_LINK    0
+#endif
+#ifdef S_IFSOCK
+#define OSFMODE_SOCKET  S_IFSOCK
+#else
+#define OSFMODE_SOCKET  0
+#endif
+
+/* File attribute bits. */
+#define OSFATTR_HIDDEN  0x0001
+#define OSFATTR_SYSTEM  0x0002
+#define OSFATTR_READ    0x0004
+#define OSFATTR_WRITE   0x0008
+
+/* Get a file's stat() type. */
+int osfmode( const char* fname, int follow_links, unsigned long* mode,
+             unsigned long* attr );
+
+#if 0
+/* The maximum width of a line of text.
+ *
+ * We ignore this, but the base code needs it defined.  If the
+ * interpreter is run inside a console or terminal with more columns
+ * than the value defined here, weird things will happen, so we go safe
+ * and use a large value. */
+#define OS_MAXWIDTH 255
+#endif
+
+/* Disable the Tads swap file; computers have plenty of RAM these days.
+ */
+#define OS_DEFAULT_SWAP_ENABLED 0
+
+/* TADS 2 macro/function configuration.  Modern configurations always
+ * use the no-macro versions, so these definitions should always be set
+ * as shown below. */
+#define OS_MCM_NO_MACRO
+#define ERR_NO_MACRO
+
+/* These values are used for the "mode" parameter of osfseek() to
+ * indicate where to seek in the file. */
+#define OSFSK_SET SEEK_SET /* Set position relative to the start of the file. */
+#define OSFSK_CUR SEEK_CUR /* Set position relative to the current file position. */
+#define OSFSK_END SEEK_END /* Set position relative to the end of the file. */
+
+
+/* ============= Functions follow ================ */
+
+/* Allocate a block of memory of the given size in bytes. */
+#define osmalloc malloc
+
+/* Free memory previously allocated with osmalloc(). */
+#define osfree free
+
+/* Reallocate memory previously allocated with osmalloc() or osrealloc(),
+ * changing the block's size to the given number of bytes. */
+#define osrealloc realloc
+
+/* Set busy cursor.
+ *
+ * We don't have a mouse cursor so there's no need to implement this. */
+#define os_csr_busy(a)
+
+/* Update progress display.
+ *
+ * We don't provide any kind of "compilation progress display", so we
+ * just define this as an empty macro.
+ */
+#define os_progress(fname,linenum)
+
+ /* ============= File Access ================ */
+
+ /*
+ *   Test access to a file - i.e., determine if the file exists.  Returns
+ *   zero if the file exists, non-zero if not.  (The semantics may seem
+ *   backwards, but this is consistent with the conventions used by most of
+ *   the other osfxxx calls: zero indicates success, non-zero indicates an
+ *   error.  If the file exists, "accessing" it was successful, so osfacc
+ *   returns zero; if the file doesn't exist, accessing it gets an error,
+ *   hence a non-zero return code.)
+ */
+int osfacc(const char *fname);
+
+
+ /*
+ *   Open text file for reading.  This opens the file with read-only access;
+ *   we're not allowed to write to the file using this handle.  Returns NULL
+ *   on error.
+ *
+ *   A text file differs from a binary file in that some systems perform
+ *   translations to map between C conventions and local file system
+ *   conventions; for example, on DOS, the stdio library maps the DOS CR-LF
+ *   newline convention to the C-style '\n' newline format.  On many systems
+ *   (Unix, for example), there is no distinction between text and binary
+ *   files.
+ *
+ *   On systems that support file sharing and locking, this should open the
+ *   file in "shared read" mode - this means that other processes are allowed
+ *   to simultaneously read from the file, but no other processs should be
+ *   allowed to write to the file as long as we have it open.  If another
+ *   process already has the file open with write access, this routine should
+ *   return failure, since we can't take away the write privileges the other
+ *   process already has and thus we can't guarantee that other processes
+ *   won't write to the file while we have it open.
+ */
+osfildef *osfoprt(const char *fname, os_filetype_t typ);
+
+/*
+*   Open a text file for "volatile" reading: we open the file with read-only
+*   access, and we explicitly accept instability in the file's contents due
+*   to other processes simultaneously writing to the file.  On systems that
+*   support file sharing and locking, the file should be opened in "deny
+*   none" mode, meaning that other processes can simultaneously open the
+*   file for reading and/or writing even while have the file open.
+*/
+osfildef *osfoprtv(const char *fname, os_filetype_t typ);
+
+/*
+*   Open text file for writing; returns NULL on error.  If the file already
+*   exists, this truncates the file to zero length, deleting any existing
+*   contents.
+*/
+osfildef *osfopwt(const char *fname, os_filetype_t typ);
+
+/*
+*   Open text file for reading and writing, keeping the file's existing
+*   contents if the file already exists or creating a new file if no such
+*   file exists.  Returns NULL on error.
+*/
+osfildef *osfoprwt(const char *fname, os_filetype_t typ);
+
+/*
+*   Open text file for reading/writing.  If the file already exists,
+*   truncate the existing contents to zero length.  Create a new file if it
+*   doesn't already exist.  Return null on error.
+*/
+osfildef *osfoprwtt(const char *fname, os_filetype_t typ);
+
+/*
+*   Open binary file for writing; returns NULL on error.  If the file
+*   exists, this truncates the existing contents to zero length.
+*/
+osfildef *osfopwb(const char *fname, os_filetype_t typ);
+
+/*
+*   Open source file for reading - use the appropriate text or binary
+*   mode.
+*/
+osfildef *osfoprs(const char *fname, os_filetype_t typ);
+
+/*
+*   Open binary file for reading; returns NULL on error.
+*/
+osfildef *osfoprb(const char *fname, os_filetype_t typ);
+
+/*
+*   Open binary file for 'volatile' reading; returns NULL on error.
+*   ("Volatile" means that we'll accept writes from other processes while
+*   reading, so the file should be opened in "deny none" mode or the
+*   equivalent, to the extent that the local system supports file sharing
+*   modes.)
+*/
+osfildef *osfoprbv(const char *fname, os_filetype_t typ);
+
+/*
+*   Open binary file for random-access reading/writing.  If the file already
+*   exists, keep the existing contents; if the file doesn't already exist,
+*   create a new empty file.
+*
+*   The caller is allowed to perform any mixture of read and write
+*   operations on the returned file handle, and can seek around in the file
+*   to read and write at random locations.
+*
+*   If the local file system supports file sharing or locking controls, this
+*   should generally open the file in something equivalent to "exclusive
+*   write, shared read" mode ("deny write" in DENY terms), so that other
+*   processes can't modify the file at the same time we're modifying it (but
+*   it doesn't bother us to have other processes reading from the file while
+*   we're working on it, as long as they don't mind that we could change
+*   things on the fly).  It's not absolutely necessary to assert these
+*   locking semantics, but if there's an option to do so this is preferred.
+*   Stricter semantics (such as "exclusive" or "deny all" mode) are better
+*   than less strict semantics.  Less strict semantics are dicey, because in
+*   that case the caller has no way of knowing that another process could be
+*   modifying the file at the same time, and no way (through osifc) of
+*   coordinating that activity.  If less strict semantics are implemented,
+*   the caller will basically be relying on luck to avoid corruptions due to
+*   writing by other processes.
+*
+*   Return null on error.
+*/
+osfildef *osfoprwb(const char *fname, os_filetype_t typ);
+
+/*
+*   Open binary file for random-access reading/writing.  If the file already
+*   exists, truncate the existing contents (i.e., delete the contents of the
+*   file, resetting it to a zero-length file).  Create a new file if it
+*   doesn't already exist.  The caller is allowed to perform any mixture of
+*   read and write operations on the returned handle, and can seek around in
+*   the file to read and write at random locations.
+*
+*   The same comments regarding sharing/locking modes for osfoprwb() apply
+*   here as well.
+*
+*   Return null on error.
+*/
+osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
+
+/*
+*   Duplicate a file handle.  Returns a new osfildef* handle that accesses
+*   the same open file as an existing osfildef* handle.  The new handle is
+*   independent of the original handle, with its own seek position,
+*   buffering, etc.  The new handle and the original handle must each be
+*   closed separately when the caller is done with them (closing one doesn't
+*   close the other).  The effect should be roughly the same as the Unix
+*   dup() function.
+*
+*   On success, returns a new, non-null osfildef* handle duplicating the
+*   original handle.  Returns null on failure.
+*
+*   'mode' is a simplified stdio fopen() mode string.  The first
+*   character(s) indicate the access type: "r" for read access, "w" for
+*   write access, or "r+" for read/write access.  Note that "w+" mode is
+*   specifically not defined, since the fopen() handling of "w+" is to
+*   truncate any existing file contents, which is not desirable when
+*   duplicating a handle.  The access type can optionally be followed by "t"
+*   for text mode, "s" for source file mode, or "b" for binary mode, with
+*   the same meanings as for the various osfop*() functions.  The default is
+*   't' for text mode if none of these are specified.
+*
+*   If the osfop*() functions are implemented in terms of stdio FILE*
+*   objects, this can be implemented as fdopen(dup(fileno(orig)), mode), or
+*   using equivalents if the local stdio library uses different names for
+*   these functions.  Note that "s" (source file format) isn't a stdio mode,
+*   so implementations must translate it to the appropriate "t" or "b" mode.
+*   (For that matter, "t" and "b" modes aren't universally supported either,
+*   so some implementations may have to translate these, or more likely
+*   simply remove them, as most platforms don't distinguish text and binary
+*   modes anyway.)
+*/
+osfildef *osfdup(osfildef *orig, const char *mode);
+
+/*
+*   Set a file's type information.  This is primarily for implementations on
+*   Mac OS 9 and earlier, where the file system keeps file-type metadata
+*   separate from the filename.  On such systems, this can be used to set
+*   the type metadata after a file is created.  The system should map the
+*   os_filetype_t values to the actual metadata values on the local system.
+*   On most systems, there's no such thing as file-type metadata, in which
+*   case this function should simply be stubbed out with an empty function.
+*/
+void os_settype(const char *f, os_filetype_t typ);
+
+/*
+ *   Get a line of text from a text file.  Uses fgets semantics.
+ */
+char *osfgets(char *buf, size_t len, osfildef *fp);
+
+/*
+*   Write a line of text to a text file.  Uses fputs semantics.
+*/
+int osfputs(const char *buf, osfildef *fp);
+
+/*
+*   Write to a text file.  os_fprintz() takes a null-terminated string,
+*   while os_fprint() takes an explicit separate length argument that might
+*   not end with a null terminator.
+*/
+void os_fprintz(osfildef *fp, const char *str);
+void os_fprint(osfildef *fp, const char *str, size_t len);
+
+/*
+*   Write bytes to file.  Return 0 on success, non-zero on error.
+*/
+int osfwb(osfildef *fp, const void *buf, size_t bufl);
+
+/*
+*   Flush buffered writes to a file.  This ensures that any bytes written to
+*   the file (with osfwb(), os_fprint(), etc) are actually sent out to the
+*   operating system, rather than being buffered in application memory for
+*   later writing.
+*
+*   Note that this routine only guarantees that we write through to the
+*   operating system.  This does *not* guarantee that the data will actually
+*   be committed to the underlying physical storage device.  Such a
+*   guarantee is hard to come by in general, since most modern systems use
+*   multiple levels of software and hardware buffering - the OS might buffer
+*   some data in system memory, and the physical disk drive might itself
+*   buffer data in its own internal cache.  This routine thus isn't good
+*   enough, for example, to protect transactional data that needs to survive
+*   a power failure or a serious system crash.  What this routine *does*
+*   ensure is that buffered data are written through to the OS; in
+*   particular, this ensures that another process that's reading from the
+*   same file will see all updates we've made up to this point.
+*
+*   Returns 0 on success, non-zero on error.  Errors can occur for any
+*   reason that they'd occur on an ordinary write - a full disk, a hardware
+*   failure, etc.
+*/
+int osfflush(osfildef *fp);
+
+/*
+*   Read a character from a file.  Provides the same semantics as fgetc().
+*/
+int osfgetc(osfildef *fp);
+
+/*
+*   Read bytes from file.  Return 0 on success, non-zero on error.
+*/
+int osfrb(osfildef *fp, void *buf, size_t bufl);
+
+/*
+*   Read bytes from file and return the number of bytes read.  0
+*   indicates that no bytes could be read.
+*/
+size_t osfrbc(osfildef *fp, void *buf, size_t bufl);
+
+/*
+*   Get the current seek location in the file.  The first byte of the
+*   file has seek position 0.
+*/
+long osfpos(osfildef *fp);
+
+/*
+*   Seek to a location in the file.  The first byte of the file has seek
+*   position 0.  Returns zero on success, non-zero on error.
+*
+*   The following constants must be defined in your OS-specific header;
+*   these values are used for the "mode" parameter to indicate where to
+*   seek in the file:
+*
+*   OSFSK_SET - set position relative to the start of the file
+*.  OSFSK_CUR - set position relative to the current file position
+*.  OSFSK_END - set position relative to the end of the file
+*/
+int osfseek(osfildef *fp, long pos, int mode);
+
+/*
+*   Close a file.
+*
+*   If the OS implementation uses buffered writes, this routine guarantees
+*   that any buffered data are flushed to the underlying file.  So, it's not
+*   necessary to call osfflush() before calling this routine.  However,
+*   since this function doesn't return any error indication, a caller could
+*   use osfflush() first to check for errors on any final buffered writes.
+*/
+void osfcls(osfildef *fp);
+
+/*
+*   Delete a file.  Returns zero on success, non-zero on error.
+*/
+int osfdel(const char *fname);
+
+/*
+*   Rename/move a file.  This should apply the usual C rename() behavior.
+*   Renames the old file to the new name, which may be in a new directory
+*   location if supported on the local system; moves across devices,
+*   volumes, file systems, etc may or may not be supported according to the
+*   local system's rules.  If the new file already exists, results are
+*   undefined.  Returns true on success, false on failure.
+*/
+int os_rename_file(const char *oldname, const char *newname);
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Look for a file in the "standard locations": current directory, program
+ *   directory, PATH-like environment variables, etc.  The actual standard
+ *   locations are specific to each platform; the implementation is free to
+ *   use whatever conventions are appropriate to the local system.  On
+ *   systems that have something like Unix environment variables, it might be
+ *   desirable to define a TADS-specific variable (TADSPATH, for example)
+ *   that provides a list of directories to search for TADS-related files.
+ *   
+ *   On return, fill in 'buf' with the full filename of the located copy of
+ *   the file (if a copy was indeed found), in a format suitable for use with
+ *   the osfopxxx() functions; in other words, after this function returns,
+ *   the caller should be able to pass the contents of 'buf' to an osfopxxx()
+ *   function to open the located file.
+ *   
+ *   Returns true (non-zero) if a copy of the file was located, false (zero)
+ *   if the file could not be found in any of the standard locations.  
+ */
+bool os_locate(const char *fname, int flen, const char *arg0,
+              char *buf, size_t bufsiz);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Create and open a temporary file.  The file must be opened to allow
+ *   both reading and writing, and must be in "binary" mode rather than
+ *   "text" mode, if the system makes such a distinction.  Returns null on
+ *   failure.
+ *   
+ *   If 'fname' is non-null, then this routine should create and open a file
+ *   with the given name.  When 'fname' is non-null, this routine does NOT
+ *   need to store anything in 'buf'.  Note that the routine shouldn't try
+ *   to put the file in a special directory or anything like that; just open
+ *   the file with the name exactly as given.
+ *   
+ *   If 'fname' is null, this routine must choose a file name and fill in
+ *   'buf' with the chosen name; if possible, the file should be in the
+ *   conventional location for temporary files on this system, and should be
+ *   unique (i.e., it shouldn't be the same as any existing file).  The
+ *   filename stored in 'buf' is opaque to the caller, and cannot be used by
+ *   the caller except to pass to osfdel_temp().  On some systems, it may
+ *   not be possible to determine the actual filename of a temporary file;
+ *   in such cases, the implementation may simply store an empty string in
+ *   the buffer.  (The only way the filename would be unavailable is if the
+ *   implementation uses a system API that creates a temporary file, and
+ *   that API doesn't return the name of the created temporary file.  In
+ *   such cases, we don't need the name; the only reason we need the name is
+ *   so we can pass it to osfdel_temp() later, but since the system is going
+ *   to delete the file automatically, osfdel_temp() doesn't need to do
+ *   anything and thus doesn't need the name.)
+ *   
+ *   After the caller is done with the file, it should close the file (using
+ *   osfcls() as normal), then the caller MUST call osfdel_temp() to delete
+ *   the temporary file.
+ *   
+ *   This interface is intended to take advantage of systems that have
+ *   automatic support for temporary files, while allowing implementation on
+ *   systems that don't have any special temp file support.  On systems that
+ *   do have automatic delete-on-close support, this routine should use that
+ *   system-level support, because it helps ensure that temp files will be
+ *   deleted even if the caller fails to call osfdel_temp() due to a
+ *   programming error or due to a process or system crash.  On systems that
+ *   don't have any automatic delete-on-close support, this routine can
+ *   simply use the same underlying system API that osfoprwbt() normally
+ *   uses (although this routine must also generate a name for the temp file
+ *   when the caller doesn't supply one).
+ *   
+ *   This routine can be implemented using ANSI library functions as
+ *   follows: if 'fname' is non-null, return fopen(fname,"w+b"); otherwise,
+ *   set buf[0] to '\0' and return tmpfile().  
+ */
+osfildef *os_create_tempfile(const char *fname, char *buf);
+
+/*
+ *   Delete a temporary file - this is used to delete a file created with
+ *   os_create_tempfile().  For most platforms, this can simply be defined
+ *   the same way as osfdel().  For platforms where the operating system or
+ *   file manager will automatically delete a file opened as a temporary
+ *   file, this routine should do nothing at all, since the system will take
+ *   care of deleting the temp file.
+ *   
+ *   Callers are REQUIRED to call this routine after closing a file opened
+ *   with os_create_tempfile().  When os_create_tempfile() is called with a
+ *   non-null 'fname' argument, the same value should be passed as 'fname' to
+ *   this function.  When os_create_tempfile() is called with a null 'fname'
+ *   argument, then the buffer passed in the 'buf' argument to
+ *   os_create_tempfile() must be passed as the 'fname' argument here.  In
+ *   other words, if the caller explicitly names the temporary file to be
+ *   opened in os_create_tempfile(), then that same filename must be passed
+ *   here to delete the named file; if the caller lets os_create_tempfile()
+ *   generate a filename, then the generated filename must be passed to this
+ *   routine.
+ *   
+ *   If os_create_tempfile() is implemented using ANSI library functions as
+ *   described above, then this routine can also be implemented with ANSI
+ *   library calls as follows: if 'fname' is non-null and fname[0] != '\0',
+ *   then call remove(fname); otherwise do nothing.  
+ */
+int osfdel_temp(const char *fname);
+
+/*
+ *   Get the temporary file path.  This should fill in the buffer with a
+ *   path prefix (suitable for strcat'ing a filename onto) for a good
+ *   directory for a temporary file, such as the swap file.  
+ */
+void os_get_tmp_path(char *buf);
+
+/* 
+ *   Generate a name for a temporary file.  This constructs a random file
+ *   path in the system temp directory that isn't already used by an existing
+ *   file.
+ *   
+ *   On systems with long filenames, this can be implemented by selecting a
+ *   GUID-strength random name (such as 32 random hex digits) with a decent
+ *   random number generator.  That's long enough that the odds of a
+ *   collision are essentially zero.  On systems that only support short
+ *   filenames, the odds of a collision are non-zero, so the routine should
+ *   actually check that the chosen filename doesn't exist.
+ *   
+ *   Optionally, before returning, this routine *may* create (and close) an
+ *   empty placeholder file to "reserve" the chosen filename.  This isn't
+ *   required, and on systems with long filenames it's usually not necessary
+ *   because of the negligible chance of a collision.  On systems with short
+ *   filenames, a placeholder can be useful to prevent a subsequent call to
+ *   this routine, or a separate process, from using the same filename before
+ *   the caller has had a chance to use the returned name to create the
+ *   actual temp file.
+ *   
+ *   Returns true on success, false on failure.  This can fail if there's no
+ *   system temporary directory defined, or the temp directory is so full of
+ *   other files that we can't find an unused filename.  
+ */
+int os_gen_temp_filename(char *buf, size_t buflen);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+*   Basic directory/folder management routines
+*/
+
+/*
+*   Switch to a new working directory.
+*
+*   This is meant to behave similarly to the Unix concept of a working
+*   directory, in that it sets the base directory assumed for subsequent
+*   file operations (e.g., the osfopxx() functions, osfdel(), etc - anything
+*   that takes a filename or directory name as an argument).  The working
+*   directory applies to filenames specified with relative paths in the
+*   local system notation.  File operations on filenames specified with
+*   absolute paths, of course, ignore the working directory.
+*/
+void os_set_pwd(const char *dir);
+
+/*
+*   Switch the working directory to the directory containing the given
+*   file.  Generally, this routine should only need to parse the filename
+*   enough to determine the part that's the directory path, then use
+*   os_set_pwd() to switch to that directory.
+*/
+void os_set_pwd_file(const char *filename);
+
+/*
+*   Create a directory.  This creates a new directory/folder with the given
+*   name, which may be given as a relative or absolute path.  Returns true
+*   on success, false on failure.
+*
+*   If 'create_parents' is true, and the directory has mulitiple path
+*   elements, this routine should create each enclosing parent that doesn't
+*   already exist.  For example, if the path is specified as "a/b/c", and
+*   there exists a folder "a" in the working directory, but "a" is empty,
+*   this should first create "b" and then create "c".  If an error occurs
+*   creating any parent, the routine should simply stop and return failure.
+*   (Optionally, the routine may attempt to behave atomically by undoing any
+*   parent folder creations it accomplished before failing on a nested
+*   folder, but this isn't required.  To reduce the chances of a failure
+*   midway through the operation, the routine might want to scan the
+*   filename before starting to ensure that it contains only valid
+*   characters, since an invalid character is the most likely reason for a
+*   failure part of the way through.)
+*
+*   We recommend making the routine flexible in terms of the notation it
+*   accepts; e.g., on Unix, "/dir/sub/folder" and "/dir/sub/folder/" should
+*   be considered equivalent.
+*/
+bool os_mkdir(const char *dir, int create_parents);
+
+/*
+*   Remove a directory.  Returns true on success, false on failure.
+*
+*   If the directory isn't already empty, this routine fails.  That is, the
+*   routine does NOT recursively delete the contents of a non-empty
+*   directory.  It's up to the caller to delete any contents before removing
+*   the directory, if that's the caller's intention.  (Note to implementors:
+*   most native OS APIs to remove directories fail by default if the
+*   directory isn't empty, so it's usually safe to implement this simply by
+*   calling the native API.  However, if your system's version of this API
+*   can remove a non-empty directory, you MUST add an extra test before
+*   removing the directory to ensure it's empty, and return failure if it's
+*   not.  For the purposes of this test, "empty" should of course ignore any
+*   special objects that are automatically or implicitly present in all
+*   directories, such as the Unix "." and ".." relative links.)
+*/
+bool os_rmdir(const char *dir);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+*   Filename manipulation routines
+*/
+
+/* apply a default extension to a filename, if it doesn't already have one */
+void os_defext(char *fname, const char *ext);
+
+/* unconditionally add an extention to a filename */
+void os_addext(char *fname, const char *ext);
+
+/* remove the extension from a filename */
+void os_remext(char *fname);
+
+/*
+*   Compare two file names/paths for syntactic equivalence.  Returns true if
+*   the names are equivalent names according to the local file system's
+*   syntax conventions, false if not.  This does a syntax-only comparison of
+*   the paths, without looking anything up in the file system.  This means
+*   that a false return doesn't guarantee that the paths don't point to the
+*   same file.
+*
+*   This routine DOES make the following equivalences:
+*
+*   - if the local file system is insensitive to case, the names are
+*   compared ignoring case
+*
+*   - meaningless path separator difference are ignored: on Unix, "a/b" ==
+*   "a//b" == "a/b/"; on Windows, "a/b" == "a\\b"
+*
+*   - relative links that are strictly structural or syntactic are applied;
+*   for example, on Unix or Windows, "a/./b" == "a/b" = "a/b/c/..".  This
+*   only applies for special relative links that can be resolved without
+*   looking anything up in the file system.
+*
+*   This DOES NOT do the following:
+*
+*   - it doesn't apply working directories/volums to relative paths
+*
+*   - it doesn't follow symbolic links in the file system
+*/
+bool os_file_names_equal(const char *a, const char *b);
+
+/*
+*   Get a pointer to the root name portion of a filename.  This is the part
+*   of the filename after any path or directory prefix.  For example, on
+*   Unix, given the string "/home/mjr/deep.gam", this function should return
+*   a pointer to the 'd' in "deep.gam".  If the filename doesn't appear to
+*   have a path prefix, it should simply return the argument unchanged.
+*
+*   IMPORTANT: the returned pointer MUST point into the original 'buf'
+*   string, and the contents of that buffer must NOT be modified.  The
+*   return value must point into the same buffer because there are no
+*   allowances for the alternatives.  In particular, (a) you can't return a
+*   pointer to newly allocated memory, because callers won't free it, so
+*   doing so would cause a memory leak; and (b) you can't return a pointer
+*   to an internal static buffer, because callers might call this function
+*   more than once and still rely on a value returned on an older call,
+*   which would be invalid if a static buffer could be overwritten on each
+*   call.  For these reasons, it's required that the return value point to a
+*   position within the original string passed in 'buf'.
+*/
+const char *os_get_root_name(const char *buf);
+
+/*
+*   Determine whether a filename specifies an absolute or relative path.
+*   This is used to analyze filenames provided by the user (for example,
+*   in a #include directive, or on a command line) to determine if the
+*   filename can be considered relative or absolute.  This can be used,
+*   for example, to determine whether to search a directory path for a
+*   file; if a given filename is absolute, a path search makes no sense.
+*   A filename that doesn't specify an absolute path can be combined with
+*   a path using os_build_full_path().
+*
+*   Returns true if the filename specifies an absolute path, false if
+*   not.
+*/
+bool os_is_file_absolute(const char *fname);
+
+/*
+*   Extract the path from a filename.  Fills in pathbuf with the path
+*   portion of the filename.  If the filename has no path, the pathbuf
+*   should be set appropriately for the current directory (on Unix or DOS,
+*   for example, it can be set to an empty string).
+*
+*   The result can end with a path separator character or not, depending on
+*   local OS conventions.  Paths extracted with this function can only be
+*   used with os_build_full_path(), so the conventions should match that
+*   function's.
+*
+*   Unix examples:
+*
+*.   /home/mjr/deep.gam -> /home/mjr
+*.   games/deep.gam -> games
+*.   deep.gam -> [empty string]
+*
+*   Mac examples:
+*
+*    :home:mjr:deep.gam -> :home:mjr
+*.   Hard Disk:games:deep.gam -> Hard Disk:games
+*.   Hard Disk:deep.gam -> Hard Disk:
+*.   deep.gam -> [empty string]
+*
+*   VMS examples:
+*
+*.   SYS$DISK:[mjr.games]deep.gam -> SYS$DISK:[mjr.games]
+*.   SYS$DISK:[mjr.games] -> SYS$DISK:[mjr]
+*.   deep.gam -> [empty string]
+*
+*   Note in the last example that we've retained the trailing colon in the
+*   path, whereas we didn't in the others; although the others could also
+*   retain the trailing colon, it's required only for the last case.  The
+*   last case requires the colon because it would otherwise be impossible to
+*   determine whether "Hard Disk" was a local subdirectory or a volume name.
+*
+*/
+void os_get_path_name(char *pathbuf, size_t pathbuflen, const char *fname);
+
+/*
+*   Build a full path name, given a path and a filename.  The path may have
+*   been specified by the user, or may have been extracted from another file
+*   via os_get_path_name().  This routine must take care to add path
+*   separators as needed, but also must take care not to add too many path
+*   separators.
+*
+*   This routine should reformat the path into canonical format to the
+*   extent possible purely through syntactic analysis.  For example, special
+*   relative links, such as Unix "." and "..", should be resolved; for
+*   example, combining "a/./b/c" with ".." on Unix should yield "a/b".
+*   However, symbolic links that require looking up names in the file system
+*   should NOT be resolved.  We don't want to perform any actual file system
+*   lookups because might want to construct hypothetical paths that don't
+*   necessarily relate to files on the local system.
+*
+*   Note that relative path names may require special care on some
+*   platforms.  In particular, if the source path is relative, the result
+*   should also be relative.  For example, on the Macintosh, a path of
+*   "games" and a filename "deep.gam" should yield ":games:deep.gam" - note
+*   the addition of the leading colon to make the result path relative.
+*
+*   Note also that the 'filename' argument is not only allowed to be an
+*   ordinary file, possibly qualified with a relative path, but is also
+*   allowed to be a subdirectory.  The result in this case should be a path
+*   that can be used as the 'path' argument to a subsequent call to
+*   os_build_full_path; this allows a path to be built in multiple steps by
+*   descending into subdirectories one at a time.
+*
+*   Unix examples:
+*
+*.   /home/mjr + deep.gam -> /home/mjr/deep.gam"
+*.   /home/mjr + .. -> /home
+*.   /home/mjr + ../deep.gam -> /home/deep.gam
+*.   /home/mjr/ + deep.gam -> /home/mjr/deep.gam"
+*.   games + deep.gam -> games/deep.gam"
+*.   games/ + deep.gam -> games/deep.gam"
+*.   /home/mjr + games/deep.gam -> /home/mjr/games/deep.gam"
+*.   games + scifi/deep.gam -> games/scifi/deep.gam"
+*.   /home/mjr + games -> /home/mjr/games"
+*
+*   Mac examples:
+*
+*.   Hard Disk: + deep.gam -> Hard Disk:deep.gam
+*.   :games: + deep.gam -> :games:deep.gam
+*.   :games:deep + ::test.gam -> :games:test.gam
+*.   games + deep.gam -> :games:deep.gam
+*.   Hard Disk: + :games:deep.gam -> Hard Disk:games:deep.gam
+*.   games + :scifi:deep.gam -> :games:scifi:deep.gam
+*.   Hard Disk: + games -> Hard Disk:games
+*.   Hard Disk:games + scifi -> Hard Disk:games:scifi
+*.   Hard Disk:games:scifi + deep.gam -> Hard Disk:games:scifi:deep.gam
+*.   Hard Disk:games + :scifi:deep.gam -> Hard Disk:games:scifi:deep.gam
+*
+*   VMS examples:
+*
+*.   [home.mjr] + deep.gam -> [home.mjr]deep.gam
+*.   [home.mjr] + [-]deep.gam -> [home]deep.gam
+*.   mjr.dir + deep.gam -> [.mjr]deep.gam
+*.   [home]mjr.dir + deep.gam -> [home.mjr]deep.gam
+*.   [home] + [.mjr]deep.gam -> [home.mjr]deep.gam
+*/
+void os_build_full_path(char *fullpathbuf, size_t fullpathbuflen,
+	const char *path, const char *filename);
+
+/*
+*   Combine a path and a filename to form a full path to the file.  This is
+*   *almost* the same as os_build_full_path(), but if the 'filename' element
+*   is a special relative link, such as Unix '.' or '..', this preserves
+*   that special link in the final name.
+*
+*   Unix examples:
+*
+*.    /home/mjr + deep.gam -> /home/mjr/deep.gam
+*.    /home/mjr + . -> /home/mjr/.
+*.    /home/mjr + .. -> /home/mjr/..
+*
+*   Mac examples:
+*
+*.    Hard Disk:games + deep.gam -> HardDisk:games:deep.gam
+*.    Hard Disk:games + :: -> HardDisk:games::
+*
+*   VMS exmaples:
+*
+*.    [home.mjr] + deep.gam -> [home.mjr]deep.gam
+*.    [home.mjr] + [-] -> [home.mjr.-]
+*/
+void os_combine_paths(char *fullpathbuf, size_t pathbuflen,
+	const char *path, const char *filename);
+
+
+/*
+*   Get the absolute, fully qualified filename for a file.  This fills in
+*   'result_buf' with the absolute path to the given file, taking into
+*   account the current working directory and any other implied environment
+*   information that affects the way the file system would resolve the given
+*   file name to a specific file on disk if we opened the file now using
+*   this name.
+*
+*   The returned path should be in absolute path form, meaning that it's
+*   independent of the current working directory or any other environment
+*   settings.  That is, this path should still refer to the same file even
+*   if the working directory changes.
+*
+*   Note that it's valid to get the absolute path for a file that doesn't
+*   exist, or for a path with directory components that don't exist.  For
+*   example, a caller might generate the absolute path for a file that it's
+*   about to create, or a hypothetical filename for path comparison
+*   purposes.  The function should succeed even if the file or any path
+*   components don't exist.  If the file is in relative format, and any path
+*   elements don't exist but are syntactically well-formed, the result
+*   should be the path obtained from syntactically combining the working
+*   directory with the relative path.
+*
+*   On many systems, a given file might be reachable through more than one
+*   absolute path.  For example, on Unix it might be possible to reach a
+*   file through symbolic links to the file itself or to parent directories,
+*   or hard links to the file.  It's up to the implementation to determine
+*   which path to use in such cases.
+*
+*   On success, returns true.  If it's not possible to resolve the file name
+*   to an absolute path, the routine copies the original filename to the
+*   result buffer exactly as given, and returns false.
+*/
+bool os_get_abs_filename(char *result_buf, size_t result_buf_size,
+	const char *filename);
+
+/*
+*   Get the relative version of the given filename path 'filename', relative
+*   to the given base directory 'basepath'.  Both paths must be given in
+*   absolute format.
+*
+*   Returns true on success, false if it's not possible to rewrite the path
+*   in relative terms.  For example, on Windows, it's not possible to
+*   express a path on the "D:" drive as relative to a base path on the "C:"
+*   drive, since each drive letter has an independent root folder; there's
+*   no namespace entity enclosing a drive letter's root folder.  On
+*   Unix-like systems where the entire namespace has a single hierarchical
+*   root, it should always be possible to express any path relative to any
+*   other.
+*
+*   The result should be a relative path that can be combined with
+*   'basepath' using os_build_full_path() to reconstruct a path that
+*   identifies the same file as the original 'filename' (it's not important
+*   that this procedure would result in the identical string - it just has
+*   to point to the same file).  If it's not possible to express the
+*   filename relative to the base path, fill in 'result_buf' with the
+*   original filename and return false.
+*
+*   Windows examples:
+*
+*.    c:\mjr\games | c:\mjr\games\deep.gam  -> deep.gam
+*.    c:\mjr\games | c:\mjr\games\tads\deep.gam  -> tads\deep.gam
+*.    c:\mjr\games | c:\mjr\tads\deep.gam  -> ..\tads\deep.gam
+*.    c:\mjr\games | d:\deep.gam  ->  d:\deep.gam (and return false)
+*
+*   Mac OS examples:
+*
+*.    Mac HD:mjr:games | Mac HD:mjr:games:deep.gam -> deep.gam
+*.    Mac HD:mjr:games | Mac HD:mjr:games:tads:deep.gam -> :tads:deep.gam
+*.    Mac HD:mjr:games | Ext Disk:deep.gam -> Ext Disk:deep.gam (return false)
+*
+*   VMS examples:
+*
+*.    SYS$:[mjr.games] | SYS$:[mjr.games]deep.gam -> deep.gam
+*.    SYS$:[mjr.games] | SYS$:[mjr.games.tads]deep.gam -> [.tads]deep.gam
+*.    SYS$:[mjr.games] | SYS$:[mjr.tads]deep.gam -> [-.tads]deep.gam
+*.    SYS$:[mjr.games] | DISK$:[mjr]deep.gam -> DISK$[mjr]deep.gam (ret false)
+*/
+bool os_get_rel_path(char *result_buf, size_t result_buf_size,
+	const char *basepath, const char *filename);
+
+/*
+*   Determine if the given file is in the given directory.  Returns true if
+*   so, false if not.  'filename' is a relative or absolute file name;
+*   'path' is a relative or absolute directory path, such as one returned
+*   from os_get_path_name().
+*
+*   If 'include_subdirs' is true, the function returns true if the file is
+*   either directly in the directory 'path', OR it's in any subdirectory of
+*   'path'.  If 'include_subdirs' is false, the function returns true only
+*   if the file is directly in the given directory.
+*
+*   If 'match_self' is true, the function returns true if 'filename' and
+*   'path' are the same directory; otherwise it returns false in this case.
+*
+*   This routine is allowed to return "false negatives" - that is, it can
+*   claim that the file isn't in the given directory even when it actually
+*   is.  The reason is that it's not always possible to determine for sure
+*   that there's not some way for a given file path to end up in the given
+*   directory.  In contrast, a positive return must be reliable.
+*
+*   If possible, this routine should fully resolve the names through the
+*   file system to determine the path relationship, rather than merely
+*   analyzing the text superficially.  This can be important because many
+*   systems have multiple ways to reach a given file, such as via symbolic
+*   links on Unix; analyzing the syntax alone wouldn't reveal these multiple
+*   pathways.
+*
+*   SECURITY NOTE: If possible, implementations should fully resolve all
+*   symbolic links, relative paths (e.g., Unix ".."), etc, before rendering
+*   judgment.  One important application for this routine is to determine if
+*   a file is in a sandbox directory, to enforce security restrictions that
+*   prevent a program from accessing files outside of a designated folder.
+*   If the implementation fails to resolve symbolic links or relative paths,
+*   a malicious program or user could bypass the security restriction by,
+*   for example, creating a symbolic link within the sandbox directory that
+*   points to the root folder.  Implementations can avoid this loophole by
+*   converting the file and directory names to absolute paths and resolving
+*   all symbolic links and relative notation before comparing the paths.
+*/
+bool os_is_file_in_dir(const char *filename, const char *path,
+	bool include_subdirs, bool match_self);
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Convert an OS filename path to URL-style format.  This isn't a true URL
+ *   conversion; rather, it simply expresses a filename in Unix-style
+ *   notation, as a series of path elements separated by '/' characters.
+ *   Unlike true URLs, we don't use % encoding or a scheme prefix (file://,
+ *   etc).
+ *   
+ *   The result path never ends in a trailing '/', unless the entire result
+ *   path is "/".  This is for consistency; even if the source path ends with
+ *   a local path separator, the result doesn't.
+ *   
+ *   If the local file system syntax uses '/' characters as ordinary filename
+ *   characters, these must be replaced with some other suitable character in
+ *   the result, since otherwise they'd be taken as path separators when the
+ *   URL is parsed.  If possible, the substitution should be reversible with
+ *   respect to os_cvt_dir_url(), so that the same URL read back in on this
+ *   same platform will produce the same original filename.  One particular
+ *   suggestion is that if the local system uses '/' to delimit what would be
+ *   a filename extension on other platforms, replace '/' with '.', since
+ *   this will provide reversibility as well as a good mapping if the URL is
+ *   read back in on another platform.
+ *   
+ *   The local equivalents of "." and "..", if they exist, are converted to
+ *   "." and ".." in the URL notation.
+ *   
+ *   Examples:
+ *   
+ *.   Windows: images\rooms\startroom.jpg -> images/rooms/startroom.jpg
+ *.   Windows: ..\startroom.jpg -> ../startroom.jpg
+ *.   Mac:     :images:rooms:startroom.jpg -> images/rooms/startroom.jpg
+ *.   Mac:     ::startroom.jpg -> ../startroom.jpg
+ *.   VMS:     [.images.rooms]startroom.jpg -> images/rooms/startroom.jpg
+ *.   VMS:     [-.images]startroom.jpg -> ../images/startroom.jpg
+ *.   Unix:    images/rooms/startroom.jpg -> images/rooms/startroom.jpg
+ *.   Unix:    ../images/startroom.jpg -> ../images/startroom.jpg
+ *   
+ *   If the local name is an absolute path in the local file system (e.g.,
+ *   Unix /file, Windows C:\file), translate as follows.  If the local
+ *   operating system uses a volume or device designator (Windows C:, VMS
+ *   SYS$DISK:, etc), make the first element of the path the exact local
+ *   syntax for the device designator: /C:/ on Windows, /SYS$DISK:/ on VMS,
+ *   etc.  Include the local syntax for the device prefix.  For a system like
+ *   Unix with a unified file system root ("/"), simply start with the root
+ *   directory.  Examples:
+ *   
+ *.    Windows:  C:\games\deep.gam         -> /C:/games/deep.gam
+ *.    Windows:  C:games\deep.gam          -> /C:./games/deep.gam
+ *.    Windows:  \\SERVER\DISK\games\deep.gam -> /\\SERVER/DISK/games/deep.gam
+ *.    Mac OS 9: Hard Disk:games:deep.gam  -> /Hard Disk:/games/deep.gam
+ *.    VMS:      SYS$DISK:[games]deep.gam  -> /SYS$DISK:/games/deep.gam
+ *.    Unix:     /games/deep.gam           -> /games/deep.gam
+ *   
+ *   Rationale: it's effectively impossible to create a truly portable
+ *   representation of an absolute path.  Operating systems are too different
+ *   in the way they represent root paths, and even if that were solvable, a
+ *   root path is essentially unusable across machines anyway because it
+ *   creates a dependency on the contents of a particular machine's disk.  So
+ *   if we're called upon to translate an absolute path, we can forget about
+ *   trying to be truly portable and instead focus on round-trip fidelity -
+ *   i.e., making sure that applying os_cvt_url_dir() to our result recovers
+ *   the exact original path, assuming it's done on the same operating
+ *   system.  The approach outlined above should achieve round-trip fidelity
+ *   when a local path is converted to a URL and back on the same machine,
+ *   since the local URL-to-path converter should recognize its own special
+ *   type of local absolute path prefix.  It also produces reasonable results
+ *   on other platforms - see the os_cvt_url_dir() comments below for
+ *   examples of the decoding results for absolute paths moved to new
+ *   platforms.  The result when a device-rooted absolute path is encoded on
+ *   one machine and then decoded on another will generally be a local path
+ *   with a root on the default device/volume and an outermost directory with
+ *   a name based on the original machine's device/volume name.  This
+ *   obviously won't reproduce the exact original path, but since that's
+ *   impossible anyway, this is probably as good an approximation as we can
+ *   create.
+ *   
+ *   Character sets: the input could be in local or UTF-8 character sets.
+ *   The implementation shouldn't care, though - just treat bytes in the
+ *   range 0-127 as plain ASCII, and everything else as opaque.  I.e., do not
+ *   quote or otherwise modify characters outside the 0-127 range.  
+ */
+void os_cvt_dir_url(char *result_buf, size_t result_buf_size,
+                    const char *src_path);
+
+/*
+ *   Convert a URL-style path into a filename path expressed in the local
+ *   file system's syntax.  Fills in result_buf with a file path, constructed
+ *   using the local file system syntax, that corresponds to the path in
+ *   src_url expressed in URL-style syntax.  Examples:
+ *   
+ *   images/rooms/startroom.jpg -> 
+ *.   Windows   -> images\rooms\startroom.jpg
+ *.   Mac OS 9  -> :images:rooms:startroom.jpg
+ *.   VMS       -> [.images.rooms]startroom.jpg
+ *   
+ *   The source format isn't a true URL; it's simply a series of path
+ *   elements separated by '/' characters.  Unlike true URLs, our input
+ *   format doesn't use % encoding and doesn't have a scheme (file://, etc).
+ *   (Any % in the source is treated as an ordinary character and left as-is,
+ *   even if it looks like a %XX sequence.  Anything that looks like a scheme
+ *   prefix is left as-is, with any // treated as path separators.
+ *   
+ *   images/file%20name.jpg ->
+ *.   Windows   -> images\file%20name.jpg
+ *   
+ *   file://images/file.jpg ->
+ *.   Windows   -> file_\\images\file.jpg
+ *   
+ *   Any characters in the path that are invalid in the local file system
+ *   naming rules are converted to "_", unless "_" is itself invalid, in
+ *   which case they're converted to "X".  One exception is that if '/' is a
+ *   valid local filename character (rather than a path separator as it is on
+ *   Unix and Windows), it can be used as the replacement for the character
+ *   that os_cvt_dir_url uses as its replacement for '/', so that this
+ *   substitution is reversible when a URL is generated and then read back in
+ *   on this same platform.
+ *   
+ *   images/file:name.jpg ->
+ *.   Windows   -> images\file_name.jpg
+ *.   Mac OS 9  -> :images:file_name.jpg
+ *.   Unix      -> images/file:name.jpg
+ *   
+ *   The path elements "." and ".." are specifically defined as having their
+ *   Unix meanings: "." is an alias for the preceding path element, or the
+ *   working directory if it's the first element, and ".." is an alias for
+ *   the parent of the preceding element.  When these appear as path
+ *   elements, this routine translates them to the appropriate local
+ *   conventions.  "." may be translated simply by removing it from the path,
+ *   since it reiterates the previous path element.  ".." may be translated
+ *   by removing the previous element - HOWEVER, if ".." appears as the first
+ *   element, it has to be retained and translated to the equivalent local
+ *   notation, since it will have to be applied later, when the result_buf
+ *   path is actually used to open a file, at which point it will combined
+ *   with the working directory or another base path.
+ *   
+ *.  /images/../file.jpg -> [Windows] file.jpg
+ *.  ../images/file.jpg ->
+ *.   Windows  -> ..\images\file.jpg
+ *.   Mac OS 9 -> ::images:file.jpg
+ *.   VMS      -> [-.images]file.jpg
+ *   
+ *   If the URL path is absolute (starts with a '/'), the routine inspects
+ *   the path to see if it was created by the same OS, according to the local
+ *   rules for converting absolute paths in os_cvt_dir_url() (see).  If so,
+ *   we reverse the encoding done there.  If it doesn't appear that the name
+ *   was created by the same operating system - that is, if reversing the
+ *   encoding doesn't produce a valid local filename - then we create a local
+ *   absolute path as follows.  If the local system uses device/volume
+ *   designators, we start with the current working device/volume or some
+ *   other suitable default volume.  We then add the first element of the
+ *   path, if any, as the root directory name, applying the usual "_" or "X"
+ *   substitution for any characters that aren't allowed in local names.  The
+ *   rest of the path is handled in the usual fashion.
+ *   
+ *.  /images/file.jpg ->
+ *.    Windows -> \images\file.jpg
+ *.    Unix    -> /images/file.jpg
+ *   
+ *.  /c:/images/file.jpg ->
+ *.    Windows -> c:\images\file.jpg
+ *.    Unix    -> /c:/images/file.jpg
+ *.    VMS     -> SYS$DISK:[c__.images]file.jpg
+ *   
+ *.  /Hard Disk:/images/file.jpg ->
+ *.    Windows -> \Hard Disk_\images\file.jpg
+ *.    Unix    -> SYS$DISK:[Hard_Disk_.images]file.jpg
+ *   
+ *   Note how the device/volume prefix becomes the top-level directory when
+ *   moving a path across machines.  It's simply not possible to reconstruct
+ *   the exact original path in such cases, since device/volume syntax rules
+ *   have little in common across systems.  But this seems like a good
+ *   approximation in that (a) it produces a valid local path, and (b) it
+ *   gives the user a reasonable basis for creating a set of folders to mimic
+ *   the original source system, if they want to use that approach to port
+ *   the data rather than just changing the paths internally in the source
+ *   material.
+ *   
+ *   Character sets: use the same rules as for os_cvt_dir_url().  
+ */
+void os_cvt_url_dir(char *result_buf, size_t result_buf_size,
+                    const char *src_url);
+
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/os_glk.cpp b/engines/glk/tads/os_glk.cpp
new file mode 100644
index 0000000..5565086
--- /dev/null
+++ b/engines/glk/tads/os_glk.cpp
@@ -0,0 +1,1002 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/os_glk.h"
+#include "glk/tads/tads.h"
+#include "glk/tads/os_buffer.h"
+
+namespace Glk {
+namespace TADS {
+
+static void redraw_windows(void);
+static void os_status_redraw(void);
+extern void os_banners_redraw(void);
+
+static char lbuf[256], rbuf[256];
+static int curwin = 0;
+static int curattr = 0;
+
+winid_t mainwin;
+winid_t statuswin;
+
+uint mainfg;
+uint mainbg;
+
+uint statusfg;
+uint statusbg;
+
+/* ------------------------------------------------------------------------ */
+
+/* 
+ *   Initialize.  This should be called during program startup to
+ *   initialize the OS layer and check OS-specific command-line arguments.
+ *   
+ *   If 'prompt' and 'buf' are non-null, and there are no arguments on the
+ *   given command line, the OS code can use the prompt to ask the user to
+ *   supply a filename, then store the filename in 'buf' and set up
+ *   argc/argv to give a one-argument command string.  (This mechanism for
+ *   prompting for a filename is obsolescent, and is retained for
+ *   compatibility with a small number of existing implementations only;
+ *   new implementations should ignore this mechanism and leave the
+ *   argc/argv values unchanged.)  
+ */
+int os_init(int *argc, char *argv[], const char *prompt,
+            char *buf, int bufsiz)
+{
+    mainwin = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
+    if (!mainwin)
+        error("fatal: could not open window!\n");
+
+    /* get default colors for main window */
+    if (!g_vm->glk_style_measure(mainwin, style_Normal, stylehint_TextColor, &mainfg))
+        mainfg = 0;
+
+    if (!g_vm->glk_style_measure(mainwin, style_Normal, stylehint_BackColor, &mainbg))
+        mainbg = 0;
+
+    /* get default colors for status window */
+    statuswin = g_vm->glk_window_open(mainwin,
+            winmethod_Above | winmethod_Fixed, 1,
+            wintype_TextGrid, 0);
+
+    if (!g_vm->glk_style_measure(statuswin, style_Normal, stylehint_TextColor, &statusfg))
+        statusfg = 0;
+
+    if (!g_vm->glk_style_measure(statuswin, style_Normal, stylehint_BackColor, &statusbg))
+        statusbg = 0;
+
+    /* close statuswin; reopened on request */
+    g_vm->glk_window_close(statuswin, 0);
+
+    statuswin = NULL;
+
+    g_vm->glk_set_window(mainwin);
+
+    strcpy(rbuf, "");
+
+    return 0;
+}
+
+/*
+ *   Uninitialize.  This is called prior to progam termination to reverse
+ *   the effect of any changes made in os_init().  For example, if
+ *   os_init() put the terminal in raw mode, this should restore the
+ *   previous terminal mode.  This routine should not terminate the
+ *   program (so don't call exit() here) - the caller might have more
+ *   processing to perform after this routine returns.
+ */
+void os_uninit(void)
+{
+}
+
+void os_term(int status) {
+	g_vm->quitGame();
+}
+
+void os_instbrk(int install) {
+	// No implementation
+}
+
+bool os_break() {
+	return false;
+}
+
+void os_sleep_ms(long delay_in_milliseconds) {
+	g_system->delayMillis(delay_in_milliseconds);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Get system information.  'code' is a SYSINFO_xxx code, which
+ *   specifies what type of information to get.  The 'param' argument's
+ *   meaning depends on which code is selected.  'result' is a pointer to
+ *   an integer that is to be filled in with the result value.  If the
+ *   code is not known, this function should return false.  If the code is
+ *   known, the function should fill in *result and return true.
+ */
+int os_get_sysinfo(int code, void *param, long *result) {
+    switch (code)
+    {
+        case SYSINFO_TEXT_HILITE:
+            *result = 1;
+            return true;
+        case SYSINFO_BANNERS:
+            *result = 1;
+            return true;
+        case SYSINFO_TEXT_COLORS:
+            *result = SYSINFO_TXC_RGB;
+            return true;
+
+#ifdef USE_HTML
+        case SYSINFO_INTERP_CLASS:
+            *result = SYSINFO_ICLASS_HTML;
+            return true;
+        case SYSINFO_HTML:
+            *result = 1;
+            return true;
+#else
+        case SYSINFO_INTERP_CLASS:
+            *result = SYSINFO_ICLASS_TEXTGUI;
+            return true;
+        case SYSINFO_HTML:
+            *result = 0;
+            return true;
+#endif
+
+        case SYSINFO_JPEG:
+        case SYSINFO_PNG:
+        case SYSINFO_WAV:
+        case SYSINFO_MIDI:
+        case SYSINFO_WAV_MIDI_OVL:
+        case SYSINFO_WAV_OVL:
+        case SYSINFO_PREF_IMAGES:
+        case SYSINFO_PREF_SOUNDS:
+        case SYSINFO_PREF_MUSIC:
+        case SYSINFO_PREF_LINKS:
+        case SYSINFO_MPEG:
+        case SYSINFO_MPEG1:
+        case SYSINFO_MPEG2:
+        case SYSINFO_MPEG3:
+        case SYSINFO_LINKS_HTTP:
+        case SYSINFO_LINKS_FTP:
+        case SYSINFO_LINKS_NEWS:
+        case SYSINFO_LINKS_MAILTO:
+        case SYSINFO_LINKS_TELNET:
+        case SYSINFO_PNG_TRANS:
+        case SYSINFO_PNG_ALPHA:
+        case SYSINFO_OGG:
+            *result = 0;
+            return true;
+
+        default:
+            return false;
+    }
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Display routines.
+ *   
+ *   Our display model is a simple stdio-style character stream.
+ *   
+ *   In addition, we provide an optional "status line," which is a
+ *   non-scrolling area where a line of text can be displayed.  If the status
+ *   line is supported, text should only be displayed in this area when
+ *   os_status() is used to enter status-line mode (mode 1); while in status
+ *   line mode, text is written to the status line area, otherwise (mode 0)
+ *   it's written to the normal main text area.  The status line is normally
+ *   shown in a different color to set it off from the rest of the text.
+ *   
+ *   The OS layer can provide its own formatting (word wrapping in
+ *   particular) if it wants, in which case it should also provide pagination
+ *   using os_more_prompt().  
+ */
+
+/*
+ *   Print a string on the console.  These routines come in two varieties:
+ *   
+ *   os_printz - write a NULL-TERMINATED string
+ *.  os_print - write a COUNTED-LENGTH string, which may not end with a null
+ *   
+ *   These two routines are identical except that os_printz() takes a string
+ *   which is terminated by a null byte, and os_print() instead takes an
+ *   explicit length, and a string that may not end with a null byte.
+ *   
+ *   os_printz(str) may be implemented as simply os_print(str, strlen(str)).
+ *   
+ *   The string is written in one of three ways, depending on the status mode
+ *   set by os_status():
+ *   
+ *   status mode == 0 -> write to main text window
+ *.  status mode == 1 -> write to status line
+ *.  anything else -> do not display the text at all
+ *   
+ *   Implementations are free to omit any status line support, in which case
+ *   they should simply suppress all output when the status mode is anything
+ *   other than zero.
+ *   
+ *   The following special characters must be recognized in the displayed
+ *   text:
+ *   
+ *   '\n' - newline: end the current line and move the cursor to the start of
+ *   the next line.  If the status line is supported, and the current status
+ *   mode is 1 (i.e., displaying in the status line), then two special rules
+ *   apply to newline handling: newlines preceding any other text should be
+ *   ignored, and a newline following any other text should set the status
+ *   mode to 2, so that all subsequent output is suppressed until the status
+ *   mode is changed with an explicit call by the client program to
+ *   os_status().
+ *   
+ *   '\r' - carriage return: end the current line and move the cursor back to
+ *   the beginning of the current line.  Subsequent output is expected to
+ *   overwrite the text previously on this same line.  The implementation
+ *   may, if desired, IMMEDIATELY clear the previous text when the '\r' is
+ *   written, rather than waiting for subsequent text to be displayed.
+ *   
+ *   All other characters may be assumed to be ordinary printing characters.
+ *   The routine need not check for any other special characters.
+ *   
+ */
+
+void os_printz(const char *str) {
+    os_print(str, strlen(str));
+}
+
+void os_print(const char *str, size_t len) {
+    if (curwin == 0 && str)
+        os_put_buffer(str, len);
+
+    if (curwin == 1)
+    {
+        const char *p;
+        size_t      rem, max;
+
+        /* The string requires some fiddling for the status window */
+        for (p = str, rem = len ; rem != 0 && *p == '\n'; p++, --rem)
+            ;
+        if (rem != 0 && p[rem-1] == '\n')
+            --rem;
+
+        /* if that leaves anything, update the statusline */
+        if (rem != 0)
+        {
+            max = sizeof(lbuf) - strlen(lbuf) - 1;
+            strncat(lbuf, p, rem > max ? max : rem);
+            os_status_redraw();
+        }
+    }
+}
+
+
+/* 
+ *   Set the status line mode.  There are three possible settings:
+ *   
+ *   0 -> main text mode.  In this mode, all subsequent text written with
+ *   os_print() and os_printz() is to be displayed to the main text area.
+ *   This is the normal mode that should be in effect initially.  This mode
+ *   stays in effect until an explicit call to os_status().
+ *   
+ *   1 -> statusline mode.  In this mode, text written with os_print() and
+ *   os_printz() is written to the status line, which is usually rendered as
+ *   a one-line area across the top of the terminal screen or application
+ *   window.  In statusline mode, leading newlines ('\n' characters) are to
+ *   be ignored, and any newline following any other character must change
+ *   the mode to 2, as though os_status(2) had been called.
+ *   
+ *   2 -> suppress mode.  In this mode, all text written with os_print() and
+ *   os_printz() must simply be ignored, and not displayed at all.  This mode
+ *   stays in effect until an explicit call to os_status().  
+ */
+
+void os_status(int stat)
+{
+    curwin = stat;
+
+    if (stat == 1)
+    {
+        if (statuswin == NULL)
+        {
+            g_vm->glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
+            statuswin = g_vm->glk_window_open(mainwin,
+                                        winmethod_Above | winmethod_Fixed, 1,
+                                        wintype_TextGrid, 0);
+        }
+        strcpy(lbuf, "");
+    }
+}
+
+/* get the status line mode */
+int os_get_status()
+{
+    return curwin;
+}
+
+/* 
+ *   Set the score value.  This displays the given score and turn counts on
+ *   the status line.  In most cases, these values are displayed at the right
+ *   edge of the status line, in the format "score/turns", but the format is
+ *   up to the implementation to determine.  In most cases, this can simply
+ *   be implemented as follows:
+ *   
+ */
+void os_score(int score, int turncount)
+{
+    char buf[40];
+    sprintf(buf, "%d/%d", score, turncount);
+    os_strsc(buf);
+}
+
+/* display a string in the score area in the status line */
+void os_strsc(const char *p)
+{
+    snprintf(rbuf, sizeof rbuf, "%s", p);
+    os_status_redraw();
+}
+
+static void os_status_redraw(void) {
+    char fmt[32];
+    char buf[256];
+    uint wid;
+    uint div;
+
+    if (!statuswin)
+        return;
+
+    g_vm->glk_window_get_size(statuswin, &wid, NULL);
+    div = wid - strlen(rbuf) - 3;
+
+    sprintf(fmt, " %%%ds %%s ", - (int)div);
+    sprintf(buf, fmt, lbuf, rbuf);
+
+    g_vm->glk_window_clear(statuswin);
+    g_vm->glk_set_window(statuswin);
+    g_vm->glk_set_style(style_User1);
+    os_put_buffer(buf, strlen(buf));
+    g_vm->glk_set_window(mainwin);
+}
+
+static void redraw_windows(void)
+{
+    os_status_redraw();
+    os_banners_redraw();
+}
+
+/* clear the screen */
+void oscls(void)
+{
+    g_vm->glk_window_clear(mainwin);
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Set text attributes.  Text subsequently displayed through os_print() and
+ *   os_printz() are to be displayed with the given attributes.
+ *   
+ *   'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values.  A value
+ *   of zero indicates normal text, with no extra attributes.  
+ */
+void os_set_text_attr(int attr)
+{
+    curattr = attr;
+    if (curattr & OS_ATTR_BOLD && curattr & OS_ATTR_ITALIC)
+        g_vm->glk_set_style(style_Alert);
+    else if (curattr & OS_ATTR_BOLD)
+        g_vm->glk_set_style(style_Subheader);
+    else if (curattr & OS_ATTR_ITALIC)
+        g_vm->glk_set_style(style_Emphasized);
+    else
+        g_vm->glk_set_style(style_Normal);
+}
+
+/*
+ *   Set the text foreground and background colors.  This sets the text
+ *   color for subsequent os_printf() and os_vprintf() calls.
+ *   
+ *   The background color can be OS_COLOR_TRANSPARENT, in which case the
+ *   background color is "inherited" from the current screen background.
+ *   Note that if the platform is capable of keeping old text for
+ *   "scrollback," then the transparency should be a permanent attribute of
+ *   the character - in other words, it should not be mapped to the current
+ *   screen color in the scrollback buffer, because doing so would keep the
+ *   current screen color even if the screen color changes in the future. 
+ *   
+ *   Text color support is optional.  If the platform doesn't support text
+ *   colors, this can simply do nothing.  If the platform supports text
+ *   colors, but the requested color or attributes cannot be displayed, the
+ *   implementation should use the best available approximation.  
+ */
+void os_set_text_color(os_color_t fg, os_color_t bg) {
+}
+
+/*
+ *   Set the screen background color.  This sets the text color for the
+ *   background of the screen.  If possible, this should immediately redraw
+ *   the main text area with this background color.  The color is given as an
+ *   OS_COLOR_xxx value.
+ *   
+ *   If the platform is capable of redisplaying the existing text, then any
+ *   existing text that was originally displayed with 'transparent'
+ *   background color should be redisplayed with the new screen background
+ *   color.  In other words, the 'transparent' background color of previously
+ *   drawn text should be a permanent attribute of the character - the color
+ *   should not be mapped on display to the then-current background color,
+ *   because doing so would lose the transparency and thus retain the old
+ *   screen color on a screen color change.  
+ */
+void os_set_screen_color(os_color_t color)
+{
+}
+
+/*
+ *   Set the game title.  The output layer calls this routine when a game
+ *   sets its title (via an HTML <title> tag, for example).  If it's
+ *   convenient to do so, the OS layer can use this string to set a window
+ *   caption, or whatever else makes sense on each system.  Most
+ *   character-mode implementations will provide an empty implementation,
+ *   since there's not usually any standard way to show the current
+ *   application title on a character-mode display.  
+ */
+void os_set_title(const char *title)
+{
+#ifdef GARGLK
+    g_vm->garglk_set_story_title(title);
+#endif
+}
+
+/*
+ *   Show the system-specific MORE prompt, and wait for the user to respond.
+ *   Before returning, remove the MORE prompt from the screen.
+ *   
+ *   This routine is only used and only needs to be implemented when the OS
+ *   layer takes responsibility for pagination; this will be the case on
+ *   most systems that use proportionally-spaced (variable-pitch) fonts or
+ *   variable-sized windows, since on such platforms the OS layer must do
+ *   most of the formatting work, leaving the standard output layer unable
+ *   to guess where pagination should occur.
+ *   
+ *   If the portable output formatter handles the MORE prompt, which is the
+ *   usual case for character-mode or terminal-style implementations, this
+ *   routine is not used and you don't need to provide an implementation.
+ *   Note that HTML TADS provides an implementation of this routine, because
+ *   the HTML renderer handles line breaking and thus must handle
+ *   pagination.  
+ */
+void os_more_prompt()
+{
+    os_printz("\n[more]\n");
+    os_waitc();
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   User Input Routines
+ */
+
+/*
+ *   Ask the user for a filename, using a system-dependent dialog or other
+ *   mechanism.  Returns one of the OS_AFE_xxx status codes (see below).
+ *   
+ *   prompt_type is the type of prompt to provide -- this is one of the
+ *   OS_AFP_xxx codes (see below).  The OS implementation doesn't need to
+ *   pay any attention to this parameter, but it can be used if desired to
+ *   determine the type of dialog to present if the system provides
+ *   different types of dialogs for different types of operations.
+ *   
+ *   file_type is one of the OSFTxxx codes for system file type.  The OS
+ *   implementation is free to ignore this information, but can use it to
+ *   filter the list of files displayed if desired; this can also be used
+ *   to apply a default suffix on systems that use suffixes to indicate
+ *   file type.  If OSFTUNK is specified, it means that no filtering
+ *   should be performed, and no default suffix should be applied.  
+ */
+int os_askfile(const char *prompt, char *fname_buf, int fname_buf_len,
+               int prompt_type, os_filetype_t file_type)
+{
+    frefid_t fileref;
+    uint gprompt, gusage;
+
+    if (prompt_type == OS_AFP_OPEN)
+        gprompt = filemode_Read;
+    else
+        gprompt = filemode_ReadWrite;
+
+    if (file_type == OSFTSAVE || file_type == OSFTT3SAV)
+        gusage = fileusage_SavedGame;
+    else if (file_type == OSFTLOG || file_type == OSFTTEXT)
+        gusage = fileusage_Transcript;
+    else
+        gusage = fileusage_Data;
+
+    fileref = g_vm->glk_fileref_create_by_prompt(gusage, (FileMode)gprompt, 0);
+    if (fileref == NULL)
+        return OS_AFE_CANCEL;
+
+    strcpy(fname_buf, g_vm->garglk_fileref_get_name(fileref));
+
+    g_vm->glk_fileref_destroy(fileref);
+
+    return OS_AFE_SUCCESS;
+}
+
+/* 
+ *   Read a string of input.  Fills in the buffer with a null-terminated
+ *   string containing a line of text read from the standard input.  The
+ *   returned string should NOT contain a trailing newline sequence.  On
+ *   success, returns 'buf'; on failure, including end of file, returns a
+ *   null pointer.  
+ */
+unsigned char *os_gets(unsigned char *buf, size_t buflen)
+{
+    event_t event;
+	char *b = (char *)buf;
+
+    os_get_buffer(b, buflen, 0);
+
+    do
+    {
+        g_vm->glk_select(&event);
+        if (event.type == evtype_Arrange)
+            redraw_windows();
+    }
+    while (event.type != evtype_LineInput);
+
+    return (unsigned char *)os_fill_buffer(b, event.val1);
+}
+
+/*
+ *   Read a string of input with an optional timeout.  This behaves like
+ *   os_gets(), in that it allows the user to edit a line of text (ideally
+ *   using the same editing keys that os_gets() does), showing the line of
+ *   text under construction during editing.  This routine differs from
+ *   os_gets() in that it returns if the given timeout interval expires
+ *   before the user presses Return (or the local equivalent).
+ *   
+ *   If the user presses Return before the timeout expires, we store the
+ *   command line in the given buffer, just as os_gets() would, and we
+ *   return OS_EVT_LINE.  We also update the display in the same manner that
+ *   os_gets() would, by moving the cursor to a new line and scrolling the
+ *   displayed text as needed.
+ *   
+ *   If a timeout occurs before the user presses Return, we store the
+ *   command line so far in the given buffer, statically store the cursor
+ *   position, insert mode, buffer text, and anything else relevant to the
+ *   editing state, and we return OS_EVT_TIMEOUT.
+ *   
+ *   If the implementation does not support the timeout operation, this
+ *   routine should simply return OS_EVT_NOTIMEOUT immediately when called;
+ *   the routine should not allow the user to perform any editing if the
+ *   timeout is not supported.  Callers must use the ordinary os_gets()
+ *   routine, which has no timeout capabilities, if the timeout is not
+ *   supported.
+ *   
+ *   When we return OS_EVT_TIMEOUT, the caller is responsible for doing one
+ *   of two things.
+ *   
+ *   The first possibility is that the caller performs some work that
+ *   doesn't require any display operations (in other words, the caller
+ *   doesn't invoke os_printf, os_getc, or anything else that would update
+ *   the display), and then calls os_gets_timeout() again.  In this case, we
+ *   will use the editing state that we statically stored before we returned
+ *   OS_EVT_TIMEOUT to continue editing where we left off.  This allows the
+ *   caller to perform some computation in the middle of user command
+ *   editing without interrupting the user - the extra computation is
+ *   transparent to the user, because we act as though we were still in the
+ *   midst of the original editing.
+ *   
+ *   The second possibility is that the caller wants to update the display.
+ *   In this case, the caller must call os_gets_cancel() BEFORE making any
+ *   display changes.  Then, the caller must do any post-input work of its
+ *   own, such as updating the display mode (for example, closing HTML font
+ *   tags that were opened at the start of the input).  The caller is now
+ *   free to do any display work it wants.
+ *   
+ *   If we have information stored from a previous call that was interrupted
+ *   by a timeout, and os_gets_cancel(true) was never called, we will resume
+ *   editing where we left off when the cancelled call returned; this means
+ *   that we'll restore the cursor position, insertion state, and anything
+ *   else relevant.  Note that if os_gets_cancel(false) was called, we must
+ *   re-display the command line under construction, but if os_gets_cancel()
+ *   was never called, we will not have to make any changes to the display
+ *   at all.
+ *   
+ *   Note that when resuming an interrupted editing session (interrupted via
+ *   os_gets_cancel()), the caller must re-display the prompt prior to
+ *   invoking this routine.
+ *   
+ *   Note that we can return OS_EVT_EOF in addition to the other codes
+ *   mentioned above.  OS_EVT_EOF indicates that an error occurred reading,
+ *   which usually indicates that the application is being terminated or
+ *   that some hardware error occurred reading the keyboard.  
+ *   
+ *   If 'use_timeout' is false, the timeout should be ignored.  Without a
+ *   timeout, the function behaves the same as os_gets(), except that it
+ *   will resume editing of a previously-interrupted command line if
+ *   appropriate.  (This difference is why the timeout is optional: a caller
+ *   might not need a timeout, but might still want to resume a previous
+ *   input that did time out, in which case the caller would invoke this
+ *   routine with use_timeout==false.  The regular os_gets() would not
+ *   satisfy this need, because it cannot resume an interrupted input.)  
+ */
+static char * timebuf = NULL;
+static size_t timelen = 0;
+
+int os_gets_timeout(unsigned char *buf, size_t bufl,
+                    unsigned long timeout_in_milliseconds, int use_timeout)
+{
+#if defined GLK_TIMERS && defined GLK_MODULE_LINE_ECHO
+    int timer = use_timeout ? timeout_in_milliseconds : 0;
+    int timeout = 0;
+    int initlen = 0;
+    event_t event;
+
+    /* restore saved buffer contents */
+    if (timebuf)
+    {
+        assert(timelen && timelen <= bufl);
+        memcpy(buf, timebuf, timelen);
+        initlen = timelen - 1;
+        buf[initlen] = 0;
+        free(timebuf);
+        timebuf = 0;
+    }
+
+    /* start timer and turn off line echo */
+    if (timer)
+    {
+        g_vm->glk_request_timer_events(timer);
+        g_vm->glk_set_echo_line_event(mainwin, 0);
+    }
+
+    os_get_buffer(buf, bufl, initlen);
+
+    do
+    {
+        g_vm->glk_select(&event);
+        if (event.type == evtype_Arrange)
+            redraw_windows();
+        else if (event.type == evtype_Timer && (timeout = 1))
+            g_vm->glk_cancel_line_event(mainwin, &event);
+    }
+    while (event.type != evtype_LineInput);
+
+    char *res = os_fill_buffer(buf, event.val1);
+
+    /* stop timer and turn on line echo */
+    if (timer)
+    {
+        g_vm->glk_request_timer_events(0);
+        g_vm->glk_set_echo_line_event(mainwin, 1);
+    }
+
+    /* save or print buffer contents */
+    if (res && timer)
+    {
+        if (timeout)
+        {
+            timelen = strlen(buf) + 1;
+            timebuf = malloc(timelen);
+            memcpy(timebuf, buf, timelen);
+        }
+        else
+        {
+            g_vm->glk_set_style(style_Input);
+            os_print(buf, strlen(buf));
+            os_print("\n", 1);
+            g_vm->glk_set_style(style_Normal);
+        }
+    }
+
+    return timeout ? OS_EVT_TIMEOUT : res ? OS_EVT_LINE : OS_EVT_EOF;
+#else
+    return OS_EVT_NOTIMEOUT;
+#endif
+}
+
+/*
+ *   Cancel an interrupted editing session.  This MUST be called if any
+ *   output is to be displayed after a call to os_gets_timeout() returns
+ *   OS_EVT_TIMEOUT.
+ *   
+ *   'reset' indicates whether or not we will forget the input state saved
+ *   by os_gets_timeout() when it last returned.  If 'reset' is true, we'll
+ *   clear the input state, so that the next call to os_gets_timeout() will
+ *   start with an empty input buffer.  If 'reset' is false, we will retain
+ *   the previous input state, if any; this means that the next call to
+ *   os_gets_timeout() will re-display the same input buffer that was under
+ *   construction when it last returned.
+ *   
+ *   This routine need not be called if os_gets_timeout() is to be called
+ *   again with no other output operations between the previous
+ *   os_gets_timeout() call and the next one.
+ *   
+ *   Note that this routine needs only a trivial implementation when
+ *   os_gets_timeout() is not supported (i.e., the function always returns
+ *   OS_EVT_NOTIMEOUT).  
+ */
+void os_gets_cancel(int reset)
+{
+#if defined GLK_TIMERS && defined GLK_MODULE_LINE_ECHO
+    if (timebuf)
+    {
+        g_vm->glk_set_style(style_Input);
+        os_print(timebuf, strlen(timebuf));
+        os_print("\n", 1);
+        g_vm->glk_set_style(style_Normal);
+
+        if (reset)
+        {
+            free(timebuf);
+            timebuf = 0;
+        }
+    }
+#endif
+}
+
+/* 
+ *   Read a character from the keyboard.  For extended keystrokes, this
+ *   function returns zero, and then returns the CMD_xxx code for the
+ *   extended keystroke on the next call.  For example, if the user
+ *   presses the up-arrow key, the first call to os_getc() should return
+ *   0, and the next call should return CMD_UP.  Refer to the CMD_xxx
+ *   codes below.
+ *   
+ *   os_getc() should return a high-level, translated command code for
+ *   command editing.  This means that, where a functional interpretation
+ *   of a key and the raw key-cap interpretation both exist as CMD_xxx
+ *   codes, the functional interpretation should be returned.  For
+ *   example, on Unix, Ctrl-E is conventionally used in command editing to
+ *   move to the end of the line, following Emacs key bindings.  Hence,
+ *   os_getc() should return CMD_END for this keystroke, rather than
+ *   (CMD_CTRL + 'E' - 'A'), because CMD_END is the high-level command
+ *   code for the operation.
+ *   
+ *   The translation ability of this function allows for system-dependent
+ *   key mappings to functional meanings.  
+ */
+static int glktotads(unsigned int key)
+{
+    if (key < 256)
+        return key;
+    switch (key)
+    {
+        case keycode_Up:
+            return CMD_UP;
+        case keycode_Down:
+            return CMD_DOWN;
+        case keycode_Left:
+            return CMD_LEFT;
+        case keycode_Right:
+            return CMD_RIGHT;
+        case keycode_PageUp:
+            return CMD_PGUP;
+        case keycode_PageDown:
+            return CMD_PGDN;
+        case keycode_Home:
+            return CMD_HOME;
+        case keycode_End:
+            return CMD_END;
+        case keycode_Func1:
+            return CMD_F1;
+        case keycode_Func2:
+            return CMD_F2;
+        case keycode_Func3:
+            return CMD_F3;
+        case keycode_Func4:
+            return CMD_F4;
+        case keycode_Func5:
+            return CMD_F5;
+        case keycode_Func6:
+            return CMD_F6;
+        case keycode_Func7:
+            return CMD_F7;
+        case keycode_Func8:
+            return CMD_F8;
+        case keycode_Func9:
+            return CMD_F9;
+        case keycode_Func10:
+            return CMD_F10;
+        default:
+            return 0;
+    }
+}
+
+static int bufchar = 0;
+static int waitchar = 0;
+static int timechar = 0;
+
+static int getglkchar(void)
+{
+    event_t event;
+
+    timechar = 0;
+
+    g_vm->glk_request_char_event(mainwin);
+
+    do
+    {
+        g_vm->glk_select(&event);
+        if (event.type == evtype_Arrange)
+            redraw_windows();
+        else if (event.type == evtype_Timer)
+            timechar = 1;
+    }
+    while (event.type != evtype_CharInput && event.type != evtype_Timer);
+
+    g_vm->glk_cancel_char_event(mainwin);
+
+    return timechar ? 0 : event.val1;
+}
+
+int os_getc(void)
+{
+    unsigned int c;
+
+    if (bufchar)
+    {
+        c = bufchar;
+        bufchar = 0;
+        return c;
+    }
+
+    c = waitchar ? waitchar : getglkchar();
+    waitchar = 0;
+
+	if (c == keycode_Return)
+		c = '\n';
+	else if (c == keycode_Tab)
+		c = '\t';
+	else if (c == keycode_Escape)
+		c = 27;
+
+    if (c < 256)
+        return c;
+
+    bufchar = glktotads(c);
+
+    return 0;
+}
+
+/*
+ *   Read a character from the keyboard, following the same protocol as
+ *   os_getc() for CMD_xxx codes (i.e., when an extended keystroke is
+ *   encountered, os_getc_raw() returns zero, then returns the CMD_xxx code
+ *   on the subsequent call).
+ *   
+ *   This function differs from os_getc() in that this function returns the
+ *   low-level, untranslated key code whenever possible.  This means that,
+ *   when a functional interpretation of a key and the raw key-cap
+ *   interpretation both exist as CMD_xxx codes, this function returns the
+ *   key-cap interpretation.  For the Unix Ctrl-E example in the comments
+ *   describing os_getc() above, this function should return 5 (the ASCII
+ *   code for Ctrl-E), because the CMD_CTRL interpretation is the low-level
+ *   key code.
+ *   
+ *   This function should return all control keys using their ASCII control
+ *   codes, whenever possible.  Similarly, this function should return ASCII
+ *   27 for the Escape key, if possible.  
+ *   
+ *   For keys for which there is no portable ASCII representation, this
+ *   should return the CMD_xxx sequence.  So, this function acts exactly the
+ *   same as os_getc() for arrow keys, function keys, and other special keys
+ *   that have no ASCII representation.  This function returns a
+ *   non-translated version ONLY when an ASCII representation exists - in
+ *   practice, this means that this function and os_getc() vary only for
+ *   CTRL keys and Escape.
+ */
+int os_getc_raw(void)
+{
+    return os_getc();
+}
+
+/* wait for a character to become available from the keyboard */
+void os_waitc(void)
+{
+    waitchar = getglkchar();
+}
+
+/*
+ *   Get an input event.  The event types are shown above.  If use_timeout
+ *   is false, this routine should simply wait until one of the events it
+ *   recognizes occurs, then return the appropriate information on the
+ *   event.  If use_timeout is true, this routine should return
+ *   OS_EVT_TIMEOUT after the given number of milliseconds elapses if no
+ *   event occurs first.
+ *   
+ *   This function is not obligated to obey the timeout.  If a timeout is
+ *   specified and it is not possible to obey the timeout, the function
+ *   should simply return OS_EVT_NOTIMEOUT.  The trivial implementation
+ *   thus checks for a timeout, returns an error if specified, and
+ *   otherwise simply waits for the user to press a key.  
+ */
+int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout,
+                 os_event_info_t *info)
+{
+#ifdef GLK_TIMERS
+    /* start timer */
+    int timer = use_timeout ? timeout_in_milliseconds : 0;
+    if (timer)
+        g_vm->glk_request_timer_events(timer);
+#else
+    /* we can't handle timeouts */
+    if (use_timeout)
+        return OS_EVT_NOTIMEOUT;
+#endif
+
+    /* get a key */
+    info->key[0] = os_getc_raw();
+    if (info->key[0] == 0 && timechar == 0)
+        info->key[1] = os_getc_raw();
+
+#ifdef GLK_TIMERS
+    /* stop timer */
+    if (timer)
+        g_vm->glk_request_timer_events(0);
+#endif
+
+    /* return the event */
+    return timechar ? OS_EVT_TIMEOUT : OS_EVT_KEY;
+}
+
+osfildef *os_exeseek(const char *argv0, const char *typ) {
+	return nullptr;
+}
+
+int os_get_str_rsc(int id, char *buf, size_t buflen) {
+	strcpy(buf, "");
+	return 0;
+}
+
+void os_dbg_printf(const char *fmt, ...) {
+	// No implementation, since ScummGlk doesn't yet implement a debugger
+}
+
+void os_dbg_vprintf(const char *fmt, va_list args) {
+	// No implementation, since ScummGlk doesn't yet implement a debugger
+}
+
+int os_vasprintf(char **bufptr, const char *fmt, va_list ap) {
+	Common::String s = Common::String::vformat(fmt, ap);
+
+	*bufptr = (char *)malloc(s.size() + 1);
+	strcpy(*bufptr, s.c_str());
+	return s.size();
+}
+
+int os_paramfile(char *buf) {
+	return false;
+}
+
+void os_rand(long *val) {
+	*val = g_vm->getRandomNumber(0x7fffffff);
+}
+
+long os_get_sys_clock_ms() {
+	return g_system->getMillis();
+}
+
+#ifndef os_tzset
+void os_tzset() {}
+#endif
+
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
index 75354ea..237502a 100644
--- a/engines/glk/tads/os_glk.h
+++ b/engines/glk/tads/os_glk.h
@@ -28,6 +28,9 @@
 #ifndef GLK_TADS_OS_GLK
 #define GLK_TADS_OS_GLK
 
+#include "common/scummsys.h"
+#include "glk/tads/os_frob_tads.h"
+
 namespace Glk {
 namespace TADS {
 
@@ -66,6 +69,3747 @@ namespace TADS {
 #define oswp4(p, l) WRITE_LE_UINT32(p, l)
 #define oswp4s(p, l) WRITE_LE_INT32(p, l)
 
+/* ------------------------------------------------------------------------ */
+
+typedef int32 int32_t;
+typedef uint32 uint32_t;
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   <time.h> definitions.
+ *   
+ *   os_time() should act like Unix time(), returning the number of seconds
+ *   elapsed since January 1, 1970 at midnight UTC.
+ *   
+ *   The original Unix <time.h> package defined time_t as a 32-bit signed
+ *   int, and many subsequent C compilers on other platforms followed suit.
+ *   A signed 32-bit time_t has the well-known year-2038 problem; some later
+ *   C compilers tried to improve matters by using an unsigned 32-bit time_t
+ *   instead, but for many purposes this is even worse since it can't
+ *   represent any date before 1/1/1970.  *Most* modern compilers solve the
+ *   problem once and for all (for 300 billion years in either direction of
+ *   1/1/1970, anyway - enough to represent literally all of eternity in most
+ *   current cosmological models) by defining time_t as a signed 64-bit int.
+ *   But some compilers stubbornly stick to the old 32-bit time_t even in
+ *   newer versions, for the sake of compatibility with older code that might
+ *   be lax about mixing time_t's with ordinary int's.  E.g., MSVC2003 does
+ *   this.  Fortunately, some of these compilers (such as MSVC2003 again)
+ *   also define a parallel, transitional set of 64-bit time functions that
+ *   you can use by replacing all references to the standard time_t and
+ *   related names with the corresponding 64-bit names.
+ *   
+ *   We'd really like to use a 64-bit time_t wherever we can - the TADS
+ *   release cycle can be a bit slow, and we don't want 2038 to sneak up on
+ *   us and catch us unawares.  So for those compilers that offer a choice of
+ *   32 or 64 bits, we'd like to select the 64 bit version.  To facilitate
+ *   this, we define covers here for the time.h types and functions that we
+ *   use.  On platforms where the regular time_t is already 64 bits, or where
+ *   there's no 64-bit option at all, you can simply do nothing - the
+ *   defaults defined here use the standard time_t typedef and functions, so
+ *   that's what you'll get if you don't define these in the OS-specific
+ *   headers for your platform.  For compilers that provide both a 32-bit
+ *   time_t and a 64-bit other_time_t, the OS headers should #define these
+ *   macros in terms of those compiler-specific 64-bit names.
+ */
+#ifndef os_time_t
+# define os_time_t        int64
+# define os_gmtime(t)     gmtime(t)
+# define os_localtime(t)  localtime(t)
+# define os_time(t)       time(t)
+#endif
+
+/*
+ *   Initialize the time zone.  This routine is meant to take care of any
+ *   work that needs to be done prior to calling localtime() and other
+ *   time-zone-dependent routines in the run-time library.  For DOS and
+ *   Windows, we need to call the run-time library routine tzset() to set up
+ *   the time zone from the environment; most systems shouldn't need to do
+ *   anything in this routine.  It's sufficient to call this once during the
+ *   process lifetime, since it's meant to perform static initialization that
+ *   lasts as long as the process is running.
+ */
+#ifndef os_tzset
+void os_tzset(void);
+#endif
+
+/*
+ *   Higher-precision time.  This retrieves the same time information as
+ *   os_time() (i.e., the elapsed time since the standard Unix Epoch, January
+ *   1, 1970 at midnight UTC), but retrieves it with the highest precision
+ *   available on the local system, up to nanosecond precision.  If less
+ *   precision is available, that's fine; just return the time to the best
+ *   precision available, but expressed in terms of the number of
+ *   nanoseconds.  For example, if you can retrieve milliseconds, you can
+ *   convert that to nanoseconds by multiplying by 1,000,000.
+ *   
+ *   On return, fills in '*seconds' with the number of whole seconds since
+ *   the Epoch, and fills in '*nanoseconds' with the fractional portion,
+ *   expressed in nanosceconds.  Note that '*nanoseconds' is merely the
+ *   fractional portion of the time, so 0 <= *nanoseconds < 1000000000.
+ */
+void os_time_ns(os_time_t *seconds, long *nanoseconds);
+
+/*
+ *   Get the local time zone name, as a location name in the IANA zoneinfo
+ *   database.  For example, locations using US Pacific Time should return
+ *   "America/Los_Angeles".
+ *   
+ *   Returns true if successful, false if not.  If the local operating system
+ *   doesn't have a way to obtain this information, or if it's not available
+ *   in the local machine's configuration, this returns false.
+ *   
+ *   The zoneinfo database is also known as the Olson or TZ (timezone)
+ *   database; it's widely used on Unix systems as the definitive source of
+ *   local time zone settings.  See http://www.iana.org/time-zones for more
+ *   information.
+ *   
+ *   On many Unix systems, the TZ environment variable contains the zoneinfo
+ *   zone name when its first character is ':'.  Windows uses a proprietary
+ *   list of time zone names that can be mapped to zoneinfo names via a
+ *   hand-coded list (such a list is maintained in the Unicode CLDR; our
+ *   Windows implementation uses the CLDR list to generate the mapping).
+ *   MacOS X uses zoneinfo keys directly; /etc/localtime is a link to the
+ *   zoneinfo file for the local zone as set via the system preferences.
+ *   
+ *   os_tzset() must be invoked at some point before this routine is called.
+ */
+int os_get_zoneinfo_key(char *buf, size_t buflen);
+
+/*
+ *   Get a description of the local time zone.  Fills in '*info' with the
+ *   available information.  Returns true on success, false on failure.
+ *   
+ *   See osstzprs.h/.c for a portable implementation of a parser for
+ *   POSIX-style TZ strings.  That can serve as a full implementation of this
+ *   function for systems that use the POSIX TZ environment variable syntax
+ *   to specify the timezone.  (That routine simply parses a string from any
+ *   source, so it can be used to parse the TZ syntax even on systems where
+ *   the string comes from somewhere other than the TZ environment variable.)
+ *   
+ *   os_tzset() must be invoked at some point before this routine is called.
+ *   
+ *   The following two structures are used for the return information:
+ *   
+ *   os_tzrule_t - Timezone Rule structure.  This describes a rule for an
+ *   annual transition between daylight savings time and standard time in a
+ *   time zone.  Most timezones that have recurring standard/daylight changes
+ *   require two of these rules, one for switching to daylight time in the
+ *   spring and one for switching to standard time in the fall.
+ *   
+ *   os_tzinfo_t - Timezone Information structure.  This describes a
+ *   timezone's clock settings, name(s), and rules for recurring annual
+ *   changes between standard time and daylight time, if applicable.
+ */
+struct os_tzrule_t {
+    /* 
+     *   Day of year, 1-365, NEVER counting Feb 29; set to 0 if not used.
+     *   Corresponds to the "J" format in Unix TZ strings.  (Called "Julian
+     *   day" in the POSIX docs, thus the "J", even though it's a bit of a
+     *   misnomer.)(Because of the invariance of the mapping from J-number to
+     *   date, this is just an obtuse way of specifying a month/day date.
+     *   But even so, we'll let the OS layer relay this back to us in
+     *   J-number format and count on the portable caller to work out the
+     *   date, rather than foisting that work on each platform
+     *   implementation.)
+     */
+    int jday;
+
+    /*
+     *   Day of year, 1-366, counting Feb 29 on leap years; set to 0 if not
+     *   used; ignored if 'jday' is nonzero.  This corresponds to the Julian
+     *   day sans "J" in TZ strings (almost - that TZ format uses 0-365 as
+     *   its range, so bump it up by one when parsing a TZ string).  This
+     *   format is even more obtuse than the J-day format, in that it doesn't
+     *   even have an invariant month/day mapping (not after day 59, anyway -
+     *   day 60 is either February 29 or March 1, depending on the leapness
+     *   of the year, and every day after that is similarly conditional).  As
+     *   far as I can tell, no one uses this option, so I'm not sure why it
+     *   exists.  The zoneinfo source format doesn't have a way to represent
+     *   it, which says to me that no one has ever used it in a statutory DST
+     *   start/end date definition in the whole history of time zones around
+     *   the world, since the whole history of time zones around the world is
+     *   exactly what the zoneinfo database captures in exhaustive and
+     *   painstaking detail.  If anyone had ever used it in defining a time
+     *   zone, zoneinfo would have an option for it.  My guess is that it's a
+     *   fossilized bug from some early C RTL that's been retained out of an
+     *   abundance of caution vis-a-vis compatibility, and was entirely
+     *   replaced in practice by the J-number format as soon as someone
+     *   noticed the fiddly leap year behavior.  But for the sake of
+     *   completeness...
+     */
+    int yday;
+    
+    /* 
+     *   The month (1-12), week of the month, and day of the week (1-7 for
+     *   Sunday to Saturday).  Week 1 is the first week in which 'day'
+     *   occurs, week 2 is the second, etc.; week 5 is the last occurrence of
+     *   'day' in the month.  These fields are used for "second Sunday in
+     *   March" types of rules.  Set these to zero if they're not used;
+     *   they're ignored in any case if 'jday' or 'yday' are non-zero.
+     */
+    int month;
+    int week;
+    int day;
+
+    /* time of day, in seconds after midnight (e.g., 2AM is 120 == 2*60*60) */
+    int time;
+};
+struct os_tzinfo_t {
+    /*
+     *   The local offset from GMT, in seconds, for standard time and
+     *   daylight time in this zone.  These values are positive for zones
+     *   east of GMT and negative for zones west: New York standard time
+     *   (EST) is 5 hours west of GMT, so its offset is -5*60*60.
+     *   
+     *   Set both of these fields (if possible) regardless of whether
+     *   standard or daylight time is currently in effect in the zone.  The
+     *   caller will select which offset to use based on the start/end rules,
+     *   or based on the 'is_dst' flag if no rules are available.
+     *   
+     *   If it's only possible to determine the current wall clock offset, be
+     *   it standard or daylight time, and it's not possible to determine the
+     *   time difference between the two, simply set both of these to the
+     *   current offset.  This information isn't available from the standard
+     *   C library, and many OS APIs also lack it.  
+     */
+    int32_t std_ofs;
+    int32_t dst_ofs;
+
+    /*
+     *   The abbreviations for the local zone's standard time and daylight
+     *   time, respectively, when displaying date/time values.  E.g., "EST"
+     *   and "EDT" for US Eastern Time.  If the zone doesn't observe daylight
+     *   time (it's on standard time year round), set dst_abbr to an empty
+     *   string.
+     *   
+     *   As with std_ofs and dst_ofs, you can set both of these to the same
+     *   string if it's only possible to determine the one that's currently
+     *   in effect.
+     */
+    char std_abbr[16];
+    char dst_abbr[16];
+
+    /*
+     *   The ongoing rules for switching between daylight and standard time
+     *   in this zone, if available.  'dst_start' is the date when daylight
+     *   savings starts, 'dst_end' is the date when standard time resumes.
+     *   Set all fields to 0 if the start/stop dates aren't available, or the
+     *   zone is on standard time year round.
+     */
+    struct os_tzrule_t dst_start;
+    struct os_tzrule_t dst_end;
+
+    /* 
+     *   True -> the zone is CURRENTLY on daylight savings time; false means
+     *   it's currently on standard time.
+     *   
+     *   This is only used if the start/end rules aren't specified.  In the
+     *   absence of start/end rules, there's no way to know when the current
+     *   standard/daylight phase ends, so we'll have to assume that the
+     *   current mode is in effect permanently.  In this case, the caller
+     *   will use only be able to use the offset and abbreviation for the
+     *   current mode and will have to ignore the other one.
+     */
+    int is_dst;
+};
+int os_get_timezone_info(struct os_tzinfo_t *info);
+
+
+/*
+ *   Get the current system high-precision timer.  This function returns a
+ *   value giving the wall-clock ("real") time in milliseconds, relative to
+ *   any arbitrary zero point.  It doesn't matter what this value is relative
+ *   to -- the only important thing is that the values returned by two
+ *   different calls should differ by the number of actual milliseconds that
+ *   have elapsed between the two calls.  This might be the number of
+ *   milliseconds since the computer was booted, since the current user
+ *   logged in, since midnight of the previous night, since the program
+ *   started running, since 1-1-1970, etc - it doesn't matter what the epoch
+ *   is, so the implementation can use whatever's convenient on the local
+ *   system.
+ *   
+ *   True millisecond precision isn't required.  Each implementation should
+ *   simply use the best precision available on the system.  If your system
+ *   doesn't have any kind of high-precision clock, you can simply use the
+ *   time() function and multiply the result by 1000 (but see the note below
+ *   about exceeding 32-bit precision).
+ *   
+ *   However, it *is* required that the return value be in *units* of
+ *   milliseconds, even if your system clock doesn't have that much
+ *   precision; so on a system that uses its own internal clock units, this
+ *   routine must multiply the clock units by the appropriate factor to yield
+ *   milliseconds for the return value.
+ *   
+ *   It is also required that the values returned by this function be
+ *   monotonically increasing.  In other words, each subsequent call must
+ *   return a value that is equal to or greater than the value returned from
+ *   the last call.  On some systems, you must be careful of two special
+ *   situations.
+ *   
+ *   First, the system clock may "roll over" to zero at some point; for
+ *   example, on some systems, the internal clock is reset to zero at
+ *   midnight every night.  If this happens, you should make sure that you
+ *   apply a bias after a roll-over to make sure that the value returned from
+ *   this return continues to increase despite the reset of the system clock.
+ *   
+ *   Second, a 32-bit signed number can only hold about twenty-three days
+ *   worth of milliseconds.  While it seems unlikely that a TADS game would
+ *   run for 23 days without a break, it's certainly reasonable to expect
+ *   that the computer itself may run this long without being rebooted.  So,
+ *   if your system uses some large type (a 64-bit number, for example) for
+ *   its high-precision timer, you may want to store a zero point the very
+ *   first time this function is called, and then always subtract this zero
+ *   point from the large value returned by the system clock.  If you're
+ *   using time(0)*1000, you should use this technique, since the result of
+ *   time(0)*1000 will almost certainly not fit in 32 bits in most cases.  
+ */
+long os_get_sys_clock_ms();
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Hardware Configuration.  Define the following functions appropriately
+ *   for your hardware.  For efficiency, these functions should be defined
+ *   as macros if possible.
+ *   
+ *   Note that these hardware definitions are independent of the OS, at
+ *   least to the extent that your OS can run on multiple types of
+ *   hardware.  So, rather than combining these definitions into your
+ *   osxxx.h header file, we recommend that you put these definitions in a
+ *   separate h_yyy.h header file, which can be configured into os.h with
+ *   an appropriate "_M_yyy" preprocessor symbol.  Refer to os.h for
+ *   details of configuring the hardware include file.  
+ */
+
+/* 
+ *   Round a size up to worst-case alignment boundary.  For example, on a
+ *   platform where the largest type must be aligned on a 4-byte boundary,
+ *   this should round the value up to the next higher mutliple of 4 and
+ *   return the result.  
+ */
+/* size_t osrndsz(size_t siz); */
+
+/* 
+ *   Round a pointer up to worst-case alignment boundary. 
+ */
+/* void *osrndpt(void *ptr); */
+
+/* 
+ *   Read an unaligned portable unsigned 2-byte value, returning an int
+ *   value.  The portable representation has the least significant byte
+ *   first, so the value 0x1234 is represented as the byte 0x34, followed
+ *   by the byte 0x12.
+ *   
+ *   The source value must be treated as unsigned, but the result is
+ *   signed.  This is significant on 32- and 64-bit platforms, because it
+ *   means that the source value should never be sign-extended to 32-bits.
+ *   For example, if the source value is 0xffff, the result is 65535, not
+ *   -1.  
+ */
+/* int osrp2(unsigned char *p); */
+
+/* 
+ *   Read an unaligned portable signed 2-byte value, returning int.  This
+ *   differs from osrp2() in that this function treats the source value as
+ *   signed, and returns a signed result; hence, on 32- and 64-bit
+ *   platforms, the result must be sign-extended to the int size.  For
+ *   example, if the source value is 0xffff, the result is -1.  
+ */
+/* int osrp2s(unsigned char *p); */
+
+/* 
+ *   Write unsigned int to unaligned portable 2-byte value.  The portable
+ *   representation stores the low-order byte first in memory, so
+ *   oswp2(0x1234) should result in storing a byte value 0x34 in the first
+ *   byte, and 0x12 in the second byte.  
+ */
+/* void oswp2(unsigned char *p, unsigned int i); */
+
+/*
+ *   Write signed int to unaligned portable 2-byte value.  Negative values
+ *   must be stored in two's complement notation.  E.g., -1 is stored as
+ *   FF.FF, -32768 is stored as 00.80 (little-endian).  
+ *   
+ *   Virtually all modern hardware uses two's complement notation as the
+ *   native representation, which makes this routine a trivial synonym of
+ *   osrp2() (i.e., #define oswp2s(p,i) oswp2(p,i)).  We distinguish the
+ *   signed version on the extremely off chance that TADS is ever ported to
+ *   wacky hardware with a different representation for negative integers
+ *   (one's complement, sign bit, etc).  
+ */
+/* void oswp2s(unsigned char *p, int i); */
+
+/* 
+ *   Read an unaligned unsigned portable 4-byte value, returning long.  The
+ *   underlying value should be considered signed, and the result is signed.
+ *   The portable representation stores the bytes starting with the least
+ *   significant: the value 0x12345678 is stored with 0x78 in the first byte,
+ *   0x56 in the second byte, 0x34 in the third byte, and 0x12 in the fourth
+ *   byte.
+ */
+/* unsigned long osrp4(unsigned char *p); */
+
+/*
+ *   Read an unaligned signed portable 4-byte value, returning long. 
+ */
+/* long osrp4s(unsigned char *p); */
+
+/* 
+ *   Write an unsigned long to an unaligned portable 4-byte value.  The
+ *   portable representation stores the low-order byte first in memory, so
+ *   0x12345678 is written to memory as 0x78, 0x56, 0x34, 0x12.  
+ */
+/* void oswp4(unsigned char *p, unsigned long l); */
+
+/*
+ *   Write a signed long, using little-endian byte order and two's complement
+ *   notation for negative numbers.  This is a trivial synonym for oswp4()
+ *   for all platforms with native two's complement arithmetic (which is
+ *   virtually all modern platforms).  See oswp2s() for more discussion.
+ */
+/* void oswp4s(unsigned char *p, long l); */
+
+/*
+ *   For convenience and readability, the 1-byte integer (signed and
+ *   unsigned) equivalents of the above.
+ */
+#define osrp1(p) (*(unsigned char *)(p))
+#define osrp1s(p) (*(signed char *)(p))
+#define oswp1(p, b) (*(unsigned char *)(p) = (b))
+#define oswp1s(p, b) (*(signed char *)(p) = (b))
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   varargs va_copy() extension.
+ *   
+ *   On some compilers, va_list is a reference type.  This means that if a
+ *   va_list value is passed to a function that uses va_arg() to step through
+ *   the referenced arguments, the caller's copy of the va_list might be
+ *   updated on return.  This is problematic in cases where the caller needs
+ *   to use the va_list again in another function call, since the va_list is
+ *   no longer pointing to the first argument for the second call.  C99 has a
+ *   solution in the form of the va_copy() macro.  Unfortunately, this isn't
+ *   typically available in pre-C99 compilers, and isn't standard in *any*
+ *   C++ version.  We thus virtualize it here in a macro.
+ *   
+ *   os_va_copy() has identical semantics to C99 va_copy().  A matching call
+ *   to os_va_copy_end() must be made for each call to os_va_copy() before
+ *   the calling function returns; this has identical semantics to C99
+ *   va_end().
+ *   
+ *   Because our semantics are identical to the C99 version, we provide a
+ *   default definition here for compilers that define va_copy().  Platform
+ *   headers must provide suitable definitions only if their compilers don't
+ *   have va_copy().  We also provide a definition for GCC compilers that
+ *   define the private __va_copy macro, which also has the same semantics.
+ */
+#ifdef va_copy
+# define os_va_copy(dst, src) va_copy(dst, src)
+# define os_va_copy_end(dst)  va_end(dst)
+#else
+# if defined(__GNUC__) && defined(__va_copy)
+#  define os_va_copy(dst, src) __va_copy(dst, src)
+#  define os_va_copy_end(dst)  va_end(dst)
+# endif
+#endif
+
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Platform Identifiers.  You must define the following macros in your
+ *   osxxx.h header file:
+ *   
+ *   OS_SYSTEM_NAME - a string giving the system identifier.  This string
+ *   must contain only characters that are valid in a TADS identifier:
+ *   letters, numbers, and underscores; and must start with a letter or
+ *   underscore.  For example, on MS-DOS, this string is "MSDOS".
+ *   
+ *   OS_SYSTEM_LDESC - a string giving the system descriptive name.  This
+ *   is used in messages displayed to the user.  For example, on MS-DOS,
+ *   this string is "MS-DOS".  
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Message Linking Configuration.  You should #define ERR_LINK_MESSAGES
+ *   in your osxxx.h header file if you want error messages linked into
+ *   the application.  Leave this symbol undefined if you want an external
+ *   message file. 
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Program Exit Codes.  These values are used for the argument to exit()
+ *   to conform to local conventions.  Define the following values in your
+ *   OS-specific header:
+ *   
+ *   OSEXSUCC - successful completion.  Usually defined to 0.
+ *.  OSEXFAIL - failure.  Usually defined to 1.  
+ */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Basic memory management interface.  These functions are merely
+ *   documented here, but no prototypes are defined, because most
+ *   platforms #define macros for these functions and types, mapping them
+ *   to malloc or other system interfaces.  
+ */
+
+/*
+ *   Theoretical maximum osmalloc() size.  This may be less than the
+ *   capacity of the argument to osmalloc() on some systems.  For example,
+ *   on segmented architectures (such as 16-bit x86), memory is divided into
+ *   segments, so a single memory allocation can allocate only a subset of
+ *   the total addressable memory in the system.  This value thus specifies
+ *   the maximum amount of memory that can be allocated in one chunk.
+ *   
+ *   Note that this is an architectural maximum for the hardware and
+ *   operating system.  It doesn't have anything to do with the total amount
+ *   of memory actually available at run-time.
+ *   
+ *   #define OSMALMAX to a constant long value with theoretical maximum
+ *   osmalloc() argument value.  For a platform with a flat (unsegmented)
+ *   32-bit memory space, this is usually 0xffffffff; for 16-bit platforms,
+ *   this is usually 0xffff.  
+ */
+/* #define OSMALMAX 0xffffffff */
+
+/*   
+ *   Allocate a block of memory of the given size in bytes.  The actual
+ *   allocation may be larger, but may be no smaller.  The block returned
+ *   should be worst-case aligned (i.e., suitably aligned for any type).
+ *   Return null if the given amount of memory is not available.  
+ */
+/* void *osmalloc(size_t siz); */
+
+/*
+ *   Free memory previously allocated with osmalloc().  
+ */
+/* void osfree(void *block); */
+
+/* 
+ *   Reallocate memory previously allocated with osmalloc() or
+ *   osrealloc(), changing the block's size to the given number of bytes.
+ *   If necessary, a new block at a different address can be allocated, in
+ *   which case the data from the original block is copied (the lesser of
+ *   the old block size and the new size is copied) to the new block, and
+ *   the original block is freed.  If the new size is less than the old
+ *   size, this need not do anything at all, since the returned block can
+ *   be larger than the new requested size.  If the block cannot be
+ *   enlarged to the requested size, return null.  
+ */
+/* void *osrealloc(void *block, size_t siz); */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Basic file I/O interface.  These functions are merely documented here,
+ *   but no prototypes are defined, because most platforms #define macros for
+ *   these functions and types, mapping them to stdio or other system I/O
+ *   interfaces.  
+ *   
+ *   When writing a file, writes might or might not be buffered in
+ *   application memory; this is up to the OS implementation, which can
+ *   perform buffering according to local conventions and what's most
+ *   efficient.  However, it shouldn't make any difference to the caller
+ *   whether writes are buffered or not - the OS implementation must take
+ *   care that any buffering is invisible to the app.  (Porters: note that
+ *   the basic C stdio package has the proper behavior here, so you'll get
+ *   the correct semantics if you use a simple stdio implementation.)
+ *   
+ *   Write buffering might be visible to *other* apps, though.  In
+ *   particular, another process might not see data written to a file (with
+ *   osfwb(), os_fprint(), etc) immediately, since the write functions might
+ *   hold the written bytes in an internal memory buffer rather than sending
+ *   them to the OS.  Any internal buffers are guaranteed to be flushed to
+ *   the OS upon calling osfcls() or osfflush().  Note that it's never
+ *   *necessary* to call osfflush(), because buffered data will always be
+ *   flushed on closing the file with osfcls().  However, if you want other
+ *   apps to be able to see updates immediately, you can use osfflush() to
+ *   ensure that buffers are flushed to a file before you close it.
+ *   
+ *   You can also use osfflush() to check for buffered write errors.  When
+ *   you use osfwb() or other write functions to write data, they will return
+ *   a success indication even if the data was only copied into a buffer.
+ *   This means that a write that appeared to succeed might actually fail
+ *   later, when the buffer is flushed.  The only way to know for sure is to
+ *   explicitly flush buffers using osfflush(), and check the result code.
+ *   If the original write function and a subsequent osfflush() *both* return
+ *   success indications, then the write has definitely succeeded.  
+ */
+
+
+/*
+ *   Define the following values in your OS header to indicate local
+ *   file/path syntax conventions:
+ *   
+ *   OSFNMAX - integer indicating maximum length of a filename
+ *   
+ *   OSPATHCHAR - character giving the normal path separator character
+ *.  OSPATHALT - string giving other path separator characters
+ *.  OSPATHURL - string giving path separator characters for URL conversions
+ *.  OSPATHSEP - directory separator for PATH-style environment variables
+ *.  OSPATHPWD - string giving the special path representing the current
+ *.              working directory; for Unix or Windows, this is "."
+ *   
+ *   OSPATHURL is a little different: this specifies the characters that
+ *   should be converted to URL-style separators when converting a path from
+ *   local notation to URL notation.  This is usually the same as the union
+ *   of OSPATHCHAR and OSPATHALT, but need not be; for example, on DOS, the
+ *   colon (':') is a path separator for most purposes, but is NOT a path
+ *   character for URL conversions.
+ */
+
+/*
+ *   Define the type osfildef as the appropriate file handle structure for
+ *   your osfxxx functions.  This type is always used as a pointer, but
+ *   the value is always obtained from an osfopxxx call, and is never
+ *   synthesized by portable code, so you can use essentially any type
+ *   here that you want.
+ *   
+ *   For platforms that use C stdio functions to implement the osfxxx
+ *   functions, osfildef can simply be defined as FILE.
+ */
+/* typedef FILE osfildef; */
+
+
+/*
+ *   File types.
+ *   
+ *   These are symbols of the form OSFTxxxx defining various content types,
+ *   somewhat aking to MIME types.  These were mainly designed for the old
+ *   Mac OS (versions up to OS 9), where the file system stored a type tag
+ *   with each file's metadata.  The type tags were used for things like
+ *   filtering file selector dialogs and setting file-to-app associations in
+ *   the desktop shell.
+ *   
+ *   Our OSFTxxx symbols are abstract file types that we define, for types
+ *   used within the TADS family of applications.  They give us a common,
+ *   cross-platform reference point for each type we use.  Each port where
+ *   file types are meaningful then maps our abstract type IDs to the
+ *   corresponding port-specific type IDs.  In practice, this has never been
+ *   used anywhere other than the old Mac OS ports; in fact, it's not even
+ *   used in the modern Mac OS (OS X and later), since Apple decided to stop
+ *   fighting the tide and start using filename suffixes for this sort of
+ *   tagging, like everyone else always has.
+ *   
+ *   For the list of file types, see osifctyp.h 
+ */
+
+
+/*
+ *   Local newline convention.
+ *   
+ *   Because of the pernicious NIH ("Not Invented Here") cultures of the
+ *   major technology vendors, basically every platform out there has its own
+ *   unique way of expressing newlines in text files.  Unix uses LF (ASCII
+ *   10); Mac uses CR (ASCII 13); DOS and Windows use CR-LF pairs.  In the
+ *   past there were heaven-only-knows how many other conventions in use, but
+ *   fortunately these three have the market pretty well locked up at this
+ *   point.  But we do still have to worry about these three.
+ *   
+ *   Our strategy on input is to be open to just about anything whenever
+ *   possible.  So, when we're reading something that we believe to be a text
+ *   file, we'll treat all of these as line endings: CR, LF, CR-LF, and
+ *   LF-CR.  It's pretty safe to do this; if we have a CR and LF occurring
+ *   adjacently, it's almost certain that they're intended to be taken
+ *   together as a single newline sequence.  Likewise, if there's a lone CR
+ *   or LF, it's rare for it to mean anything other than a newline.
+ *   
+ *   On output, though, we can't be as loose.  The problem is that other
+ *   applications on our big three platforms *don't* tend to aim for the same
+ *   flexibility we do on input: other apps usually expect exactly the local
+ *   conventions on input, and don't always work well if they don't get it.
+ *   So it's important that when we're writing a text file, we write newlines
+ *   in the local convention.  This means that we sometimes need to know what
+ *   the local convention actually is.  That's where this definition comes
+ *   in.
+ *   
+ *   Each port must define OS_NEWLINE_SEQ as an ASCII string giving the local
+ *   newline sequence to write on output.  For example, DOS defines it as
+ *   "\r\n" (CR-LF).  Always define it as a STRING (not a character
+ *   constant), even if it's only one character long.
+ *   
+ *   (Note that some compilers use wacky mappings for \r and \n.  Some older
+ *   Mac compilers, for example, defined \n as CR and \r as LF, because of
+ *   the Mac convention where newline is represented as CR in a text file.
+ *   If there's any such variability on your platform, you can always use the
+ *   octal codes to be unambiguous: \012 for LF and \015 for CR.)  
+ */
+/* #define OS_NEWLINE_SEQ  "\r\n" */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   File "stat()" information - mode, size, time stamps 
+ */
+
+/*
+ *   Get a file's mode and attribute flags.  This retrieves information on
+ *   the given file equivalent to the st_mode member of the 'struct stat'
+ *   data returned by the Unix stat() family of functions, as well as some
+ *   extra system-specific attributes.  On success, fills in *mode (if mode
+ *   is non-null) with the mode information as a bitwise combination of
+ *   OSFMODE_xxx values, fills in *attr (if attr is non-null) with a
+ *   combination of OSFATTR_xxx attribute flags, and returns true; on
+ *   failure, simply returns false.  Failure can occur if the file doesn't
+ *   exist, can't be accessed due to permissions, etc.
+ *   
+ *   Note that 'mode' and/or 'attr' can be null if the caller doesn't need
+ *   that information.  Implementations must check these parameters for null
+ *   pointers and skip returning the corresponding information if null.
+ *   
+ *   If the file in 'fname' is a symbolic link, the behavior depends upon
+ *   'follow_links'.  If 'follow_links' is true, the function should resolve
+ *   the link reference (and if that points to another link, the function
+ *   resolves that link as well, and so on) and return information on the
+ *   object the link points to.  Otherwise, the function returns information
+ *   on the link itself.  This only applies for symbolic links (not for hard
+ *   links), and only if the underlying OS and file system support this
+ *   distinction; if the OS transparently resolves links and doesn't allow
+ *   retrieving information about the link itself, 'follow_links' can be
+ *   ignored.  Likewise, hard links (on systems that support them) are
+ *   generally indistinguishable from regular files, so this function isn't
+ *   expected to do anything special with them.
+ *   
+ *   The '*mode' value returned is a bitwise combination of OSFMODE_xxx flag.
+ *   Many of the flags are mutually exclusive; for example, "file" and
+ *   "directory" should never be combined.  It's also possible for '*mode' to
+ *   be zero for a valid file; this means that the file is of some special
+ *   type on the local system that doesn't fit any of the OSFMODE_xxx types.
+ *   (If any ports do encounter such cases, we can add OSFMODE_xxx types to
+ *   accommodate new types.  The list below isn't meant to be final; it's
+ *   just what we've encountered so far on the platforms where TADS has
+ *   already been ported.)
+ *   
+ *   The OSFMODE_xxx values are left for the OS to define so that they can be
+ *   mapped directly to the OS API's equivalent constants, if desired.  This
+ *   makes the routine easy to write, since you can simply set *mode directly
+ *   to the mode information the OS returns from its stat() or equivalent.
+ *   However, note that these MUST be defined as bit flags - that is, each
+ *   value must be exactly a power of 2.  Windows and Unix-like systems
+ *   follow this practice, as do most "stat()" functions in C run-time
+ *   libraries, so this usually works automatically if you map these
+ *   constants to OS or C library values.  However, if a port defines its own
+ *   values for these, take care that they're all powers of 2.
+ *   
+ *   Obviously, a given OS might not have all of the file types listed here.
+ *   If any OSFMODE_xxx values aren't applicable on the local OS, you can
+ *   simply define them as zero since they'll never be returned.
+ *   
+ *   Notes on attribute flags:
+ *   
+ *   OSFATTR_HIDDEN means that the file is conventionally hidden by default
+ *   in user interface views or listings, but is still fully accessible to
+ *   the user.  Hidden files are also usually excluded by default from
+ *   wildcard patterns in commands ("rm *.*").  On Unix, a hidden file is one
+ *   whose name starts with "."; on Windows, it's a file with the HIDDEN bit
+ *   in its file attributes.  On systems where this concept exists, the user
+ *   can still manipulate these files as normal by naming them explicitly,
+ *   and can typically make them appear in UI views or directory listings via
+ *   a preference setting or command flag (e.g., "ls -a" on Unix).  The
+ *   "hidden" flag is explicitly NOT a security or permissions mechanism, and
+ *   it doesn't protect the file against intentional access by a user; it's
+ *   merely a convenience designed to reduce clutter by excluding files
+ *   maintained by the OS or by an application (such as preference files,
+ *   indices, caches, etc) from casual folder browsing, where a user is
+ *   typically only concerned with her own document files.  On systems where
+ *   there's no such naming convention or attribute metadata, this flag will
+ *   never appear.
+ *   
+ *   OSFATTR_SYSTEM is similar to 'hidden', but means that the file is
+ *   specially marked as an operating system file.  This is mostly a
+ *   DOS/Windows concept, where it corresponds to the SYSTEM bit in the file
+ *   attributes; this flag will probably never appear on other systems.  The
+ *   distinction between 'system' and 'hidden' is somewhat murky even on
+ *   Windows; most 'system' file are also marked as 'hidden', and in
+ *   practical terms in the user interface, 'system' files are treated the
+ *   same as 'hidden'.
+ *   
+ *   OSFATTR_READ means that the file is readable by this process.
+ *   
+ *   OSFATTR_WRITE means that the file is writable by this process.
+ */
+/* int osfmode(const char *fname, int follow_links, */
+/*             unsigned long *mode, unsigned long *attr); */
+
+/* file mode/type constants */
+/* #define OSFMODE_FILE    - regular file */
+/* #define OSFMODE_DIR     - directory */
+/* #define OSFMODE_BLK     - block-mode device */
+/* #define OSFMODE_CHAR    - character-mode device */
+/* #define OSFMODE_PIPE    - pipe/FIFO/other character-oriented IPC */
+/* #define OSFMODE_SOCKET  - network socket */
+/* #define OSFMODE_LINK    - symbolic link */
+
+/* file attribute constants */
+/* #define OSFATTR_HIDDEN  - hidden file */
+/* #define OSFATTR_SYSTEM  - system file */
+/* #define OSFATTR_READ    - the file is readable by this process */
+/* #define OSFATTR_WRITE   - the file is writable by this process */
+
+struct os_file_stat_t {
+    /* 
+     *   Size of the file, in bytes.  For platforms lacking 64-bit types, we
+     *   split this into high and low 32-bit portions.  Platforms where the
+     *   native stat() or equivalent only returns a 32-bit file size can
+     *   simply set sizehi to zero, since sizelo can hold the entire size
+     *   value.
+     */
+    uint32_t sizelo;
+    uint32_t sizehi;
+
+    /* 
+     *   Creation time, modification time, and last access time.  If the file
+     *   system doesn't keep information on one or more of these, use
+     *   (os_time_t)0 to indicate that the timestamp isn't available.  It's
+     *   fine to return any subset of these.  Per the standard C stat(),
+     *   these should be expressed as seconds after the Unix Epoch.
+     */
+    os_time_t cre_time;
+    os_time_t mod_time;
+    os_time_t acc_time;
+
+    /* file mode, using the same flags as returned from osfmode() */
+    unsigned long mode;
+
+    /* file attributes, using the same flags as returned from osfmode() */
+    unsigned long attrs;
+};
+
+
+/*
+ *   Get stat() information.  This fills in the portable os_file_stat
+ *   structure with the requested file information.  Returns true on success,
+ *   false on failure (file not found, permissions error, etc).
+ *   
+ *   'follow_links' has the same meaning as for osfmode().
+ */
+int os_file_stat(const char *fname, int follow_links, os_file_stat_t *s);
+
+/*
+ *   Manually resolve a symbolic link.  If the local OS and file system
+ *   support symbolic links, and the given filename is a symbolic link (in
+ *   which case osfmode(fname, FALSE, &m, &a) will set OSFMODE_LINK in the
+ *   mode bits), this fills in 'target' with the name of the link target
+ *   (i.e., the object that the link in 'fname' points to).  This should
+ *   return a fully qualified file system path.  Returns true on success,
+ *   false on failure.
+ *   
+ *   This should only resolve a single level of indirection.  If the link
+ *   target of 'fname' is itself a link to a second target, this should only
+ *   resolve the single reference from 'fname' to its direct direct.  Callers
+ *   that wish to resolve the final target of a chain of link references must
+ *   iterate until the returned path doesn't refer to a link.
+ */
+int os_resolve_symlink(const char *fname, char *target, size_t target_size);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Get a list of root directories.  If 'buf' is non-null, fills in 'buf'
+ *   with a list of strings giving the root directories for the local,
+ *   file-oriented devices on the system.  The strings are each null
+ *   terminated and are arranged consecutively in the buffer, with an extra
+ *   null terminator after the last string to mark the end of the list.
+ *   
+ *   The return value is the length of the buffer required to hold the
+ *   results.  If the caller's buffer is null or is too short, the routine
+ *   should return the full length required, and leaves the contents of the
+ *   buffer undefined; the caller shouldn't expect any contents to be filled
+ *   in if the return value is greater than buflen.  Both 'buflen' and the
+ *   return value include the null terminators, including the extra null
+ *   terminator at the end of the list.  If an error occurs, or the system
+ *   has no concept of a root directory, returns zero.
+ *   
+ *   Each result string should be expressed using the syntax for the root
+ *   directory on a device.  For example, on Windows, "C:\" represents the
+ *   root directory on the C: drive.
+ *   
+ *   "Local" means a device is mounted locally, as opposed to being merely
+ *   visible on the network via some remote node syntax; e.g., on Windows
+ *   this wouldn't include any UNC-style \\SERVER\SHARE names, and on VMS it
+ *   excludes any SERVER:: nodes.  It's up to each system how to treat
+ *   virtual local devices, i.e., those that look synctactically like local
+ *   devices but are actually mounted network devices, such as Windows mapped
+ *   network drives; we recommend including them if it would take extra work
+ *   to filter them out, and excluding them if it would take extra work to
+ *   include them.  "File-oriented" means that the returned devices are
+ *   accessed via file systems, not as character devices or raw block
+ *   devices; so this would exclude /dev/xxx devices on Unix and things like
+ *   CON: and LPT1: on Windows.
+ *   
+ *   Examples ("." represents a null byte):
+ *   
+ *   Windows: C:\.D:\.E:\..
+ *   
+ *   Unix example: /..
+ */
+size_t os_get_root_dirs(char *buf, size_t buflen);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Open a directory.  This begins an enumeration of a directory's contents.
+ *   'dirname' is a relative or absolute path to a directory.  On success,
+ *   returns true, and 'handle' is set to a port-defined handle value that's
+ *   used in subsequent calls to os_read_dir() and os_close_dir().  Returns
+ *   false on failure.
+ *   
+ *   If the routine succeeds, the caller must eventually call os_close_dir()
+ *   to release the resources associated with the handle.
+ */
+/* typedef <local system type> osdirhdl_t; */
+int os_open_dir(const char *dirname, /*OUT*/osdirhdl_t *handle);
+
+/*
+ *   Read the next file in a directory.  'handle' is a handle value obtained
+ *   from a call to os_open_dir().  On success, returns true and fills in
+ *   'fname' with the next filename; the handle is also internally updated so
+ *   that the next call to this function will retrieve the next file, and so
+ *   on until all files have been retrieved.  If an error occurs, or there
+ *   are no more files in the directory, returns false.
+ *   
+ *   The filename returned is the root filename only, without the path.  The
+ *   caller can build the full path by calling os_build_full_path() or
+ *   os_combine_paths() with the original directory name and the returned
+ *   filename as parameters.
+ *   
+ *   This routine lists all objects in the directory that are visible to the
+ *   corresponding native API, and is non-recursive.  The listing should thus
+ *   include subdirectory objects, but not the contents of subdirectories.
+ *   Implementations are encouraged to simply return all objects returned
+ *   from the corresponding native directory scan API; there's no need to do
+ *   any filtering, except perhaps in cases where it's difficult or
+ *   impossible to represent an object in terms of the osifc APIs (e.g., it
+ *   might be reasonable to exclude files without names).  System relative
+ *   links, such as the Unix/DOS "." and "..", specifically should be
+ *   included in the listing.  For unusual objects that don't fit into the
+ *   os_file_stat() taxonomy or that otherwise might create confusion for a
+ *   caller, err on the side of full disclosure (i.e., just return everything
+ *   unfiltered); if necessary, we can extend the os_file_stat() taxonomy or
+ *   add new osifc APIs to create a portable abstraction to handle whatever
+ *   is unusual or potentially confusing about the native object.  For
+ *   example, Unix implementations should feel free to return symbolic link
+ *   objects, including dangling links, since we have the portable
+ *   os_resolve_symlink() that lets the caller examine the meaning of the
+ *   link object.
+ */
+int os_read_dir(osdirhdl_t handle, char *fname, size_t fname_size);
+
+/*
+ *   Close a directory handle.  This releases the resources associated with a
+ *   directory search started with os_open_dir().  Every successful call to
+ *   os_open_dir() must have a matching call to os_close_dir().  As usual for
+ *   open/close protocols, the handle is invalid after calling this function,
+ *   so no more calls to os_read_dir() may be made with the handle.
+ */
+void os_close_dir(osdirhdl_t handle);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   NB - this routine is DEPRECATED as of TADS 2.5.16/3.1.1.  Callers should
+ *   use os_open_dir(), os_read_dir(), os_close_dir() instead.
+ *   
+ *   Find the first file matching a given pattern.  The returned context
+ *   pointer is a pointer to whatever system-dependent context structure is
+ *   needed to continue the search with the next file, and is opaque to the
+ *   caller.  The caller must pass the context pointer to the next-file
+ *   routine.  The caller can optionally cancel a search by calling the
+ *   close-search routine with the context pointer.  If the return value is
+ *   null, it indicates that no matching files were found.  If a file was
+ *   found, outbuf will be filled in with its name, and isdir will be set to
+ *   true if the match is a directory, false if it's a file.  If pattern is
+ *   null, all files in the given directory should be returned; otherwise,
+ *   pattern is a string containing '*' and '?' as wildcard characters, but
+ *   not containing any directory separators, and all files in the given
+ *   directory matching the pattern should be returned.
+ *   
+ *   Important: because this routine may allocate memory for the returned
+ *   context structure, the caller must either call os_find_next_file until
+ *   that routine returns null, or call os_find_close() to cancel the search,
+ *   to ensure that the os code has a chance to release the allocated memory.
+ *   
+ *   'outbuf' should be set on output to the name of the matching file,
+ *   without any path information.
+ *   
+ *   'outpathbuf' should be set on output to full path of the matching file.
+ *   If possible, 'outpathbuf' should use the same relative or absolute
+ *   notation that the search criteria used on input.  For example, if dir =
+ *   "resfiles", and the file found is "MyPic.jpg", outpathbuf should be set
+ *   to "resfiles/MyPic.jpg" (or appropriate syntax for the local platform).
+ *   Similarly, if dir = "/home/tads/resfiles", outpath buf should be
+ *   "/home/tads/resfiles/MyPic.jpg".  The result should always conform to
+ *   correct local conventions, which may require some amount of manipulation
+ *   of the filename; for example, on the Mac, if dir = "resfiles", the
+ *   result should be ":resfiles:MyPic.jpg" (note the added leading colon) to
+ *   conform to Macintosh relative path notation.
+ *   
+ *   Note that 'outpathbuf' may be null, in which case the caller is not
+ *   interested in the full path information.  
+ */
+/*   
+ *   Note the following possible ways this function may be called:
+ *   
+ *   dir = "", pattern = filename - in this case, pattern is the name of a
+ *   file or directory in the current directory.  filename *might* be a
+ *   relative path specified by the user (on a command line, for example);
+ *   for instance, on Unix, it could be something like "resfiles/jpegs".
+ *   
+ *   dir = path, pattern = filname - same as above, but this time the
+ *   filename or directory pattern is relative to the given path, rather
+ *   than to the current directory.  For example, we could have dir =
+ *   "/games/mygame" and pattern = "resfiles/jpegs".
+ *   
+ *   dir = path, pattern = 0 (NULL) - this should search for all files in
+ *   the given path.  The path might be absolute or it might be relative.
+ *   
+ *   dir = path, pattern = "*" - this should have the same result as when
+ *   pattern = 0.
+ *   
+ *   dir = path, pattern = "*.ext" - this should search for all files in
+ *   the given path whose names end with ".ext".
+ *   
+ *   dir = path, pattern = "abc*" - this should search for all files in
+ *   the given path whose names start with "abc".
+ *   
+ *   All of these combinations are possible because callers, for
+ *   portability, must generally not manipulate filenames directly;
+ *   instead, callers obtain paths and search strings from external
+ *   sources, such as from the user, and present them to this routine with
+ *   minimal manipulation.  
+ */
+void *os_find_first_file(const char *dir,
+                         char *outbuf, size_t outbufsiz, int *isdir,
+                         char *outpathbuf, size_t outpathbufsiz);
+
+/*
+ *   Implementation notes for porting os_find_first_file:
+ *   
+ *   The algorithm for this routine should go something like this:
+ *   
+ *   - If 'path' is null, create a variable real_path and initialize it
+ *   with the current directory.  Otherwise, copy path to real_path.
+ *   
+ *   - If 'pattern' contains any directory separators ("/" on Unix, for
+ *   example), change real_path so that it reflects the additional leading
+ *   subdirectories in the path in 'pattern', and remove the leading path
+ *   information from 'pattern'.  For example, on Unix, if real_path
+ *   starts out as "./subdir", and pattern is "resfiles/jpegs", change
+ *   real_path to "./subdir/resfiles", and change pattern to "jpegs".
+ *   Take care to add and remove path separators as needed to keep the
+ *   path strings well-formed.
+ *   
+ *   - Begin a search using appropriate OS API's for all files in
+ *   real_path.
+ *   
+ *   - Check each file found.  Skip any files that don't match 'pattern',
+ *   treating "*" as a wildcard that matches any string of zero or more
+ *   characters, and "?" as a wildcard that matches any single character
+ *   (or matches nothing at the end of a string).  For example:
+ *   
+ *.  "*" matches anything
+ *.  "abc?" matches "abc", "abcd", "abce", "abcf", but not "abcde"
+ *.  "abc???" matches "abc", "abcd", "abcde", "abcdef", but not "abcdefg"
+ *.  "?xyz" matches "wxyz", "axyz", but not "xyz" or "abcxyz"
+ *   
+ *   - Return the first file that matches, if any, by filling in 'outbuf'
+ *   and 'isdir' with appropriate information.  Before returning, allocate
+ *   a context structure (which is entirely for your own use, and opaque
+ *   to the caller) and fill it in with the information necessary for
+ *   os_find_next_file to get the next matching file.  If no file matches,
+ *   return null.  
+ */
+
+
+/*
+ *   Find the next matching file, continuing a search started with
+ *   os_find_first_file().  Returns null if no more files were found, in
+ *   which case the search will have been automatically closed (i.e.,
+ *   there's no need to call os_find_close() after this routine returns
+ *   null).  Returns a non-null context pointer, which is to be passed to
+ *   this function again to get the next file, if a file was found.
+ *   
+ *   'outbuf' and 'outpathbuf' are filled in with the filename (without
+ *   path) and full path (relative or absolute, as appropriate),
+ *   respectively, in the same manner as they do for os_find_first_file().
+ *   
+ *   Implementation note: if os_find_first_file() allocated memory for the
+ *   search context, this routine must free the memory if it returs null,
+ *   because this indicates that the search is finished and the caller
+ *   need not call os_find_close().  
+ */
+void *os_find_next_file(void *ctx, char *outbuf, size_t outbufsiz,
+                        int *isdir, char *outpathbuf, size_t outpathbufsiz);
+
+/*
+ *   Cancel a search.  The context pointer returned by the last call to
+ *   os_find_first_file() or os_find_next_file() is the parameter.  There
+ *   is no need to call this function if find-first or find-next returned
+ *   null, since they will have automatically closed the search.
+ *   
+ *   Implementation note: if os_find_first_file() allocated memory for the
+ *   search context, this routine should release the memory.  
+ */
+void os_find_close(void *ctx);
+
+/*
+ *   Special filename classification 
+ */
+enum os_specfile_t
+{
+    /* not a special file */
+    OS_SPECFILE_NONE,
+
+    /* 
+     *   current directory link - this is a file like the "." file on Unix
+     *   or DOS, which is a special link that simply refers to itself 
+     */
+    OS_SPECFILE_SELF,
+
+    /* 
+     *   parent directory link - this is a file like the ".." file on Unix
+     *   or DOS, which is a special link that refers to the parent
+     *   directory 
+     */
+    OS_SPECFILE_PARENT
+};
+
+/*
+ *   Determine if the given filename refers to a special file.  Returns the
+ *   appropriate enum value if so, or OS_SPECFILE_NONE if not.  The given
+ *   filename must be a root name - it must not contain a path prefix.  The
+ *   purpose here is to classify the results from os_find_first_file() and
+ *   os_find_next_file() to identify the special relative links, so callers
+ *   can avoid infinite recursion when traversing a directory tree.
+ */
+enum os_specfile_t os_is_special_file(const char *fname);
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   Convert string to all-lowercase. 
+ */
+char *os_strlwr(char *s);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Character classifications for quote characters.  os_squote() returns
+ *   true if its argument is any type of single-quote character;
+ *   os_dquote() returns true if its argument is any type of double-quote
+ *   character; and os_qmatch(a, b) returns true if a and b are matching
+ *   open- and close-quote characters.
+ *   
+ *   These functions allow systems with extended character codes with
+ *   weird quote characters (such as the Mac) to match the weird
+ *   characters, so that users can use the extended quotes in input.
+ *   
+ *   These are usually implemented as macros.  The most common
+ *   implementation simply returns true for the standard ASCII quote
+ *   characters:
+ *   
+ *   #define os_squote(c) ((c) == '\'')
+ *.  #define os_dquote(c) ((c) == '"')
+ *.  #define os_qmatch(a, b) ((a) == (b))
+ *   
+ *   These functions take int arguments to allow for the possibility of
+ *   Unicode input.  
+ */
+/* int os_squote(int c); */
+/* int os_dquote(int c); */
+/* int os_qmatch(int a, int b); */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Special file and directory locations
+ */
+
+/*
+ *   Get the full filename (including directory path) to the executable
+ *   file, given the argv[0] parameter passed into the main program.  This
+ *   fills in the buffer with a null-terminated string that can be used in
+ *   osfoprb(), for example, to open the executable file.
+ *   
+ *   Returns non-zero on success.  If it's not possible to determine the
+ *   name of the executable file, returns zero.
+ *   
+ *   Some operating systems might not provide access to the executable file
+ *   information, so non-trivial implementation of this routine is optional;
+ *   if the necessary information is not available, simply implement this to
+ *   return zero.  If the information is not available, callers should offer
+ *   gracefully degraded functionality if possible.  
+ */
+int os_get_exe_filename(char *buf, size_t buflen, const char *argv0);
+
+/*
+ *   Get a special directory path.  Returns the selected path, in a format
+ *   suitable for use with os_build_full_path().  The main program's argv[0]
+ *   parameter is provided so that the system code can choose to make the
+ *   special paths relative to the program install directory, but this is
+ *   entirely up to the system implementation, so the argv[0] parameter can
+ *   be ignored if it is not needed.
+ *   
+ *   The 'id' parameter selects which special path is requested; this is one
+ *   of the constants defined below.  If the id is not understood, there is
+ *   no way of signalling an error to the caller; this routine can fail with
+ *   an assert() in such cases, because it indicates that the OS layer code
+ *   is out of date with respect to the calling code.
+ *   
+ *   This routine can be implemented using one of the strategies below, or a
+ *   combination of these.  These are merely suggestions, though, and systems
+ *   are free to ignore these and implement this routine using whatever
+ *   scheme is the best fit to local conventions.
+ *   
+ *   - Relative to argv[0].  Some systems use this approach because it keeps
+ *   all of the TADS files together in a single install directory tree, and
+ *   doesn't require any extra configuration information to find the install
+ *   directory.  Since we base the path name on the executable that's
+ *   actually running, we don't need any environment variables or parameter
+ *   files or registry entries to know where to look for related files.
+ *   
+ *   - Environment variables or local equivalent.  On some systems, it is
+ *   conventional to set some form of global system parameter (environment
+ *   variables on Unix, for example) for this sort of install configuration
+ *   data.  In these cases, this routine can look up the appropriate
+ *   configuration variables in the system environment.
+ *   
+ *   - Hard-coded paths.  Some systems have universal conventions for the
+ *   installation configuration of compiler-like tools, so the paths to our
+ *   component files can be hard-coded based on these conventions.
+ *   
+ *   - Hard-coded default paths with environment variable overrides.  Let the
+ *   user set environment variables if they want, but use the standard system
+ *   paths as hard-coded defaults if the variables aren't set.  This is often
+ *   the best choice; users who expect the standard system conventions won't
+ *   have to fuss with any manual settings or even be aware of them, while
+ *   users who need custom settings aren't stuck with the defaults.
+ */
+void os_get_special_path(char *buf, size_t buflen,
+                         const char *argv0, int id);
+
+/* 
+ *   TADS 3 system resource path.  This path is used to load system
+ *   resources, such as character mapping files and error message files.  
+ */
+#define OS_GSP_T3_RES       1
+
+/* 
+ *   TADS 3 compiler - system headers.  This is the #include path for the
+ *   header files included with the compiler. 
+ */
+#define OS_GSP_T3_INC       2
+
+/*
+ *   TADS 3 compiler - system library source code.  This is the path to the
+ *   library source files that the compiler includes in every compilation by
+ *   default (such as _main.t). 
+ */
+#define OS_GSP_T3_LIB       3
+
+/*
+ *   TADS 3 compiler - user library path list.  This is a list of directory
+ *   paths, separated by the OSPATHSEP character, that should be searched for
+ *   user library files.  The TADS 3 compiler uses this as an additional set
+ *   of locations to search after the list of "-Fs" options and before the
+ *   OS_GSP_T3_LIB directory.
+ *   
+ *   This path list is intended for the user's use, so no default value is
+ *   needed.  The value should be user-configurable using local conventions;
+ *   on Unix, for example, this might be handled with an environment
+ *   variable.  
+ */
+#define OS_GSP_T3_USER_LIBS 4
+
+/*
+ *   TADS 3 interpreter - application data path.  This is the directory where
+ *   we should store things like option settings: data that we want to store
+ *   in a file, global to all games.  Depending on local system conventions,
+ *   this can be a global shared directory for all users, or can be a
+ *   user-specific directory. 
+ */
+#define OS_GSP_T3_APP_DATA 5
+
+/*
+ *   TADS 3 interpreter - system configuration files.  This is used for files
+ *   that affect all games, and generally all users on the system, so it
+ *   should be in a central location.  On Windows, for example, we simply
+ *   store these files in the install directory containing the intepreter
+ *   binary.  
+ */
+#define OS_GSP_T3_SYSCONFIG  6
+
+/*
+ *   System log files.  This is the directory for system-level status, debug,
+ *   and error logging files.  (Note that we're NOT talking about in-game
+ *   transcript logging per the SCRIPT command.  SCRIPT logs are usually sent
+ *   to files selected by the user via a save-file dialog, so these don't
+ *   need a special location.)
+ */
+#define OS_GSP_LOGFILE  7
+
+
+/* 
+ *   Seek to the resource file embedded in the current executable file,
+ *   given the main program's argv[0].
+ *   
+ *   On platforms where the executable file format allows additional
+ *   information to be attached to an executable, this function can be used
+ *   to find the extra information within the executable.
+ *   
+ *   The 'typ' argument gives a resource type to find.  This is an arbitrary
+ *   string that the caller uses to identify what type of object to find.
+ *   The "TGAM" type, for example, is used by convention to indicate a TADS
+ *   compiled GAM file.  
+ */
+osfildef *os_exeseek(const char *argv0, const char *typ);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Load a string resource.  Given a string ID number, load the string
+ *   into the given buffer.
+ *   
+ *   Returns zero on success, non-zero if an error occurs (for example,
+ *   the buffer is too small, or the requested resource isn't present).
+ *   
+ *   Whenever possible, implementations should use an operating system
+ *   mechanism for loading the string from a user-modifiable resource
+ *   store; this will make localization of these strings easier, since the
+ *   resource store can be modified without the need to recompile the
+ *   application.  For example, on the Macintosh, the normal system string
+ *   resource mechanism should be used to load the string from the
+ *   application's resource fork.
+ *   
+ *   When no operating system mechanism exists, the resources can be
+ *   stored as an array of strings in a static variable; this isn't ideal,
+ *   because it makes it much more difficult to localize the application.
+ *   
+ *   Resource ID's are application-defined.  For example, for TADS 2,
+ *   "res.h" defines the resource ID's.  
+ */
+int os_get_str_rsc(int id, char *buf, size_t buflen);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Get a suitable seed for a random number generator; should use the system
+ *   clock or some other source of an unpredictable and changing seed value.
+ */
+void os_rand(long *val);
+
+/*
+ *   Generate random bytes for use in seeding a PRNG (pseudo-random number
+ *   generator).  This is an extended version of os_rand() for PRNGs that use
+ *   large seed vectors containing many bytes, rather than the simple 32-bit
+ *   seed that os_rand() assumes.
+ *   
+ *   As with os_rand(), this function isn't meant to be used directly as a
+ *   random number source for ongoing use - instead, this is intended mostly
+ *   for seeding a PRNG, which will then be used as the primary source of
+ *   random numbers.  The big problem with using this routine directly as a
+ *   randomness source is that some implementations might rely heavily on
+ *   environmental randomness, such as the real-time clock or OS usage
+ *   statistics.  Such sources tend to provide reasonable entropy from one
+ *   run to the next, but not within a single session, as the underlying data
+ *   sources don't change rapidly enough.
+ *   
+ *   Ideally, this routine should generate *truly* random bytes obtained from
+ *   hardware sources.  Not all systems can provide that, though, so true
+ *   randomness isn't guaranteed.  Here are the suggested implementation
+ *   options, in descending order of desirability:
+ *   
+ *   1.  Use a hardware source of true randomness, such as a /dev/rand type
+ *   of device.  However, note that this call should return reasonably
+ *   quickly, so always use a non-blocking source.  Some Unix /dev/rand
+ *   devices, for example, can block indefinitely to allow sufficient entropy
+ *   to accumulate.
+ *   
+ *   2. Use a cryptographic random number source provided by the OS.  Some
+ *   systems provide this as an API service.  If going this route, be sure
+ *   that the OS generator is itself "seeded" with some kind of true
+ *   randomness source, as it defeats the whole purpose if you always return
+ *   a fixed pseudo-random sequence each time the program runs.
+ *   
+ *   3. Use whatever true random sources are available locally to seed a
+ *   software pseudo-random number generator, then generate bytes from your
+ *   PRNG.  Some commonly available sources of true randomness are a
+ *   high-resolution timer, the system clock, the current process ID,
+ *   logged-in user ID, environment variables, uninitialized pages of memory,
+ *   the IP address; each of these sources might give you a few bits of
+ *   entropy at best, so the best bet is to use an ensemble.  You could, for
+ *   example, concatenate a bunch of this type of information together and
+ *   calculate an MD5 or SHA1 hash to mix the bits more thoroughly.  For the
+ *   PRNG, use a cryptographic generator.  (You could use the ISAAC generator
+ *   from TADS 3, as that's a crypto PRNG, but it's probably better to use a
+ *   different generator here since TADS 3 is going to turn around and use
+ *   this function's output to seed ISAAC - seeding one ISAAC instance with
+ *   another ISAAC's output seems likely to magnify any weaknesses in the
+ *   ISAAC algorithm.)  Note that this option is basically the DIY version of
+ *   option 2.  Option 2 is better because the OS probably has access to
+ *   better sources of true randomness than an application does.  
+ */
+void os_gen_rand_bytes(unsigned char *buf, size_t len);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Display routines.
+ *   
+ *   Our display model is a simple stdio-style character stream.
+ *   
+ *   In addition, we provide an optional "status line," which is a
+ *   non-scrolling area where a line of text can be displayed.  If the status
+ *   line is supported, text should only be displayed in this area when
+ *   os_status() is used to enter status-line mode (mode 1); while in status
+ *   line mode, text is written to the status line area, otherwise (mode 0)
+ *   it's written to the normal main text area.  The status line is normally
+ *   shown in a different color to set it off from the rest of the text.
+ *   
+ *   The OS layer can provide its own formatting (word wrapping in
+ *   particular) if it wants, in which case it should also provide pagination
+ *   using os_more_prompt().  
+ */
+
+/*
+ *   OS_MAXWIDTH - the maximum width of a line of text.  Most platforms use
+ *   135 for this, but you can use more or less as appropriate.  If you use
+ *   OS-level line wrapping, then the true width of a text line is
+ *   irrelevant, and the portable code will use this merely for setting its
+ *   internal buffer sizes.
+ *   
+ *   This must be defined in the os_xxx.h header file for each platform.
+ */
+/*#define OS_MAXWIDTH 135 - example only: define for real in os_xxx.h header*/
+
+/*
+ *   Print a string on the console.  These routines come in two varieties:
+ *   
+ *   os_printz - write a NULL-TERMINATED string
+ *.  os_print - write a COUNTED-LENGTH string, which may not end with a null
+ *   
+ *   These two routines are identical except that os_printz() takes a string
+ *   which is terminated by a null byte, and os_print() instead takes an
+ *   explicit length, and a string that may not end with a null byte.
+ *   
+ *   os_printz(str) may be implemented as simply os_print(str, strlen(str)).
+ *   
+ *   The string is written in one of three ways, depending on the status mode
+ *   set by os_status():
+ *   
+ *   status mode == 0 -> write to main text window
+ *.  status mode == 1 -> write to status line
+ *.  anything else -> do not display the text at all
+ *   
+ *   Implementations are free to omit any status line support, in which case
+ *   they should simply suppress all output when the status mode is anything
+ *   other than zero.
+ *   
+ *   The following special characters must be recognized in the displayed
+ *   text:
+ *   
+ *   '\n' - newline: end the current line and move the cursor to the start of
+ *   the next line.  If the status line is supported, and the current status
+ *   mode is 1 (i.e., displaying in the status line), then two special rules
+ *   apply to newline handling: newlines preceding any other text should be
+ *   ignored, and a newline following any other text should set the status
+ *   mode to 2, so that all subsequent output is suppressed until the status
+ *   mode is changed with an explicit call by the client program to
+ *   os_status().
+ *   
+ *   '\r' - carriage return: end the current line and move the cursor back to
+ *   the beginning of the current line.  Subsequent output is expected to
+ *   overwrite the text previously on this same line.  The implementation
+ *   may, if desired, IMMEDIATELY clear the previous text when the '\r' is
+ *   written, rather than waiting for subsequent text to be displayed.
+ *   
+ *   All other characters may be assumed to be ordinary printing characters.
+ *   The routine need not check for any other special characters.
+ *   
+ */
+void os_printz(const char *str);
+void os_print(const char *str, size_t len);
+
+/*
+ *   Print to the debugger console.  These routines are for interactive
+ *   debugger builds only: they display the given text to a separate window
+ *   within the debugger UI (separate from the main game command window)
+ *   where the debugger displays status information specific to the debugging
+ *   session (such as compiler/build output, breakpoint status messages,
+ *   etc).  For example, TADS Workbench on Windows displays these messages in
+ *   its "Debug Log" window.
+ *   
+ *   These routines only need to be implemented for interactive debugger
+ *   builds, such as TADS Workbench on Windows.  These can be omitted for
+ *   regular interpreter builds.  
+ */
+void os_dbg_printf(const char *fmt, ...);
+void os_dbg_vprintf(const char *fmt, va_list args);
+
+/*
+ *   Allocating sprintf and vsprintf.  These work like the regular C library
+ *   sprintf and vsprintf funtions, but they allocate a return buffer that's
+ *   big enough to hold the result, rather than formatting into a caller's
+ *   buffer.  This protects against buffer overruns and ensures that the
+ *   result isn't truncated.
+ *   
+ *   On return, '*bufptr' is filled in with a pointer to a buffer allocated
+ *   with osmalloc().  This buffer contains the formatted string result.  The
+ *   caller is responsible for freeing the buffer by calling osfree().
+ *   *bufptr can be null on return if an error occurs.
+ *   
+ *   The return value is the number of bytes written to the allocated buffer,
+ *   not including the null terminator.  If an error occurs, the return value
+ *   is -1 and *bufptr is undefined.
+ *   
+ *   Many modern C libraries provide equivalents of these, usually called
+ *   asprintf() and vasprintf(), respectively.  
+ */
+/* int os_asprintf(char **bufptr, const char *fmt, ...); */
+int os_vasprintf(char **bufptr, const char *fmt, va_list ap);
+
+
+/* 
+ *   Set the status line mode.  There are three possible settings:
+ *   
+ *   0 -> main text mode.  In this mode, all subsequent text written with
+ *   os_print() and os_printz() is to be displayed to the main text area.
+ *   This is the normal mode that should be in effect initially.  This mode
+ *   stays in effect until an explicit call to os_status().
+ *   
+ *   1 -> statusline mode.  In this mode, text written with os_print() and
+ *   os_printz() is written to the status line, which is usually rendered as
+ *   a one-line area across the top of the terminal screen or application
+ *   window.  In statusline mode, leading newlines ('\n' characters) are to
+ *   be ignored, and any newline following any other character must change
+ *   the mode to 2, as though os_status(2) had been called.
+ *   
+ *   2 -> suppress mode.  In this mode, all text written with os_print() and
+ *   os_printz() must simply be ignored, and not displayed at all.  This mode
+ *   stays in effect until an explicit call to os_status().  
+ */
+void os_status(int stat);
+
+/* get the status line mode */
+int os_get_status();
+
+/* 
+ *   Set the score value.  This displays the given score and turn counts on
+ *   the status line.  In most cases, these values are displayed at the right
+ *   edge of the status line, in the format "score/turns", but the format is
+ *   up to the implementation to determine.  In most cases, this can simply
+ *   be implemented as follows:
+ *   
+ *.  void os_score(int score, int turncount)
+ *.  {
+ *.     char buf[40];
+ *.     sprintf(buf, "%d/%d", score, turncount);
+ *.     os_strsc(buf);
+ *.  }
+ */
+void os_score(int score, int turncount);
+
+/* display a string in the score area in the status line */
+void os_strsc(const char *p);
+
+/* clear the screen */
+void oscls(void);
+
+/* redraw the screen */
+void os_redraw(void);
+
+/* flush any buffered display output */
+void os_flush(void);
+
+/*
+ *   Update the display - process any pending drawing immediately.  This
+ *   only needs to be implemented for operating systems that use
+ *   event-driven drawing based on window invalidations; the Windows and
+ *   Macintosh GUI's both use this method for drawing window contents.
+ *   
+ *   The purpose of this routine is to refresh the display prior to a
+ *   potentially long-running computation, to avoid the appearance that the
+ *   application is frozen during the computation delay.
+ *   
+ *   Platforms that don't need to process events in the main thread in order
+ *   to draw their window contents do not need to do anything here.  In
+ *   particular, text-mode implementations generally don't need to implement
+ *   this routine.
+ *   
+ *   This routine doesn't absolutely need a non-empty implementation on any
+ *   platform, but it will provide better visual feedback if implemented for
+ *   those platforms that do use event-driven drawing.  
+ */
+void os_update_display();
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Set text attributes.  Text subsequently displayed through os_print() and
+ *   os_printz() are to be displayed with the given attributes.
+ *   
+ *   'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values.  A value
+ *   of zero indicates normal text, with no extra attributes.  
+ */
+void os_set_text_attr(int attr);
+
+/* attribute code: bold-face */
+#define OS_ATTR_BOLD     0x0001
+
+/* attribute code: italic */
+#define OS_ATTR_ITALIC   0x0002
+
+/*
+ *   Abstract attribute codes.  Each platform can choose a custom rendering
+ *   for these by #defining them before this point, in the OS-specific header
+ *   (osdos.h, osmac.h, etc).  We provide *default* definitions in case the
+ *   platform doesn't define these.
+ *   
+ *   For compatibility with past versions, we treat HILITE, EM, and BOLD as
+ *   equivalent.  Platforms that can display multiple kinds of text
+ *   attributes (boldface and italic, say) should feel free to use more
+ *   conventional HTML mappings, such as EM->italic and STRONG->bold.  
+ */
+
+/* 
+ *   "Highlighted" text, as appropriate to the local platform.  On most
+ *   text-mode platforms, the only kind of rendering variation possible is a
+ *   brighter or intensified color.  If actual bold-face is available, that
+ *   can be used instead.  This is the attribute used for text enclosed in a
+ *   TADS2 "\( \)" sequence.  
+ */
+#ifndef OS_ATTR_HILITE
+# define OS_ATTR_HILITE  OS_ATTR_BOLD
+#endif
+
+/* HTML <em> attribute - by default, map this to bold-face */
+#ifndef OS_ATTR_EM
+# define OS_ATTR_EM      OS_ATTR_BOLD
+#endif
+
+/* HTML <strong> attribute - by default, this has no effect */
+#ifndef OS_ATTR_STRONG
+# define OS_ATTR_STRONG  0
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Colors.
+ *   
+ *   There are two ways of encoding a color.  First, a specific color can be
+ *   specified as an RGB (red-green-blue) value, with discreet levels for
+ *   each component's intensity, ranging from 0 to 255.  Second, a color can
+ *   be "parameterized," which doesn't specify a color in absolute terms but
+ *   rather specified one of a number of pre-defined *types* of colors;
+ *   these pre-defined types can be chosen by the OS implementation, or, on
+ *   some systems, selected by the user via a preferences mechanism.
+ *   
+ *   The os_color_t type encodes a color in 32 bits.  The high-order 8 bits
+ *   of a color value give the parameterized color identifier, or are set to
+ *   zero to indicate an RGB color.  An RGB color is encoded in the
+ *   low-order 24 bits, via the following formula:
+ *   
+ *   (R << 16) + (G << 8) + B
+ *   
+ *   R specifies the intensity of the red component of the color, G green,
+ *   and B blue.  Each of R, G, and B must be in the range 0-255.  
+ */
+typedef unsigned long os_color_t;
+
+/* encode an R, G, B triplet into an os_color_t value */
+#define os_rgb_color(r, g, b) (((r) << 16) + ((g) << 8) + (b))
+
+/* 
+ *   Determine if a color is given as an RGB value or as a parameterized
+ *   color value.  Returns true if the color is given as a parameterized
+ *   color (one of the OS_COLOR_xxx values), false if it's given as an
+ *   absolute RGB value.  
+ */
+#define os_color_is_param(color) (((color) & 0xFF000000) != 0)
+
+/* get the red/green/blue components of an os_color_t value */
+#define os_color_get_r(color) ((int)(((color) >> 16) & 0xFF))
+#define os_color_get_g(color) ((int)(((color) >> 8) & 0xFF))
+#define os_color_get_b(color) ((int)((color) & 0xFF))
+
+/*
+ *   Parameterized color codes.  These are os_color_t values that indicate
+ *   colors by type, rather than by absolute RGB values.  
+ */
+
+/* 
+ *   "transparent" - applicable to backgrounds only, this specifies that the
+ *   current screen background color should be used 
+ */
+#define OS_COLOR_P_TRANSPARENT ((os_color_t)0x01000000)
+
+/* "normal text" color (as set via user preferences, if applicable) */
+#define OS_COLOR_P_TEXT        ((os_color_t)0x02000000)
+
+/* normal text background color (from user preferences) */
+#define OS_COLOR_P_TEXTBG      ((os_color_t)0x03000000)
+
+/* "status line" text color (as set via user preferences, if applicable) */
+#define OS_COLOR_P_STATUSLINE  ((os_color_t)0x04000000)
+
+/* status line background color (from user preferences) */
+#define OS_COLOR_P_STATUSBG    ((os_color_t)0x05000000)
+
+/* input text color (as set via user preferences, if applicable) */
+#define OS_COLOR_P_INPUT       ((os_color_t)0x06000000)
+
+/*
+ *   Set the text foreground and background colors.  This sets the text
+ *   color for subsequent os_printf() and os_vprintf() calls.
+ *   
+ *   The background color can be OS_COLOR_TRANSPARENT, in which case the
+ *   background color is "inherited" from the current screen background.
+ *   Note that if the platform is capable of keeping old text for
+ *   "scrollback," then the transparency should be a permanent attribute of
+ *   the character - in other words, it should not be mapped to the current
+ *   screen color in the scrollback buffer, because doing so would keep the
+ *   current screen color even if the screen color changes in the future. 
+ *   
+ *   Text color support is optional.  If the platform doesn't support text
+ *   colors, this can simply do nothing.  If the platform supports text
+ *   colors, but the requested color or attributes cannot be displayed, the
+ *   implementation should use the best available approximation.  
+ */
+void os_set_text_color(os_color_t fg, os_color_t bg);
+
+/*
+ *   Set the screen background color.  This sets the text color for the
+ *   background of the screen.  If possible, this should immediately redraw
+ *   the main text area with this background color.  The color is given as an
+ *   OS_COLOR_xxx value.
+ *   
+ *   If the platform is capable of redisplaying the existing text, then any
+ *   existing text that was originally displayed with 'transparent'
+ *   background color should be redisplayed with the new screen background
+ *   color.  In other words, the 'transparent' background color of previously
+ *   drawn text should be a permanent attribute of the character - the color
+ *   should not be mapped on display to the then-current background color,
+ *   because doing so would lose the transparency and thus retain the old
+ *   screen color on a screen color change.  
+ */
+void os_set_screen_color(os_color_t color);
+
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   os_plain() - Use plain ascii mode for the display.  If possible and
+ *   necessary, turn off any text formatting effects, such as cursor
+ *   positioning, highlighting, or coloring.  If this routine is called,
+ *   the terminal should be treated as a simple text stream; users might
+ *   wish to use this mode for things like text-to-speech converters.
+ *   
+ *   Purely graphical implementations that cannot offer a textual mode
+ *   (such as Mac OS or Windows) can ignore this setting.
+ *   
+ *   If this routine is to be called, it must be called BEFORE os_init().
+ *   The implementation should set a flag so that os_init() will know to
+ *   set up the terminal for plain text output.  
+ */
+#ifndef os_plain
+/* 
+ *   some platforms (e.g. Mac OS) define this to be a null macro, so don't
+ *   define a prototype in those cases 
+ */
+void os_plain(void);
+#endif
+
+/*
+ *   Set the game title.  The output layer calls this routine when a game
+ *   sets its title (via an HTML <title> tag, for example).  If it's
+ *   convenient to do so, the OS layer can use this string to set a window
+ *   caption, or whatever else makes sense on each system.  Most
+ *   character-mode implementations will provide an empty implementation,
+ *   since there's not usually any standard way to show the current
+ *   application title on a character-mode display.  
+ */
+void os_set_title(const char *title);
+
+/*
+ *   Show the system-specific MORE prompt, and wait for the user to respond.
+ *   Before returning, remove the MORE prompt from the screen.
+ *   
+ *   This routine is only used and only needs to be implemented when the OS
+ *   layer takes responsibility for pagination; this will be the case on
+ *   most systems that use proportionally-spaced (variable-pitch) fonts or
+ *   variable-sized windows, since on such platforms the OS layer must do
+ *   most of the formatting work, leaving the standard output layer unable
+ *   to guess where pagination should occur.
+ *   
+ *   If the portable output formatter handles the MORE prompt, which is the
+ *   usual case for character-mode or terminal-style implementations, this
+ *   routine is not used and you don't need to provide an implementation.
+ *   Note that HTML TADS provides an implementation of this routine, because
+ *   the HTML renderer handles line breaking and thus must handle
+ *   pagination.  
+ */
+void os_more_prompt();
+
+/*
+ *   Interpreter Class Configuration.
+ *   
+ *   If this is a TEXT-ONLY interpreter: DO NOT define USE_HTML.
+ *   
+ *   If this is a MULTIMEDIA (HTML TADS) intepreter: #define USE_HTML
+ *   
+ *   (This really should be called something like OS_USE_HTML - the USE_ name
+ *   is for historical reasons.  This purpose of this macro is to configure
+ *   the tads 2 VM-level output formatter's line breaking and MORE mode
+ *   behavior.  In HTML mode, the VM-level formatter knows that it's feeding
+ *   its output to a page layout engine, so the VM-level output is just a
+ *   text stream.  In plain-text mode, the VM formatter *is* the page layout
+ *   engine, so it needs to do all of the word wrapping and MORE prompting
+ *   itself.  The tads 3 output layer does NOT use this macro for its
+ *   equivalent configuration, but instead has different .cpp files for the
+ *   different modes, and you simply link in the one for the configuration
+ *   you want.)  
+ */
+/* #define USE_HTML */
+
+
+/*
+ *   Enter HTML mode.  This is only used when the run-time is compiled
+ *   with the USE_HTML flag defined.  This call instructs the renderer
+ *   that HTML sequences should be parsed; until this call is made, the
+ *   renderer should not interpret output as HTML.  Non-HTML
+ *   implementations do not need to define this routine, since the
+ *   run-time will not call it if USE_HTML is not defined.  
+ */
+void os_start_html(void);
+
+/* exit HTML mode */
+void os_end_html(void);
+
+/*
+ *   Global variables with the height and width (in character cells - rows
+ *   and columns) of the main text display area into which os_printf
+ *   displays.  The height and width are given in text lines and character
+ *   columns, respectively.  The portable code can use these values to
+ *   format text for display via os_printf(); for example, the caller can
+ *   use the width to determine where to put line breaks.
+ *   
+ *   These values are only needed for systems where os_printf() doesn't
+ *   perform its own word-wrap formatting.  On systems such as the Mac,
+ *   where os_printf() performs word wrapping, these sizes aren't really
+ *   important because the portable code doesn't need to perform any real
+ *   formatting.
+ *   
+ *   These variables reflect the size of the "main text area," which is the
+ *   area of the screen excluding the status line and any "banner" windows
+ *   (as created with the os_banner_xxx() interfaces).
+ *   
+ *   The OS code must initialize these variables during start-up, and must
+ *   adjust them whenever the display size is changed by user action or
+ *   other external events (for example, if we're running inside a terminal
+ *   window, and the user resizes the window, the OS code must recalculate
+ *   the layout and adjust these accordingly).  
+ */
+extern int G_os_pagelength;
+extern int G_os_linewidth;
+
+/*
+ *   Global flag that tells the output formatter whether to count lines
+ *   that it's displaying against the total on the screen so far.  If this
+ *   variable is true, lines are counted, and the screen is paused with a
+ *   [More] message when it's full.  When not in MORE mode, lines aren't
+ *   counted.  This variable should be set to false when displaying text
+ *   that doesn't count against the current page, such as status line
+ *   information.
+ *   
+ *   This flag should not be modified by OS code.  Instead, the output
+ *   formatter will set this flag according to its current state; the OS
+ *   code can use this flag to determine whether or not to display a MORE
+ *   prompt during os_printf()-type operations.  Note that this flag is
+ *   normally interesting to the OS code only when the OS code itself is
+ *   handling the MORE prompt.  
+ */
+extern int G_os_moremode;
+
+/*
+ *   Global buffer containing the name of the byte-code file (the "game
+ *   file") loaded into the VM.  This is used only where applicable, which
+ *   generally means in TADS Interpreter builds.  In other application
+ *   builds, this is simply left empty.  The application is responsible for
+ *   setting this during start-up (or wherever else the byte-code filename
+ *   becomes known or changes).  
+ */
+extern char G_os_gamename[OSFNMAX];
+
+/*
+ *   Set non-stop mode.  This tells the OS layer that it should disable any
+ *   MORE prompting it would normally do.
+ *   
+ *   This routine is needed only when the OS layer handles MORE prompting; on
+ *   character-mode platforms, where the prompting is handled in the portable
+ *   console layer, this can be a dummy implementation.  
+ */
+void os_nonstop_mode(int flag);
+
+/* 
+ *   Update progress display with current info, if appropriate.  This can
+ *   be used to provide a status display during compilation.  Most
+ *   command-line implementations will just ignore this notification; this
+ *   can be used for GUI compiler implementations to provide regular
+ *   display updates during compilation to show the progress so far.  
+ */
+/* void os_progress(const char *fname, unsigned long linenum); */
+
+/* 
+ *   Set busy cursor.  If 'flag' is true, provide a visual representation
+ *   that the system or application is busy doing work.  If 'flag' is
+ *   false, remove any visual "busy" indication and show normal status.
+ *   
+ *   We provide a prototype here if your osxxx.h header file does not
+ *   #define a macro for os_csr_busy.  On many systems, this function has
+ *   no effect at all, so the osxxx.h header file simply #define's it to
+ *   do an empty macro.  
+ */
+#ifndef os_csr_busy
+void os_csr_busy(int flag);
+#endif
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   User Input Routines
+ */
+
+/*
+ *   Ask the user for a filename, using a system-dependent dialog or other
+ *   mechanism.  Returns one of the OS_AFE_xxx status codes (see below).
+ *   
+ *   prompt_type is the type of prompt to provide -- this is one of the
+ *   OS_AFP_xxx codes (see below).  The OS implementation doesn't need to
+ *   pay any attention to this parameter, but it can be used if desired to
+ *   determine the type of dialog to present if the system provides
+ *   different types of dialogs for different types of operations.
+ *   
+ *   file_type is one of the OSFTxxx codes for system file type.  The OS
+ *   implementation is free to ignore this information, but can use it to
+ *   filter the list of files displayed if desired; this can also be used
+ *   to apply a default suffix on systems that use suffixes to indicate
+ *   file type.  If OSFTUNK is specified, it means that no filtering
+ *   should be performed, and no default suffix should be applied.  
+ */
+int os_askfile(const char *prompt, char *fname_buf, int fname_buf_len,
+               int prompt_type, os_filetype_t file_type);
+
+/* 
+ *   os_askfile status codes 
+ */
+
+/* success */
+#define OS_AFE_SUCCESS  0 
+
+/* 
+ *   Generic failure - this is largely provided for compatibility with
+ *   past versions, in which only zero and non-zero error codes were
+ *   meaningful; since TRUE is defined as 1 on most platforms, we assume
+ *   that 1 is probably the generic non-zero error code that most OS
+ *   implementations have traditionally used.  In addition, this can be
+ *   used to indicate any other error for which there is no more specific
+ *   error code.  
+ */
+#define OS_AFE_FAILURE  1
+
+/* user cancelled */
+#define OS_AFE_CANCEL   2
+
+/* 
+ *   os_askfile prompt types
+ *   
+ *   Important note: do not change these values when porting TADS.  These
+ *   values can be used by games, so they must be the same on all
+ *   platforms.  
+ */
+#define OS_AFP_OPEN    1     /* choose an existing file to open for reading */
+#define OS_AFP_SAVE    2          /* choose a filename for saving to a file */
+
+
+/* 
+ *   Read a string of input.  Fills in the buffer with a null-terminated
+ *   string containing a line of text read from the standard input.  The
+ *   returned string should NOT contain a trailing newline sequence.  On
+ *   success, returns 'buf'; on failure, including end of file, returns a
+ *   null pointer.  
+ */
+unsigned char *os_gets(unsigned char *buf, size_t bufl);
+
+/*
+ *   Read a string of input with an optional timeout.  This behaves like
+ *   os_gets(), in that it allows the user to edit a line of text (ideally
+ *   using the same editing keys that os_gets() does), showing the line of
+ *   text under construction during editing.  This routine differs from
+ *   os_gets() in that it returns if the given timeout interval expires
+ *   before the user presses Return (or the local equivalent).
+ *   
+ *   If the user presses Return before the timeout expires, we store the
+ *   command line in the given buffer, just as os_gets() would, and we return
+ *   OS_EVT_LINE.  We also update the display in the same manner that
+ *   os_gets() would, by moving the cursor to a new line and scrolling the
+ *   displayed text as needed.
+ *   
+ *   If a timeout occurs before the user presses Return, we store the command
+ *   line so far in the given buffer, statically store the cursor position,
+ *   insert mode, buffer text, and anything else relevant to the editing
+ *   state, and we return OS_EVT_TIMEOUT.
+ *   
+ *   If the implementation does not support the timeout operation, this
+ *   routine should simply return OS_EVT_NOTIMEOUT immediately when called;
+ *   the routine should not allow the user to perform any editing if the
+ *   timeout is not supported.  Callers must use the ordinary os_gets()
+ *   routine, which has no timeout capabilities, if the timeout is not
+ *   supported.
+ *   
+ *   When we return OS_EVT_TIMEOUT, the caller is responsible for doing one
+ *   of two things.
+ *   
+ *   The first possibility is that the caller performs some work that doesn't
+ *   require any display operations (in other words, the caller doesn't
+ *   invoke os_printf, os_getc, or anything else that would update the
+ *   display), and then calls os_gets_timeout() again.  In this case, we will
+ *   use the editing state that we statically stored before we returned
+ *   OS_EVT_TIMEOUT to continue editing where we left off.  This allows the
+ *   caller to perform some computation in the middle of user command editing
+ *   without interrupting the user - the extra computation is transparent to
+ *   the user, because we act as though we were still in the midst of the
+ *   original editing.
+ *   
+ *   The second possibility is that the caller wants to update the display.
+ *   In this case, the caller must call os_gets_cancel() BEFORE making any
+ *   display changes.  Then, the caller must do any post-input work of its
+ *   own, such as updating the display mode (for example, closing HTML font
+ *   tags that were opened at the start of the input).  The caller is now
+ *   free to do any display work it wants.
+ *   
+ *   If we have information stored from a previous call that was interrupted
+ *   by a timeout, and os_gets_cancel(TRUE) was never called, we will resume
+ *   editing where we left off when the cancelled call returned; this means
+ *   that we'll restore the cursor position, insertion state, and anything
+ *   else relevant.  Note that if os_gets_cancel(FALSE) was called, we must
+ *   re-display the command line under construction, but if os_gets_cancel()
+ *   was never called, we will not have to make any changes to the display at
+ *   all.
+ *   
+ *   Note that when resuming an interrupted editing session (interrupted via
+ *   os_gets_cancel()), the caller must re-display the prompt prior to
+ *   invoking this routine.
+ *   
+ *   Note that we can return OS_EVT_EOF in addition to the other codes
+ *   mentioned above.  OS_EVT_EOF indicates that an error occurred reading,
+ *   which usually indicates that the application is being terminated or that
+ *   some hardware error occurred reading the keyboard.  
+ *   
+ *   If 'use_timeout' is false, the timeout should be ignored.  Without a
+ *   timeout, the function behaves the same as os_gets(), except that it will
+ *   resume editing of a previously-interrupted command line if appropriate.
+ *   (This difference is why the timeout is optional: a caller might not need
+ *   a timeout, but might still want to resume a previous input that did time
+ *   out, in which case the caller would invoke this routine with
+ *   use_timeout==FALSE.  The regular os_gets() would not satisfy this need,
+ *   because it cannot resume an interrupted input.)
+ *   
+ *   Note that a zero timeout has the same meaning as for os_get_event(): if
+ *   input is available IMMEDIATELY, return the input, otherwise return
+ *   immediately with the OS_EVT_TIMEOUT result code.  
+ */
+int os_gets_timeout(unsigned char *buf, size_t bufl,
+                    unsigned long timeout_in_milliseconds, int use_timeout);
+
+/*
+ *   Cancel an interrupted editing session.  This MUST be called if any
+ *   output is to be displayed after a call to os_gets_timeout() returns
+ *   OS_EVT_TIMEOUT.
+ *   
+ *   'reset' indicates whether or not we will forget the input state saved
+ *   by os_gets_timeout() when it last returned.  If 'reset' is true, we'll
+ *   clear the input state, so that the next call to os_gets_timeout() will
+ *   start with an empty input buffer.  If 'reset' is false, we will retain
+ *   the previous input state, if any; this means that the next call to
+ *   os_gets_timeout() will re-display the same input buffer that was under
+ *   construction when it last returned.
+ *   
+ *   This routine need not be called if os_gets_timeout() is to be called
+ *   again with no other output operations between the previous
+ *   os_gets_timeout() call and the next one.
+ *   
+ *   Note that this routine needs only a trivial implementation when
+ *   os_gets_timeout() is not supported (i.e., the function always returns
+ *   OS_EVT_NOTIMEOUT).  
+ */
+void os_gets_cancel(int reset);
+
+/* 
+ *   Read a character from the keyboard.  For extended keystrokes, this
+ *   function returns zero, and then returns the CMD_xxx code for the
+ *   extended keystroke on the next call.  For example, if the user presses
+ *   the up-arrow key, the first call to os_getc() should return 0, and the
+ *   next call should return CMD_UP.  Refer to the CMD_xxx codes below.
+ *   
+ *   os_getc() should return a high-level, translated command code for
+ *   command editing.  This means that, where a functional interpretation of
+ *   a key and the raw key-cap interpretation both exist as CMD_xxx codes,
+ *   the functional interpretation should be returned.  For example, on Unix,
+ *   Ctrl-E is conventionally used in command editing to move to the end of
+ *   the line, following Emacs key bindings.  Hence, os_getc() should return
+ *   CMD_END for this keystroke, rather than a single 0x05 character (ASCII
+ *   Ctrl-E), because CMD_END is the high-level command code for the
+ *   operation.
+ *   
+ *   The translation ability of this function allows for system-dependent key
+ *   mappings to functional meanings.  
+ */
+int os_getc(void);
+
+/*
+ *   Read a character from the keyboard, following the same protocol as
+ *   os_getc() for CMD_xxx codes (i.e., when an extended keystroke is
+ *   encountered, os_getc_raw() returns zero, then returns the CMD_xxx code
+ *   on the subsequent call).
+ *   
+ *   This function differs from os_getc() in that this function returns the
+ *   low-level, untranslated key code whenever possible.  This means that,
+ *   when a functional interpretation of a key and the raw key-cap
+ *   interpretation both exist as CMD_xxx codes, this function returns the
+ *   key-cap interpretation.  For the Unix Ctrl-E example in the comments
+ *   describing os_getc() above, this function should return 5 (the ASCII
+ *   code for Ctrl-E), because the ASCII control character interpretation is
+ *   the low-level key code.
+ *   
+ *   This function should return all control keys using their ASCII control
+ *   codes, whenever possible.  Similarly, this function should return ASCII
+ *   27 for the Escape key, if possible.  
+ *   
+ *   For keys for which there is no portable ASCII representation, this
+ *   should return the CMD_xxx sequence.  So, this function acts exactly the
+ *   same as os_getc() for arrow keys, function keys, and other special keys
+ *   that have no ASCII representation.  This function returns a
+ *   non-translated version ONLY when an ASCII representation exists - in
+ *   practice, this means that this function and os_getc() vary only for CTRL
+ *   keys and Escape.  
+ */
+int os_getc_raw(void);
+
+
+/* wait for a character to become available from the keyboard */
+void os_waitc(void);
+
+/*
+ *   Constants for os_getc() when returning commands.  When used for
+ *   command line editing, special keys (arrows, END, etc.)  should cause
+ *   os_getc() to return 0, and return the appropriate CMD_ value on the
+ *   NEXT call.  Hence, os_getc() must keep the appropriate information
+ *   around statically for the next call when a command key is issued.
+ *   
+ *   The comments indicate which CMD_xxx codes are "translated" codes and
+ *   which are "raw"; the difference is that, when a particular keystroke
+ *   could be interpreted as two different CMD_xxx codes, one translated
+ *   and the other raw, os_getc() should always return the translated
+ *   version of the key, and os_getc_raw() should return the raw version.
+ */
+#define CMD_UP    1                        /* move up/up arrow (translated) */
+#define CMD_DOWN  2                    /* move down/down arrow (translated) */
+#define CMD_RIGHT 3                  /* move right/right arrow (translated) */
+#define CMD_LEFT  4                    /* move left/left arrow (translated) */
+#define CMD_END   5              /* move cursor to end of line (translated) */
+#define CMD_HOME  6            /* move cursor to start of line (translated) */
+#define CMD_DEOL  7                   /* delete to end of line (translated) */
+#define CMD_KILL  8                      /* delete entire line (translated) */
+#define CMD_DEL   9                /* delete current character (translated) */
+#define CMD_SCR   10                 /* toggle scrollback mode (translated) */
+#define CMD_PGUP  11                                /* page up (translated) */
+#define CMD_PGDN  12                              /* page down (translated) */
+#define CMD_TOP   13                            /* top of file (translated) */
+#define CMD_BOT   14                         /* bottom of file (translated) */
+#define CMD_F1    15                               /* function key F1 (raw) */
+#define CMD_F2    16                               /* function key F2 (raw) */
+#define CMD_F3    17                               /* function key F3 (raw) */
+#define CMD_F4    18                               /* function key F4 (raw) */
+#define CMD_F5    19                               /* function key F5 (raw) */
+#define CMD_F6    20                               /* function key F6 (raw) */
+#define CMD_F7    21                               /* function key F7 (raw) */
+#define CMD_F8    22                               /* function key F8 (raw) */
+#define CMD_F9    23                               /* function key F9 (raw) */
+#define CMD_F10   24                              /* function key F10 (raw) */
+#define CMD_CHOME 25                                  /* control-home (raw) */
+#define CMD_TAB   26                                    /* tab (translated) */
+#define CMD_SF2   27                                      /* shift-F2 (raw) */
+/* not used (obsolete) - 28 */
+#define CMD_WORD_LEFT  29      /* word left (ctrl-left on dos) (translated) */
+#define CMD_WORD_RIGHT 30    /* word right (ctrl-right on dos) (translated) */
+#define CMD_WORDKILL 31                   /* delete word right (translated) */
+#define CMD_EOF   32                                   /* end-of-file (raw) */
+#define CMD_BREAK 33     /* break (Ctrl-C or local equivalent) (translated) */
+#define CMD_INS   34                                    /* insert key (raw) */
+
+
+/*
+ *   ALT-keys - add alphabetical code to CMD_ALT: ALT-A == CMD_ALT + 0,
+ *   ALT-B == CMD_ALT + 1, ALT-C == CMD_ALT + 2, etc
+ *   
+ *   These keys are all raw (untranslated).  
+ */
+#define CMD_ALT   128                                  /* start of ALT keys */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Event information structure for os_get_event.  The appropriate union
+ *   member should be filled in, depending on the type of event that
+ *   occurs. 
+ */
+union os_event_info_t
+{
+    /* 
+     *   OS_EVT_KEY - this returns the one or two characters of the
+     *   keystroke.  If the key is an extended key, so that os_getc() would
+     *   return a two-character sequence for the keystroke, the first
+     *   character should be zero and the second the extended key code.
+     *   Otherwise, the first character should simply be the ASCII key code.
+     *   
+     *   The key code here is the "raw" keycode, equivalent to the codes
+     *   returned by os_getc_raw().  Note in particular that this means that
+     *   CTRL and Escape keys are presented as one-byte ASCII control
+     *   characters, not as two-byte CMD_xxx sequences.  
+     *   
+     *   For multi-byte character sets (Shift-JIS, for example), note that
+     *   os_get_event() must NOT return a complete two-byte character here.
+     *   The two bytes here are exclusively used to represent special
+     *   CMD_xxx keys (such as arrow keys and function keys).  For a
+     *   keystroke that is represented in a multi-byte character set using
+     *   more than one byte, os_get_event() must return a series of events.
+     *   Return an ordinary OS_EVT_KEY for the first byte of the sequence,
+     *   putting the byte in key[0]; then, return the second byte as a
+     *   separate OS_EVT_KEY as the next event; and so on for any additional
+     *   bytes.  This will allow callers that are not multibyte-aware to
+     *   treat multi-byte characters as though they were sequences of
+     *   one-byte characters.  
+     */
+    int key[2];
+
+    /*
+     *   OS_EVT_HREF - this returns the text of the HREF as a
+     *   null-terminated string.  
+     */
+    char href[256];
+
+    /* command ID (for OS_EVT_COMMAND) */
+    int cmd_id;
+};
+typedef union os_event_info_t os_event_info_t;
+
+/*
+ *   Event types for os_get_event 
+ */
+
+/* invalid/no event */
+#define OS_EVT_NONE      0x0000
+
+/* OS_EVT_KEY - user typed a key on the keyboard */
+#define OS_EVT_KEY       0x0001
+
+/* OS_EVT_TIMEOUT - no event occurred before the timeout elapsed */
+#define OS_EVT_TIMEOUT   0x0002
+
+/* 
+ *   OS_EVT_HREF - user clicked on a <A HREF> link.  This only applies to
+ *   the HTML-enabled run-time. 
+ */
+#define OS_EVT_HREF      0x0003
+
+/* 
+ *   OS_EVT_NOTIMEOUT - caller requested a timeout, but timeout is not
+ *   supported by this version of the run-time 
+ */
+#define OS_EVT_NOTIMEOUT 0x0004
+
+/*
+ *   OS_EVT_EOF - an error occurred reading the event.  This generally
+ *   means that the application is quitting or we can no longer read from
+ *   the keyboard or terminal. 
+ */
+#define OS_EVT_EOF       0x0005
+
+/* 
+ *   OS_EVT_LINE - user entered a line of text on the keyboard.  This event
+ *   is not returned from os_get_event(), but rather from os_gets_timeout().
+ */
+#define OS_EVT_LINE      0x0006
+
+
+/*
+ *   Get an input event.  The event types are shown above.  If use_timeout is
+ *   false, this routine should simply wait until one of the events it
+ *   recognizes occurs, then return the appropriate information on the event.
+ *   If use_timeout is true, this routine should return OS_EVT_TIMEOUT after
+ *   the given number of milliseconds elapses if no event occurs first.
+ *   
+ *   This function is not obligated to obey the timeout.  If a timeout is
+ *   specified and it is not possible to obey the timeout, the function
+ *   should simply return OS_EVT_NOTIMEOUT.  The trivial implementation thus
+ *   checks for a timeout, returns an error if specified, and otherwise
+ *   simply waits for the user to press a key.
+ *   
+ *   A timeout value of 0 does *not* mean that there's no timeout (i.e., it
+ *   doesn't mean we should wait indefinitely) - that's specified by passing
+ *   FALSE for use_timeout.  A zero timeout also doesn't meant that the
+ *   function should unconditionally return OS_EVT_TIMEOUT.  Instead, a zero
+ *   timeout specifically means that IF an event is available IMMEDIATELY,
+ *   without blocking the thread, we should return that event; otherwise we
+ *   should immediately return a timeout event.  
+ */
+int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout,
+                 os_event_info_t *info);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Extended os_get_event() codes.
+ *   
+ *   THESE ARE NOT USED in the basic osifc implementation - these are only
+ *   used if the interpreter supports the "extended" interface defined in
+ *   osifcext.h.  
+ */
+
+/*
+ *   System menu command event.  Some interpreters (such as HTML TADS for
+ *   Windows) provide menu commands for common system-level game verbs -
+ *   SAVE, RESTORE, UNDO, QUIT.  By default, these commands are returned as
+ *   literal command strings ("save", "restore", etc) via os_gets(), as
+ *   though the user had typed the commands at the keyboard.  The extended OS
+ *   interface allows the program to receive these commands via
+ *   os_get_event().  When a command is enabled for os_get_event() via the
+ *   extended OS interface, and the player selects the command via a menu (or
+ *   toolbar button, etc) during a call to os_get_event(), os_get_event()
+ *   returns this event code, with the menu ID stored in the cmd_id field of
+ *   the event structure.  
+ */
+#define OS_EVT_COMMAND   0x0100
+
+/* command IDs for OS_EVT_COMMAND */
+#define OS_CMD_NONE      0x0000     /* invalid command ID, for internal use */
+#define OS_CMD_SAVE      0x0001                                /* save game */
+#define OS_CMD_RESTORE   0x0002                             /* restore game */
+#define OS_CMD_UNDO      0x0003                           /* undo last turn */
+#define OS_CMD_QUIT      0x0004                                /* quit game */
+#define OS_CMD_CLOSE     0x0005                    /* close the game window */
+#define OS_CMD_HELP      0x0006                           /* show game help */
+
+/* highest command ID used in this version of the interface */
+#define OS_CMD_LAST      0x0006
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Ask for input through a dialog.
+ *   
+ *   'prompt' is a text string to display as a prompting message.  For
+ *   graphical systems, this message should be displayed in the dialog;
+ *   for text systems, this should be displayed on the terminal after a
+ *   newline.
+ *   
+ *   'standard_button_set' is one of the OS_INDLG_xxx values defined
+ *   below, or zero.  If this value is zero, no standard button set is to
+ *   be used; the custom set of buttons defined in 'buttons' is to be used
+ *   instead.  If this value is non-zero, the appropriate set of standard
+ *   buttons, with labels translated to the local language if possible, is
+ *   to be used.
+ *   
+ *   'buttons' is an array of strings to use as button labels.
+ *   'button_count' gives the number of entries in the 'buttons' array.
+ *   'buttons' and 'button_count' are ignored if 'standard_button_set' is
+ *   non-zero, since a standard set of buttons is used instead.  If
+ *   'buttons' and 'button_count' are to be used, each entry contains the
+ *   label of a button to show.  
+ */
+/*   
+ *   An ampersand ('&') character in a label string indicates that the
+ *   next character after the '&' is to be used as the short-cut key for
+ *   the button, if supported.  The '&' should NOT be displayed in the
+ *   string; instead, the character should be highlighted according to
+ *   local system conventions.  On Windows, for example, the short-cut
+ *   character should be shown underlined; on a text display, the response
+ *   might be shown with the short-cut character enclosed in parentheses.
+ *   If there is no local convention for displaying a short-cut character,
+ *   then the '&' should simply be removed from the displayed text.  
+ *   
+ *   The short-cut key specified by each '&' character should be used in
+ *   processing responses.  If the user presses the key corresponding to a
+ *   button's short-cut, the effect should be the same as if the user
+ *   clicked the button with the mouse.  If local system conventions don't
+ *   allow for short-cut keys, any short-cut keys can be ignored.
+ *   
+ *   'default_index' is the 1-based index of the button to use as the
+ *   default.  If this value is zero, there is no default response.  If
+ *   the user performs the appropriate system-specific action to select
+ *   the default response for the dialog, this is the response that is to
+ *   be selected.  On Windows, for example, pressing the "Return" key
+ *   should select this item.
+ *   
+ *   'cancel_index' is the 1-based index of the button to use as the
+ *   cancel response.  If this value is zero, there is no cancel response.
+ *   This is the response to be used if the user cancels the dialog using
+ *   the appropriate system-specific action.  On Windows, for example,
+ *   pressing the "Escape" key should select this item.  
+ */
+/*
+ *   icon_id is one of the OS_INDLG_ICON_xxx values defined below.  If
+ *   possible, an appropriate icon should be displayed in the dialog.
+ *   This can be ignored in text mode, and also in GUI mode if there is no
+ *   appropriate system icon.
+ *   
+ *   The return value is the 1-based index of the response selected.  If
+ *   an error occurs, return 0.  
+ */
+int os_input_dialog(int icon_id, const char *prompt, int standard_button_set,
+                    const char **buttons, int button_count,
+                    int default_index, int cancel_index);
+
+/*
+ *   Standard button set ID's 
+ */
+
+/* OK */
+#define OS_INDLG_OK            1
+
+/* OK, Cancel */
+#define OS_INDLG_OKCANCEL      2
+
+/* Yes, No */
+#define OS_INDLG_YESNO         3
+
+/* Yes, No, Cancel */
+#define OS_INDLG_YESNOCANCEL   4
+
+/*
+ *   Dialog icons 
+ */
+
+/* no icon */
+#define OS_INDLG_ICON_NONE     0
+
+/* warning */
+#define OS_INDLG_ICON_WARNING  1
+
+/* information */
+#define OS_INDLG_ICON_INFO     2
+
+/* question */
+#define OS_INDLG_ICON_QUESTION 3
+
+/* error */
+#define OS_INDLG_ICON_ERROR    4
+
+/*
+ *   OBSOLETE - Get filename from startup parameter, if possible; returns
+ *   true and fills in the buffer with the parameter filename on success,
+ *   false if no parameter file could be found.
+ *   
+ *   (This was used until TADS 2.2.5 for the benefit of the Mac interpreter,
+ *   and interpreters on systems with similar desktop shells, to allow the
+ *   user to launch the terp by double-clicking on a saved game file.  The
+ *   terp would read the launch parameters, discover that a saved game had
+ *   been used to invoke it, and would then stash away the saved game info
+ *   for later retrieval from this function.  This functionality was replaced
+ *   in 2.2.5 with a command-line parameter: the terp now uses the desktop
+ *   launch data to synthesize a suitable argv[] vectro to pass to os0main()
+ *   or os0main2().  This function should now simply be stubbed out - it
+ *   should simply return FALSE.)  
+ */
+int os_paramfile(char *buf);
+
+/* 
+ *   Initialize.  This should be called during program startup to
+ *   initialize the OS layer and check OS-specific command-line arguments.
+ *   
+ *   If 'prompt' and 'buf' are non-null, and there are no arguments on the
+ *   given command line, the OS code can use the prompt to ask the user to
+ *   supply a filename, then store the filename in 'buf' and set up
+ *   argc/argv to give a one-argument command string.  (This mechanism for
+ *   prompting for a filename is obsolescent, and is retained for
+ *   compatibility with a small number of existing implementations only;
+ *   new implementations should ignore this mechanism and leave the
+ *   argc/argv values unchanged.)  
+ */
+int os_init(int *argc, char *argv[], const char *prompt,
+            char *buf, int bufsiz);
+
+/*
+ *   Termination functions.  There are three main termination functions,
+ *   described individually below; here's a brief overview of the
+ *   relationship among the functions.  The important thing to realize is
+ *   that these functions have completely independent purposes; they should
+ *   never call one another, and they should never do any of the work that's
+ *   intended for the others.
+ *   
+ *   os_uninit() is meant to undo the effects of os_init().  On many
+ *   systems, os_init() has some global effect, such as setting the terminal
+ *   to some special input or output mode.  os_uninit's purpose is to undo
+ *   these global effects, returning the terminal mode (and whatever else)
+ *   to the conditions they were in at program startup.  Portable callers
+ *   are meant to call this routine at some point before terminating if they
+ *   ever called os_init().  Note that this routine DOES NOT terminate the
+ *   program - it should simply undo anything that os_init() did and return,
+ *   to let the caller do any additional termination work of its own.
+ *   
+ *   os_expause() optionally pauses before termination, to allow the user to
+ *   acknowledge any text the program displays just before exiting.  This
+ *   doesn't have to do anything at all, but it's useful on systems where
+ *   program termination will do something drastic like close the window:
+ *   without a pause, the user wouldn't have a chance to read any text the
+ *   program displayed after the last interactive input, because the window
+ *   would abruptly disappear moments after the text was displayed.  For
+ *   systems where termination doesn't have such drastic effects, there's no
+ *   need to do anything in this routine.  Because it's up to this routine
+ *   whether or not to pause, this routine must display a prompt if it's
+ *   going to pause for user input - the portable caller obviously can't do
+ *   so, because the caller doesn't know if the routine is going to pause or
+ *   not (so if the caller displayed a prompt assuming the routine would
+ *   pause, the prompt would show up even on systems where there actually is
+ *   no pause, which would be confusing).  This routine DOES NOT terminate
+ *   the program; it simply pauses if necessary to allow the user to
+ *   acknowledge the last bit of text the program displayed, then returns to
+ *   allow the caller to carry on with its own termination work.
+ *   
+ *   os_term() is meant to perform the same function as the C standard
+ *   library routine exit(): this actually terminates the program, exiting
+ *   to the operating system.  This routine is not meant to return to its
+ *   caller, because it's supposed to exit the program directly.  For many
+ *   systems, this routine can simply call exit(); the portable code calls
+ *   this routine instead of calling exit() directly, because some systems
+ *   have their own OS-specific way of terminating rather than using exit().
+ *   This routine MUST NOT undo the effects of os_init(), because callers
+ *   might not have ever called os_init(); callers are required to call
+ *   os_uninit() if they ever called os_init(), before calling os_term(), so
+ *   this routine can simply assume that any global modes set by os_init()
+ *   have already been undone by the time this is called.  
+ */
+
+/*
+ *   Uninitialize.  This is called prior to progam termination to reverse
+ *   the effect of any changes made in os_init().  For example, if
+ *   os_init() put the terminal in raw mode, this should restore the
+ *   previous terminal mode.  This routine should not terminate the
+ *   program (so don't call exit() here) - the caller might have more
+ *   processing to perform after this routine returns.  
+ */
+void os_uninit(void);
+
+/* 
+ *   Pause prior to exit, if desired.  This is meant to be called by
+ *   portable code just before the program is to be terminated; it can be
+ *   implemented to show a prompt and wait for user acknowledgment before
+ *   proceeding.  This is useful for implementations that are using
+ *   something like a character-mode terminal window running on a graphical
+ *   operating system: this gives the implementation a chance to pause
+ *   before exiting, so that the window doesn't just disappear
+ *   unceremoniously.
+ *   
+ *   This is allowed to do nothing at all.  For regular character-mode
+ *   systems, this routine usually doesn't do anything, because when the
+ *   program exits, the terminal will simply return to the OS command
+ *   prompt; none of the text displayed just before the program exited will
+ *   be lost, so there's no need for any interactive pause.  Likewise, for
+ *   graphical systems where the window will remain open, even after the
+ *   program exits, until the user explicitly closes the window, there's no
+ *   need to do anything here.
+ *   
+ *   If this is implemented to pause, then this routine MUST show some kind
+ *   of prompt to let the user know we're waiting.  In the simple case of a
+ *   text-mode terminal window on a graphical OS, this should simply print
+ *   out some prompt text ("Press a key to exit...") and then wait for the
+ *   user to acknowledge the prompt (by pressing a key, for example).  For
+ *   graphical systems, the prompt could be placed in the window's title
+ *   bar, or status-bar, or wherever is appropriate for the OS.  
+ */
+void os_expause(void);
+
+/* 
+ *   Terminate.  This should exit the program with the given exit status.
+ *   In general, this should be equivalent to the standard C library
+ *   exit() function, but we define this interface to allow the OS code to
+ *   do any necessary pre-termination cleanup.  
+ */
+void os_term(int status);
+
+/* 
+ *   Install/uninstall the break handler.  If possible, the OS code should
+ *   set (if 'install' is true) or clear (if 'install' is false) a signal
+ *   handler for keyboard break signals (control-C, etc, depending on
+ *   local convention).  The OS code should set its own handler routine,
+ *   which should note that a break occurred with an internal flag; the
+ *   portable code uses os_break() from time to time to poll this flag.  
+ */
+void os_instbrk(int install);
+
+/*
+ *   Check for user break ("control-C", etc) - returns true if a break is
+ *   pending, false if not.  If this returns true, it should "consume" the
+ *   pending break (probably by simply clearing the OS code's internal
+ *   break-pending flag).  
+ */
+bool os_break(void);
+
+/*
+ *   Sleep for a given interval.  This should simply pause for the given
+ *   number of milliseconds, then return.  On multi-tasking systems, this
+ *   should use a system API to suspend the current process for the desired
+ *   delay; on single-tasking systems, this can simply sit in a wait loop
+ *   until the desired interval has elapsed.  
+ */
+void os_sleep_ms(long delay_in_milliseconds);
+
+/*
+ *   Yield CPU; returns TRUE if user requested an interrupt (a "control-C"
+ *   type of operation to abort the entire program), FALSE to continue.
+ *   Portable code should call this routine from time to time during lengthy
+ *   computations that don't involve any UI operations; if practical, this
+ *   routine should be invoked roughly every 10 to 100 milliseconds.
+ *   
+ *   The purpose of this routine is to support "cooperative multitasking"
+ *   systems, such as pre-X MacOS, where it's necessary for each running
+ *   program to call the operating system explicitly in order to yield the
+ *   CPU from time to time to other concurrently running programs.  On
+ *   cooperative multitasking systems, a program can only lose control of
+ *   the CPU by making specific system calls, usually related to GUI events;
+ *   a program can never lose control of the CPU asynchronously.  So, a
+ *   program that performs lengthy computations without any UI interaction
+ *   can cause the rest of the system to freeze up until the computations
+ *   are finished; but if a compute-intensive program explicitly yields the
+ *   CPU from time to time, it allows other programs to remain responsive.
+ *   Yielding the CPU at least every 100 milliseconds or so will generally
+ *   allow the UI to remain responsive; yielding more frequently than every
+ *   10 ms or so will probably start adding noticeable overhead.
+ *   
+ *   On single-tasking systems (such as MS-DOS), there's only one program
+ *   running at a time, so there's no need to yield the CPU; on virtually
+ *   every modern system, the OS automatically schedules CPU time without
+ *   the running programs having any say in the matter, so again there's no
+ *   need for a program to yield the CPU.  For systems where this routine
+ *   isn't needed, the system header should simply #define os_yield to
+ *   something like "((void)0)" - this will allow the compiler to completely
+ *   ignore calls to this routine for systems where they aren't needed.
+ *   
+ *   Note that this routine is NOT meant to provide scheduling hinting to
+ *   modern systems with true multitasking, so a trivial implementation is
+ *   fine for any modern system.  
+ */
+#ifndef os_yield
+int os_yield(void);
+#endif
+
+/*
+ *   Set the default saved-game extension.  This routine will NOT be called
+ *   when we're using the standard saved game extension; this routine will be
+ *   invoked only if we're running as a stand-alone game, and the game author
+ *   specified a non-standard saved-game extension when creating the
+ *   stand-alone game.
+ *   
+ *   This routine is not required if the system does not use the standard,
+ *   semi-portable os0.c implementation.  Even if the system uses the
+ *   standard os0.c implementation, it can provide an empty routine here if
+ *   the system code doesn't need to do anything special with this
+ *   information.
+ *   
+ *   The extension is specified as a null-terminated string.  The extension
+ *   does NOT include the leading period.  
+ */
+void os_set_save_ext(const char *ext);
+
+/* 
+ *   Get the saved game extension previously set with os_set_save_ext().
+ *   Returns null if no custom extension has been set.
+ */
+const char *os_get_save_ext();
+
+
+/* ------------------------------------------------------------------------*/
+/*
+ *   Translate a character from the HTML 4 Unicode character set to the
+ *   current character set used for display.  Takes an HTML 4 character
+ *   code and returns the appropriate local character code.
+ *   
+ *   The result buffer should be filled in with a null-terminated string
+ *   that should be used to represent the character.  Multi-character
+ *   results are possible, which may be useful for certain approximations
+ *   (such as using "(c)" for the copyright symbol).
+ *   
+ *   Note that we only define this prototype if this symbol isn't already
+ *   defined as a macro, which may be the case on some platforms.
+ *   Alternatively, if the function is already defined (for example, as an
+ *   inline function), the defining code can define OS_XLAT_HTML4_DEFINED,
+ *   in which case we'll also omit this prototype.
+ *   
+ *   Important: this routine provides the *default* mapping that is used
+ *   when no external character mapping file is present, and for any named
+ *   entities not defined in the mapping file.  Any entities in the
+ *   mapping file, if used, will override this routine.
+ *   
+ *   A trivial implementation of this routine (that simply returns a
+ *   one-character result consisting of the original input character,
+ *   truncated to eight bits if necessary) can be used if you want to
+ *   require an external mapping file to be used for any game that
+ *   includes HTML character entities.  The DOS version implements this
+ *   routine so that games will still look reasonable when played with no
+ *   mapping file present, but other systems are not required to do this.  
+ */
+#ifndef os_xlat_html4
+# ifndef OS_XLAT_HTML4_DEFINED
+void os_xlat_html4(unsigned int html4_char,
+                   char *result, size_t result_buf_len);
+# endif
+#endif
+
+/*
+ *   Generate a filename for a character-set mapping file.  This function
+ *   should determine the current native character set in use, if
+ *   possible, then generate a filename, according to system-specific
+ *   conventions, that we should attempt to load to get a mapping between
+ *   the current native character set and the internal character set
+ *   identified by 'internal_id'.
+ *   
+ *   The internal character set ID is a string of up to 4 characters.
+ *   
+ *   On DOS, the native character set is a DOS code page.  DOS code pages
+ *   are identified by 3- or 4-digit identifiers; for example, code page
+ *   437 is the default US ASCII DOS code page.  We generate the
+ *   character-set mapping filename by appending the internal character
+ *   set identifier to the DOS code page number, then appending ".TCP" to
+ *   the result.  So, to map between ISO Latin-1 (internal ID = "La1") and
+ *   DOS code page 437, we would generate the filename "437La1.TCP".
+ *   
+ *   Note that this function should do only two things.  First, determine
+ *   the current native character set that's in use.  Second, generate a
+ *   filename based on the current native code page and the internal ID.
+ *   This function is NOT responsible for figuring out the mapping or
+ *   anything like that -- it's simply where we generate the correct
+ *   filename based on local convention.
+ *   
+ *   'filename' is a buffer of at least OSFNMAX characters.
+ *   
+ *   'argv0' is the executable filename from the original command line.
+ *   This parameter is provided so that the system code can look for
+ *   mapping files in the original TADS executables directory, if desired.
+ */
+void os_gen_charmap_filename(char *filename, char *internal_id,
+                             char *argv0);
+
+/*
+ *   Receive notification that a character mapping file has been loaded.
+ *   The caller doesn't require this routine to do anything at all; this
+ *   is purely for the system-dependent code's use so that it can take
+ *   care of any initialization that it must do after the caller has
+ *   loaded a charater mapping file.  'id' is the character set ID, and
+ *   'ldesc' is the display name of the character set.  'sysinfo' is the
+ *   extra system information string that is stored in the mapping file;
+ *   the interpretation of this information is up to this routine.
+ *   
+ *   For reference, the Windows version uses the extra information as a
+ *   code page identifier, and chooses its default font character set to
+ *   match the code page.  On DOS, the run-time requires the player to
+ *   activate an appropriate code page using a DOS command (MODE CON CP
+ *   SELECT) prior to starting the run-time, so this routine doesn't do
+ *   anything at all on DOS. 
+ */
+void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo);
+
+/*
+ *   Generate the name of the character set mapping table for Unicode
+ *   characters to and from the given local character set.  Fills in the
+ *   buffer with the implementation-dependent name of the desired
+ *   character set map.  See below for the character set ID codes.
+ *   
+ *   For example, on Windows, the implementation would obtain the
+ *   appropriate active code page (which is simply a Windows character set
+ *   identifier number) from the operating system, and build the name of
+ *   the Unicode mapping file for that code page, such as "CP1252".  On
+ *   Macintosh, the implementation would look up the current script system
+ *   and return the name of the Unicode mapping for that script system,
+ *   such as "ROMAN" or "CENTEURO".
+ *   
+ *   If it is not possible to determine the specific character set that is
+ *   in use, this function should return "asc7dflt" (ASCII 7-bit default)
+ *   as the character set identifier on an ASCII system, or an appropriate
+ *   base character set name on a non-ASCII system.  "asc7dflt" is the
+ *   generic character set mapping for plain ASCII characters.
+ *   
+ *   The given buffer must be at least 32 bytes long; the implementation
+ *   must limit the result it stores to 32 bytes.  (We use a fixed-size
+ *   buffer in this interface for simplicity, and because there seems no
+ *   need for greater flexibility in the interface; a character set name
+ *   doesn't carry very much information so shouldn't need to be very
+ *   long.  Note that this function doesn't generate a filename, but
+ *   simply a mapping name; in practice, a map name will be used to
+ *   construct a mapping file name.)
+ *   
+ *   Because this function obtains the Unicode mapping name, there is no
+ *   need to specify the internal character set to be used: the internal
+ *   character set is Unicode.  
+ */
+/*
+ *   Implementation note: when porting this routine, the convention that
+ *   you use to name your mapping files is up to you.  You should simply
+ *   choose a convention for this implementation, and then use the same
+ *   convention for packaging the mapping files for your OS release.  In
+ *   most cases, the best convention is to use the names that the Unicode
+ *   consortium uses in their published cross-mapping listings, since
+ *   these listings can be used as the basis of the mapping files that you
+ *   include with your release.  For example, on Windows, the convention
+ *   is to use the code page number to construct the map name, as in
+ *   CP1252 or CP1250.  
+ */
+void os_get_charmap(char *mapname, int charmap_id);
+
+/*
+ *   Character map for the display (i.e., for the user interface).  This
+ *   is the character set which is used for input read from the keyboard,
+ *   and for output displayed on the monitor or terminal.  
+ */
+#define OS_CHARMAP_DISPLAY     1
+
+/* 
+ *   Character map for mapping filename strings.  This should identify the
+ *   character set currently in use on the local system for filenames
+ *   (i.e., for strings identifying objects in the local file system),
+ *   providing a suitable name for use in opening a mapping file.
+ *   
+ *   On many platforms, the entire system uses only one character set at
+ *   the OS level, so the file system character set is the same as the
+ *   display character set.  Some systems define a particular character
+ *   set for file system use, though, because different users might be
+ *   running applications on terminals that display different character
+ *   sets.  
+ */
+#define OS_CHARMAP_FILENAME    2
+
+/*
+ *   Default character map for file contents.  On most systems, this will
+ *   be the same as display.  On some systems, it won't be possible to
+ *   know in general what character set files use; in fact, this isn't
+ *   possible anywhere, since a file might have been copied without
+ *   translation from a different type of computer using a different
+ *   character set.  So, this isn't meant to provide a reliable choice of
+ *   character set for any arbitrary file; it's simply meant to be a good
+ *   guess that most files on this system are likely to use.  
+ */
+#define OS_CHARMAP_FILECONTENTS  3
+
+/*
+ *   Default character map for the command line.  This is the maping we use
+ *   to interpret command line arguments passed to our main() or equivalent.
+ *   On most systems, this will be the same as the display character set.
+ */
+#define OS_CHARMAP_CMDLINE     4
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   External Banner Interface.  This interface provides the ability to
+ *   divide the display window into multiple sub-windows, each with its own
+ *   independent contents.
+ *   
+ *   To determine where a new banner is displayed, we look at the banners as
+ *   a tree, rooted at the "main window," the special banner that the system
+ *   automatically creates initially for the main game text.  We start by
+ *   allocating the entire display (or the entire application window, if
+ *   we're running on a GUI system) to the main window.  We then traverse
+ *   the tree, starting with the root window's children.  For each child
+ *   window, we allocate space for the child out of the parent window's
+ *   area, according to the child's alignment and size settings, and deduct
+ *   this space from the parent window's size.  We then lay out the children
+ *   of the child.
+ *   
+ *   For each banner window, we take its requested space out of the parent
+ *   window's area by starting at the edge of the parent window rectangle as
+ *   indicated by the banner's alignment, and taking the requested `width
+ *   (for a left/right banner) or height (for a top/bottom banner), limiting
+ *   to the available width/height in the parent window's space.  Give the
+ *   banner the full extent of the parent's space in its other dimension (so
+ *   a left/right banner gets the full height of the parent space, and a
+ *   top/bottom banner gets the full width).
+ *   
+ *   Note that the layout proceeds exclusively down the tree (i.e., from the
+ *   root to children to grandchildren, and so on).  It *appears* that a
+ *   child affects its parent, because of the deduction step: a child
+ *   acquires screen space by carving out a chunk of its parent.  The right
+ *   way to think about this, though, is that the parent's full area is the
+ *   union of the parent window and all of its children; when viewed this
+ *   way, the parent's full area is fully determined the instant the parent
+ *   is laid out, and never changes as its children are laid out.  Note in
+ *   particular that a child can never make a parent larger; the only thing
+ *   a child can do to a parent is carve out a chunk of the parent for
+ *   itself, which doesn't affect the boundaries of the union of the parent
+ *   plus its children.
+ *   
+ *   Note also that if the banner has a border, and the implementation
+ *   actually draws borders, the border must be drawn for the *full* area of
+ *   the banner, as defined above.  For example, suppose we have two
+ *   borders: banner A is a child of the main window, is top-aligned, and
+ *   has a border.  Banner B is a child of banner A, right-aligned, with no
+ *   border.  Obviously, without considering banner B, banner A's space runs
+ *   across the entire width of the main window, so its border (at the
+ *   bottom of its area) runs across the entire width of the main window.
+ *   Banner B carves out some space from A's right side for itself, so
+ *   banner A's actual on-screen area runs from the left edge of the main
+ *   window to banner B's left edge.  However, even though banner A itself
+ *   no longer runs the full width of the main window, banner A's *full*
+ *   area - that is, the union of banner A's on-screen area and all of its
+ *   children's full areas - does still run the entire width of the main
+ *   window, hence banner A's border must still run the full width of the
+ *   main window.  The simple way of looking at this is that a banner's
+ *   border is always to be drawn exactly the same way, regardless of
+ *   whether or not the banner has children - simply draw the banner as it
+ *   would be drawn if the banner had no children.
+ *   
+ *   Each time a banner is added or removed, we must recalculate the layout
+ *   of the remaining banners and main text area.  The os_banner_xxx()
+ *   implementation is responsible for this layout refiguring.
+ *   
+ *   The entire external banner window interface is optional, although the
+ *   functions must at least be defined as dummies to avoid linker errors
+ *   when building.  If a platform doesn't implement this feature,
+ *   os_banner_create() should simply return null, and the other routines
+ *   can do nothing.  
+ */
+
+/* 
+ *   Create a banner window.  'info' gives the desired parameters for the new
+ *   banner.
+ *   
+ *   Note that certain requested parameter settings might or might not be
+ *   respected, depending on the capabilities of the platform and user
+ *   preferences.  os_banner_getinfo() can be used after creation to
+ *   determine which parameter settings are actually used in the new banner.
+ *   
+ *   'parent' gives the parent of this banner; this is the banner handle of
+ *   another banner window, or null.  If 'parent' is null, then the new
+ *   banner is a child of the main window, which the system creates
+ *   automatically at startup and which contains the main input/output
+ *   transcript.  The new banner's on-screen area is carved out of the
+ *   parent's space, according to the alignment and size settings of the new
+ *   window, so this determines how the window is laid out on the screen.
+ *   
+ *   'where' is OS_BANNER_FIRST to make the new window the first child of its
+ *   parent; OS_BANNER_LAST to make it the last child of its parent;
+ *   OS_BANNER_BEFORE to insert it immediately before the existing banner
+ *   identified by handle in 'other'; or OS_BANNER_AFTER to insert
+ *   immediately after 'other'.  When BEFORE or AFTER is used, 'other' must
+ *   be another child of the same parent; if it is not, the routine should
+ *   act as though 'where' were given as OS_BANNER_LAST.
+ *   
+ *   'other' is a banner handle for an existing banner window.  This is used
+ *   to specify the relative position among children of the new banner's
+ *   parent, if 'where' is either OS_BANNER_BEFORE or OS_BANNER_AFTER.  If
+ *   'where' is OS_BANNER_FIRST or OS_BANNER_LAST, 'other' is ignored.
+ *   
+ *   'wintype' is the type of the window.  This is one of the
+ *   OS_BANNER_TYPE_xxx codes indicating what kind of window is desired.
+ *   
+ *   'align' is the banner's alignment, given as an OS_BANNER_ALIGN_xxx
+ *   value.  Top/bottom banners are horizontal: they run across the full
+ *   width of the existing main text area.  Left/right banners are vertical:
+ *   they run down the full height of the existing main text area.
+ *   
+ *   'siz' is the requested size of the new banner.  The meaning of 'siz'
+ *   depends on the value of 'siz_units', which can be OS_BANNER_SIZE_PCT to
+ *   set the size as a percentage of the REMAINING space, or
+ *   OS_BANNER_SIZE_ABS to set an absolute size in the "natural" units of the
+ *   window.  The natural units vary by window type: for text and text grid
+ *   windows, this is in rows/columns of '0' characters in the default font
+ *   for the window.  Note that when OS_BANNER_SIZE_ABS is used in a text or
+ *   text grid window, the OS implementation MUST add the space needed for
+ *   margins and borders when determining the actual pixel size of the
+ *   window; in other words, the window should be large enough that it can
+ *   actually display the given number or rows or columns.
+ *   
+ *   The size is interpreted as a width or height according to the window's
+ *   orientation.  For a TOP or BOTTOM banner, the size is the height; for a
+ *   LEFT or RIGHT banner, the size is the width.  A banner has only one
+ *   dimension's size given, since the other dimension's size is determined
+ *   automatically by the layout rules.
+ *   
+ *   Note that the window's size can be changed later using
+ *   banner_size_to_contents() or banner_set_size().
+ *   
+ *   'style' is a combination of OS_BANNER_STYLE_xxx flags - see below.  The
+ *   style flags give the REQUESTED style for the banner, which might or
+ *   might not be respected, depending on the platform's capabilities, user
+ *   preferences, and other factors.  os_banner_getinfo() can be used to
+ *   determine which style flags are actually used.
+ *   
+ *   Returns the "handle" to the new banner window, which is an opaque value
+ *   that is used in subsequent os_banner_xxx calls to operate on the window.
+ *   Returns null if the window cannot be created.  An implementation is not
+ *   required to support this functionality at all, and can subset it if it
+ *   does support it (for example, an implementation could support only
+ *   top/bottom-aligned banners, but not left/right-aligned), so callers must
+ *   be prepared for this routine to return null.  
+ */
+void *os_banner_create(void *parent, int where, void *other, int wintype,
+                       int align, int siz, int siz_units,
+                       unsigned long style);
+
+
+/*
+ *   insertion positions 
+ */
+#define OS_BANNER_FIRST   1
+#define OS_BANNER_LAST    2
+#define OS_BANNER_BEFORE  3
+#define OS_BANNER_AFTER   4
+
+/*
+ *   banner types 
+ */
+
+/* 
+ *   Normal text stream window.  This is a text stream that behaves
+ *   essentially like the main text window: text is displayed to this
+ *   through os_banner_disp(), always in a stream-like fashion by adding new
+ *   text to the end of any exiting text.
+ *   
+ *   Systems that use proportional fonts should usually simply use the same
+ *   font they use by default in the main text window.  However, note that
+ *   the OS_BANNER_STYLE_TAB_ALIGN style flag might imply that a fixed-pitch
+ *   font should be used even when proportional fonts are available, because
+ *   a fixed-pitch font will allow the calling code to rely on using spaces
+ *   to align text within the window.  
+ */
+#define OS_BANNER_TYPE_TEXT       1
+
+/* 
+ *   "Text grid" window.  This type of window is similar to an normal text
+ *   window (OS_BANNER_TYPE_TEXT), but is guaranteed to arrange its text in
+ *   a regular grid of character cells, all of the same size.  This means
+ *   that the output position can be moved to an arbitrary point within the
+ *   window at any time, so the calling program can precisely control the
+ *   layout of the text in the window.
+ *   
+ *   Because the output position can be moved to arbitrary positions in the
+ *   window, it is possible to overwrite text previously displayed.  When
+ *   this happens, the old text is completely obliterated by the new text,
+ *   leaving no trace of the overwritten text.
+ *   
+ *   In order to guarantee that character cells are all the same size, this
+ *   type of window does not allow any text attributes.  The implementation
+ *   should simply ignore any attempts to change text attributes in this
+ *   type of window.  However, colors can be used to the same degree they
+ *   can be used in an ordinary text window.
+ *   
+ *   To guarantee the regular spacing of character cells, all
+ *   implementations must use fixed-pitch fonts for these windows.  This
+ *   applies even to platforms where proportional fonts are available.  
+ */
+#define OS_BANNER_TYPE_TEXTGRID   2
+
+
+/* 
+ *   banner alignment types 
+ */
+#define OS_BANNER_ALIGN_TOP       0
+#define OS_BANNER_ALIGN_BOTTOM    1
+#define OS_BANNER_ALIGN_LEFT      2
+#define OS_BANNER_ALIGN_RIGHT     3
+
+/*
+ *   size units 
+ */
+#define OS_BANNER_SIZE_PCT  1
+#define OS_BANNER_SIZE_ABS  2
+
+
+/* 
+ *   banner style flags 
+ */
+
+/* 
+ *   The banner has a visible border; this indicates that a line is to be
+ *   drawn to separate the banner from the adjacent window or windows
+ *   "inside" the banner.  So, a top-aligned banner will have its border
+ *   drawn along its bottom edge; a left-aligned banner will show a border
+ *   along its right edge; and so forth.
+ *   
+ *   Note that character-mode platforms generally do NOT respect the border
+ *   style, since doing so takes up too much screen space.  
+ */
+#define OS_BANNER_STYLE_BORDER     0x00000001
+
+/*
+ *   The banner has a vertical/horizontal scrollbar.  Character-mode
+ *   platforms generally do not support scrollbars.  
+ */
+#define OS_BANNER_STYLE_VSCROLL    0x00000002
+#define OS_BANNER_STYLE_HSCROLL    0x00000004
+
+/* 
+ *   Automatically scroll the banner vertically/horizontally whenever new
+ *   text is displayed in the window.  In other words, whenever
+ *   os_banner_disp() is called, scroll the window so that the text that the
+ *   new cursor position after the new text is displayed is visible in the
+ *   window.
+ *   
+ *   Note that this style is independent of the presence of scrollbars.
+ *   Even if there are no scrollbars, we can still scroll the window's
+ *   contents programmatically.
+ *   
+ *   Implementations can, if desired, keep an internal buffer of the
+ *   window's contents, so that the contents can be recalled via the
+ *   scrollbars if the text displayed in the banner exceeds the space
+ *   available in the banner's window on the screen.  If the implementation
+ *   does keep such a buffer, we recommend the following method for managing
+ *   this buffer.  If the AUTO_VSCROLL flag is not set, then the banner's
+ *   contents should be truncated at the bottom when the contents overflow
+ *   the buffer; that is, once the banner's internal buffer is full, any new
+ *   text that the calling program attempts to add to the banner should
+ *   simply be discarded.  If the AUTO_VSCROLL flag is set, then the OLDEST
+ *   text should be discarded instead, so that the most recent text is
+ *   always retained.  
+ */
+#define OS_BANNER_STYLE_AUTO_VSCROLL 0x00000008
+#define OS_BANNER_STYLE_AUTO_HSCROLL 0x00000010
+
+/*
+ *   Tab-based alignment is required/supported.  On creation, this is a hint
+ *   to the implementation that is sometimes necessary to determine what
+ *   kind of font to use in the new window, for non-HTML platforms.  If this
+ *   flag is set on creation, the caller is indicating that it wants to use
+ *   <TAB> tags to align text in the window.
+ *   
+ *   Character-mode implementations that use a single font with fixed pitch
+ *   can simply ignore this.  These implementations ALWAYS have a working
+ *   <TAB> capability, because the portable output formatter provides <TAB>
+ *   interpretation for a fixed-pitch window.
+ *   
+ *   Full HTML TADS implementations can also ignore this.  HTML TADS
+ *   implementations always have full <TAB> support via the HTML
+ *   parser/renderer.
+ *   
+ *   Text-only implementations on GUI platforms (i.e., implementations that
+ *   are not based on the HTML parser/renderer engine in HTML TADS, but
+ *   which run on GUI platforms with proportionally-spaced text) should use
+ *   this flag to determine the font to display.  If this flag is NOT set,
+ *   then the caller doesn't care about <TAB>, and the implementation is
+ *   free to use a proportionally-spaced font in the window if desired.
+ *   
+ *   When retrieving information on an existing banner, this flag indicates
+ *   that <TAB> alignment is actually supported on the window.  
+ */
+#define OS_BANNER_STYLE_TAB_ALIGN 0x00000020
+
+/*
+ *   Use "MORE" mode in this window.  By default, a banner window should
+ *   happily allow text to overflow the vertical limits of the window; the
+ *   only special thing that should happen on overflow is that the window
+ *   should be srolled down to show the latest text, if the auto-vscroll
+ *   style is set.  With this flag, though, a banner window acts just like
+ *   the main text window: when the window fills up vertically, we show a
+ *   MORE prompt (using appropriate system conventions), and wait for the
+ *   user to indicate that they're ready to see more text.  On most systems,
+ *   the user acknowledges a MORE prompt by pressing a key or scrolling with
+ *   the mouse, but it's up to the system implementor to decide what's
+ *   appropriate for the system.
+ *   
+ *   Note that MORE mode in ANY banner window should generally override all
+ *   other user input focus.  In other words, if the game in the main window
+ *   would like to read a keystroke from the user, but one of the banner
+ *   windows is pausing with a MORE prompt, any keyboard input should be
+ *   directed to the banner paused at the MORE prompt, not to the main
+ *   window; the main window should not receive any key events until the MORE
+ *   prompt has been removed.
+ *   
+ *   This style requires the auto-vscroll style.  Implementations should
+ *   assume auto-vscroll when this style is set.  This style can be ignored
+ *   with text grid windows.  
+ */
+#define OS_BANNER_STYLE_MOREMODE  0x00000040
+
+/*
+ *   This banner is a horizontal/vertical "strut" for sizing purposes.  This
+ *   means that the banner's content size is taken into account when figuring
+ *   the content size of its *parent* banner.  If the banner has the same
+ *   orientation as the parent, its content size is added to its parent's
+ *   internal content size to determine the parent's overall content size.
+ *   If the banner's orientation is orthogonal to the parent's, then the
+ *   parent's overall content size is the larger of the parent's internal
+ *   content size and this banner's content size.  
+ */
+#define OS_BANNER_STYLE_HSTRUT    0x00000080
+#define OS_BANNER_STYLE_VSTRUT    0x00000100
+
+
+/* 
+ *   Delete a banner.  This removes the banner from the display, which
+ *   requires recalculating the entire screen's layout to reallocate this
+ *   banner's space to other windows.  When this routine returns, the banner
+ *   handle is invalid and can no longer be used in any os_banner_xxx
+ *   function calls.  
+ *   
+ *   If the banner has children, the children will no longer be displayed,
+ *   but will remain valid in memory until deleted.  A child window's
+ *   display area always comes out of its parent's space, so once the parent
+ *   is gone, a child has no way to acquire any display space; resizing the
+ *   child won't help, since it simply has no way to obtain any screen space
+ *   once its parent has been deleted.  Even though the window's children
+ *   will become invisible, their banner handles will remain valid; the
+ *   caller is responsible for explicitly deleting the children even after
+ *   deleting their parent.  
+ */
+void os_banner_delete(void *banner_handle);
+
+/*
+ *   "Orphan" a banner.  This tells the osifc implementation that the caller
+ *   wishes to sever all of its ties with the banner (as part of program
+ *   termination, for example), but that the calling program does not
+ *   actually require that the banner's on-screen display be immediately
+ *   removed.
+ *   
+ *   The osifc implementation can do one of two things:
+ *   
+ *   1.  Simply call os_banner_delete().  If the osifc implementation
+ *   doesn't want to do anything extra with the banner, it can simply delete
+ *   the banner, since the caller has no more use for it.
+ *   
+ *   2.  Take ownership of the banner.  If the osifc implementation wishes
+ *   to continue displaying the final screen configuration after a program
+ *   has terminated, it can simply take over the banner and leave it on the
+ *   screen.  The osifc subsystem must eventually delete the banner itself
+ *   if it takes this routine; for example, if the osifc subsystem allows
+ *   another client program to be loaded into the same window after a
+ *   previous program has terminated, it would want to delete any orphaned
+ *   banners from the previous program when loading a new program.  
+ */
+void os_banner_orphan(void *banner_handle);
+
+/*
+ *   Banner information structure.  This is filled in by the system-specific
+ *   implementation in os_banner_getinfo().  
+ */
+struct os_banner_info_t
+{
+    /* alignment */
+    int align;
+
+    /* style flags - these indicate the style flags actually in use */
+    unsigned long style;
+
+    /* 
+     *   Actual on-screen size of the banner, in rows and columns.  If the
+     *   banner is displayed in a proportional font or can display multiple
+     *   fonts of different sizes, this is approximated by the number of "0"
+     *   characters in the window's default font that will fit in the
+     *   window's display area.  
+     */
+    int rows;
+    int columns;
+
+    /*
+     *   Actual on-screen size of the banner in pixels.  This is meaningful
+     *   only for full HTML interpreter; for text-only interpreters, these
+     *   are always set to zero.
+     *   
+     *   Note that even if we're running on a GUI operating system, these
+     *   aren't meaningful unless this is a full HTML interpreter.  Text-only
+     *   interpreters should always set these to zero, even on GUI OS's.  
+     */
+    int pix_width;
+    int pix_height;
+
+    /* 
+     *   OS line wrapping flag.  If this is set, the window uses OS-level
+     *   line wrapping because the window uses a proportional font, so the
+     *   caller does not need to (and should not) perform line breaking in
+     *   text displayed in the window.
+     *   
+     *   Note that OS line wrapping is a PERMANENT feature of the window.
+     *   Callers can note this information once and expect it to remain
+     *   fixed through the window's lifetime.  
+     */
+    int os_line_wrap;
+};
+typedef struct os_banner_info_t os_banner_info_t;
+
+/* 
+ *   Get information on the banner - fills in the information structure with
+ *   the banner's current settings.  Note that this should indicate the
+ *   ACTUAL properties of the banner, not the requested properties; this
+ *   allows callers to determine how the banner is actually displayed, which
+ *   depends upon the platform's capabilities and user preferences.
+ *   
+ *   Returns true if the information was successfully obtained, false if
+ *   not.  This can return false if the underlying OS window has already
+ *   been closed by a user action, for example.  
+ */
+int os_banner_getinfo(void *banner_handle, os_banner_info_t *info);
+
+/* 
+ *   Get the character width/height of the banner, for layout purposes.  This
+ *   gives the size of the banner in character cells.
+ *   
+ *   These are not meaningful when the underlying window uses a proportional
+ *   font or varying fonts of different sizes.  When the size of text varies
+ *   in the window, the OS layer is responsible for word-wrapping and other
+ *   layout, in which case these simply return zero.
+ *   
+ *   Note that these routines might appear to be redundant with the 'rows'
+ *   and 'columns' information returned from os_banner_getinfo(), but these
+ *   have two important distinctions.  First, these routines return only the
+ *   width and height information, so they can be implemented with less
+ *   overhead than os_banner_getinfo(); this is important because formatters
+ *   might need to call these routines frequently while formatting text.
+ *   Second, these routines are not required to return an approximation for
+ *   windows using proportional fonts, as os_banner_getinfo() does; these can
+ *   simply return zero when a proportional font is in use.  
+ */
+int os_banner_get_charwidth(void *banner_handle);
+int os_banner_get_charheight(void *banner_handle);
+
+/* clear the contents of a banner */
+void os_banner_clear(void *banner_handle);
+
+/* 
+ *   Display output on a banner.  Writes the output to the window on the
+ *   display at the current output position.
+ *   
+ *   The following special characters should be recognized and handled:
+ *   
+ *   '\n' - newline; move output position to the start of the next line.
+ *   
+ *   '\r' - move output position to start of current line; subsequent text
+ *   overwrites any text previously displayed on the current line.  It is
+ *   permissible to delete the old text immediately on seeing the '\r',
+ *   rather than waiting for additional text to actually overwrite it.
+ *   
+ *   All other characters should simply be displayed as ordinary printing
+ *   text characters.  Note that tab characters should not be passed to this
+ *   routine, but if they are, they can simply be treated as ordinary spaces
+ *   if desired.  Other control characters (backspace, escape, etc) should
+ *   never be passed to this routine; the implementation is free to ignore
+ *   any control characters not listed above.
+ *   
+ *   If any text displayed here overflows the current boundaries of the
+ *   window on the screen, the text MUST be "clipped" to the current window
+ *   boundaries; in other words, anything this routine tries to display
+ *   outside of the window's on-screen rectangle must not actually be shown
+ *   on the screen.
+ *   
+ *   Text overflowing the display boundaries MUST also be retained in an
+ *   internal buffer.  This internal buffer can be limited to the actual
+ *   maximum display size of the terminal screen or application window, if
+ *   desired.  It is necessary to retain clipped text, because this allows a
+ *   window to be expanded to the size of its contents AFTER the contents
+ *   have already been displayed.
+ *   
+ *   If the banner does its own line wrapping, it must indicate this via the
+ *   os_line_wrap flag in the os_banner_getinfo() return data.  If the
+ *   banner doesn't indicate this flag, then it must not do any line
+ *   wrapping at all, even if the caller attempts to write text beyond the
+ *   right edge of the window - any text overflowing the width of the window
+ *   must simply be clipped.
+ *   
+ *   Text grid banners must ALWAYS clip - these banners should never perform
+ *   any line wrapping.  
+ */
+void os_banner_disp(void *banner_handle, const char *txt, size_t len);
+
+/*
+ *   Set the text attributes in a banner, for subsequent text displays.
+ *   'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values. 
+ */
+void os_banner_set_attr(void *banner_handle, int attr);
+
+/* 
+ *   Set the text color in a banner, for subsequent text displays.  The 'fg'
+ *   and 'bg' colors are given as RGB or parameterized colors; see the
+ *   definition of os_color_t for details.
+ *   
+ *   If the underlying renderer is HTML-enabled, then this should not be
+ *   used; the appropriate HTML code should simply be displayed to the
+ *   banner instead.  
+ */
+void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg);
+
+/* 
+ *   Set the screen color in the banner - this is analogous to the screen
+ *   color in the main text area.
+ *   
+ *   If the underlying renderer is HTML-enabled, then this should not be
+ *   used; the HTML <BODY> tag should be used instead.  
+ */
+void os_banner_set_screen_color(void *banner_handle, os_color_t color);
+
+/* flush output on a banner */
+void os_banner_flush(void *banner_handle);
+
+/*
+ *   Set the banner's size.  The size has the same meaning as in
+ *   os_banner_create().
+ *   
+ *   'is_advisory' indicates whether the sizing is required or advisory only.
+ *   If this flag is false, then the size should be set as requested.  If
+ *   this flag is true, it means that the caller intends to call
+ *   os_banner_size_to_contents() at some point, and that the size being set
+ *   now is for advisory purposes only.  Platforms that support
+ *   size-to-contents may simply ignore advisory sizing requests, although
+ *   they might want to ensure that they have sufficient off-screen buffer
+ *   space to keep track of the requested size of display, so that the
+ *   information the caller displays in preparation for calling
+ *   size-to-contents will be retained.  Platforms that do not support
+ *   size-to-contents should set the requested size even when 'is_advisory'
+ *   is true.  
+ */
+void os_banner_set_size(void *banner_handle, int siz, int siz_units,
+                        int is_advisory);
+
+/* 
+ *   Set the banner to the size of its current contents.  This can be used
+ *   to set the banner's size after some text (or other material) has been
+ *   displayed to the banner, so that the size can be set according to the
+ *   banner's actual space requirements.
+ *   
+ *   This changes the banner's "requested size" to match the current size.
+ *   Subsequent calls to os_banner_getinfo() will thus indicate a requested
+ *   size according to the size set here.  
+ */
+void os_banner_size_to_contents(void *banner_handle);
+
+/* 
+ *   Turn HTML mode on/off in the banner window.  If the underlying renderer
+ *   doesn't support HTML, these have no effect.  
+ */
+void os_banner_start_html(void *banner_handle);
+void os_banner_end_html(void *banner_handle);
+
+/*
+ *   Set the output coordinates in a text grid window.  The grid window is
+ *   arranged into character cells numbered from row zero, column zero for
+ *   the upper left cell.  This function can only be used if the window was
+ *   created with type OS_BANNER_TYPE_TEXTGRID; the request should simply be
+ *   ignored by other window types.
+ *   
+ *   Moving the output position has no immediate effect on the display, and
+ *   does not itself affect the "content size" for the purposes of
+ *   os_banner_size_to_contents().  This simply sets the coordinates where
+ *   any subsequent text is displayed.  
+ */
+void os_banner_goto(void *banner_handle, int row, int col);
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Get system information.  'code' is a SYSINFO_xxx code, which
+ *   specifies what type of information to get.  The 'param' argument's
+ *   meaning depends on which code is selected.  'result' is a pointer to
+ *   an integer that is to be filled in with the result value.  If the
+ *   code is not known, this function should return FALSE.  If the code is
+ *   known, the function should fill in *result and return TRUE.
+ */
+int os_get_sysinfo(int code, void *param, long *result);
+
+/* determine if systemInfo is supported - os_get_sysinfo never gets this */
+#define SYSINFO_SYSINFO   1
+
+/* get interpreter version number - os_get_sysinfo never gets this */
+#define SYSINFO_VERSION   2
+
+/* get operating system name - os_get_sysinfo never gets this */
+#define SYSINFO_OS_NAME   3
+
+/* 
+ *   Can the system process HTML directives?  returns 1 if so, 0 if not.
+ *   Note that if this returns false, then all of the codes below from
+ *   JPEG to LINKS are implicitly false as well, since TADS can only use
+ *   images, sounds, and links through HTML. 
+ */
+#define SYSINFO_HTML      4
+
+/* can the system display JPEG's?  1 if yes, 0 if no */
+#define SYSINFO_JPEG      5
+
+/* can the system display PNG's?  1 if yes, 0 if no */
+#define SYSINFO_PNG       6
+
+/* can the system play WAV's?  1 if yes, 0 if no */
+#define SYSINFO_WAV       7
+
+/* can the system play MIDI's?  1 if yes, 0 if no */
+#define SYSINFO_MIDI      8
+
+/* can the system play MIDI and WAV's simultaneously?  yes=1, no=0 */
+#define SYSINFO_WAV_MIDI_OVL  9
+
+/* can the system play multiple WAV's simultaneously?  yes=1, no=0 */
+#define SYSINFO_WAV_OVL   10
+
+/*
+ *   GENERAL NOTES ON PREFERENCE SETTINGS:
+ *   
+ *   Several of the selectors below refer to the preference settings.  We're
+ *   talking about user-settable options to control various aspects of the
+ *   interpreter.  The conventional GUI for this kind of thing is a dialog
+ *   box reachable through a menu command named something like "Options" or
+ *   "Preferences".  A couple of general notes about these:
+ *   
+ *   1.  The entire existence of a preferences mechanism is optional -
+ *   interpreter writers aren't required to implement anything along these
+ *   lines.  In some cases the local platforms might not have any suitable
+ *   conventions for a preferences UI (e.g., character-mode console
+ *   applications), and in other cases the terp developer might just want to
+ *   omit a prefs mechanism because of the work involved to implement it, or
+ *   to keep the UI simpler.
+ *   
+ *   2.  If a given SYSINFO_PREF_xxx selector refers to a preference item
+ *   that's not implemented in the local interpreter, the terp should simply
+ *   return a suitable default result.  For example, if the interpreter
+ *   doesn't have a preference item to allow the user to turn sounds off, the
+ *   selector SYSINFO_PREF_SOUNDS should return 1 to indicate that the user
+ *   has not in fact turned off sounds (because there's no way to do so).
+ *   
+ *   3.  The various SYSINFO_PREF_xxx selectors are purely queries - they're
+ *   NOT a mechanism for enforcing the preferences.  For example, if the
+ *   interpreter provides a "Sounds On/Off" option, it's up to the terp to
+ *   enforce it the Off setting by ignoring any sound playback requests.  The
+ *   game isn't under any obligation to query any of the preferences or to
+ *   alter its behavior based on their settings - you should expect that the
+ *   game will go on trying to play sounds even when "Sounds Off" is selected
+ *   in the preferences.  The purpose of these SYSINFO selectors is to let
+ *   the game determine the current presentation status, but *only if it
+ *   cares*.  For example, the game might determine whether or not sounds are
+ *   actually being heard just before playing a sound effect that's important
+ *   to the progress of the game, so that it can provide a visual alternative
+ *   if the effect won't be heard.  
+ */
+
+/* 
+ *   Get image preference setting - 1 = images can be displayed, 0 = images
+ *   are not being displayed because the user turned off images in the
+ *   preferences.  This is, of course, irrelevant if images can't be
+ *   displayed at all.
+ *   
+ *   See the general notes on preferences queries above.  
+ */
+#define SYSINFO_PREF_IMAGES  11
+
+/*
+ *   Get digitized sound effect (WAV) preference setting - 1 = sounds can be
+ *   played, 0 = sounds are not being played because the user turned off
+ *   sounds in the preferences.
+ *   
+ *   See the general notes on preferences queries above.  
+ */
+#define SYSINFO_PREF_SOUNDS  12
+
+/*
+ *   Get music (MIDI) preference setting - 1 = music can be played, 0 = music
+ *   is not being played because the user turned off music in the
+ *   preferences.
+ *   
+ *   See the general notes on preferences queries above.  
+ */
+#define SYSINFO_PREF_MUSIC   13
+
+/*
+ *   Get link display preference setting - 0 = links are not being displayed
+ *   because the user set a preference item that suppresses all links (which
+ *   doesn't actually hide them, but merely displays them and otherwise
+ *   treats them as ordinary text).  1 = links are to be displayed normally.
+ *   2 = links can be displayed temporarily by the user by pressing a key or
+ *   some similar action, but aren't being displayed at all times.  
+ *   
+ *   See the general note on preferences queries above.  
+ */
+#define SYSINFO_PREF_LINKS   14
+
+/* can the system play MPEG sounds of any kind? */
+#define SYSINFO_MPEG         15
+
+/* can the system play MPEG audio 2.0 layer I/II/III sounds? */
+#define SYSINFO_MPEG1        16
+#define SYSINFO_MPEG2        17
+#define SYSINFO_MPEG3        18
+
+/* 
+ *   is the system *currently* in HTML mode?  os_get_sysinfo never gets
+ *   this code, since the portable output layer keeps track of this 
+ */
+#define SYSINFO_HTML_MODE    19
+
+/* 
+ *   Does the system allow following external URL links of the various
+ *   types?  These return true if the system is capable of following these
+ *   types of hypertext links, false if not.  Note that, if the system is
+ *   capable of following these links, these should return true regardless
+ *   of any current mode settings; in particular, these should not be
+ *   sensitive to the current HTML mode or the current link display mode,
+ *   since the question is not whether a link now displayed can be
+ *   followed by the user, but rather whether the system has the
+ *   capability to follow these types of links at all.  
+ */
+#define SYSINFO_LINKS_HTTP   20
+#define SYSINFO_LINKS_FTP    21
+#define SYSINFO_LINKS_NEWS   22
+#define SYSINFO_LINKS_MAILTO 23
+#define SYSINFO_LINKS_TELNET 24
+
+/* is PNG transparency supported? */
+#define SYSINFO_PNG_TRANS    25
+
+/* is PNG alpha blending supported? */
+#define SYSINFO_PNG_ALPHA    26
+
+/* is the Ogg Vorbis audio format supported? */
+#define SYSINFO_OGG          27
+
+/* can the system display MNG's? */
+#define SYSINFO_MNG          28
+
+/* can the system display MNG's with transparency? */
+#define SYSINFO_MNG_TRANS    29
+
+/* can the system display MNG's with alpha blending? */
+#define SYSINFO_MNG_ALPHA    30
+
+/* can we display highlighted text in its own appearance? */
+#define SYSINFO_TEXT_HILITE  31
+
+/* 
+ *   Can we display text colors?  This returns a SYSINFO_TXC_xxx code
+ *   indicating the level of color support.
+ *   
+ *   The os_xxx interfaces don't presently support anything beyond the ANSI
+ *   colors; however, HTML-enabled interpreters generally support full RGB
+ *   colors, so we call this out as a separate level.  
+ */
+#define SYSINFO_TEXT_COLORS  32
+
+/* no text color support */
+#define SYSINFO_TXC_NONE      0
+
+/* parameterized color names only (OS_COLOR_P_TEXT, etc) */
+#define SYSINFO_TXC_PARAM     1
+
+/* 
+ *   we support only the basic ANSI colors, foreground control only (white,
+ *   black, blue, red, green, yellow, cyan, magenta) 
+ */
+#define SYSINFO_TXC_ANSI_FG   2
+
+/* ANSI colors, foreground and background */
+#define SYSINFO_TXC_ANSI_FGBG 3
+
+/* full RGB support */
+#define SYSINFO_TXC_RGB       4
+
+/* are the os_banner_xxx() interfaces supported? */
+#define SYSINFO_BANNERS      33
+
+/* Interpreter Class - this returns one of the SYSINFO_ICLASS_xxx codes */
+#define SYSINFO_INTERP_CLASS 34
+
+/* 
+ *   Interpreter class: Character-mode Text-Only.  Interpreters of this class
+ *   use a single, fixed-pitch font to display all text, and use the
+ *   text-only HTML subset, and cannot display graphics.
+ */
+#define SYSINFO_ICLASS_TEXT    1
+
+/* 
+ *   Interpreter class: Text-Only GUI.  Interpreters of this class are
+ *   traditional text-only interpreters running on graphical operating
+ *   systems.  These interpreters might use multiple fonts (for example, they
+ *   might display highlighted text in boldface), and might use
+ *   proportionally-spaced text for some windows.  These interpreters support
+ *   the text-only HTML subset, and cannot display graphics.
+ *   
+ *   Text-only GUI interpreters act essentially the same as character-mode
+ *   text-only interpreters, from the perspective of the client program.  
+ */
+#define SYSINFO_ICLASS_TEXTGUI 2
+
+/*
+ *   Interpreter class: HTML.  Interpreters of this class can display
+ *   graphics and sounds, can display multiple fonts and font sizes, can use
+ *   proportional fonts, and support the full HTML TADS markup language for
+ *   formatting.  
+ */
+#define SYSINFO_ICLASS_HTML    3
+
+/*
+ *   Audio fade information.
+ *   
+ *   SYSINFO_AUDIO_FADE: basic fade-in and fade-out support.  Interpreters
+ *   that don't support audio fade at all should return 0.  Interpreters that
+ *   support fades should return a bitwise OR'd combination of
+ *   SYSINFO_AUDIOFADE_xxx flags below indicating which formats can be used
+ *   with fades.
+ *   
+ *   SYSINFO_AUDIO_CROSSFADE: cross-fades are supported (i.e., simultaneous
+ *   play of overlapping tracks, one fading out while the other fades in).
+ *   If cross-fades aren't supported, return 0.  If they're supported, return
+ *   a combination of SYSINFO_AUDIOFADE_xxx flags indicating which formats
+ *   can be used with cross-fades.  
+ */
+#define SYSINFO_AUDIO_FADE       35
+#define SYSINFO_AUDIO_CROSSFADE  36
+
+/* 
+ *   Specific audio fading features.  These are bit flags that can be
+ *   combined to indicate the fading capabilities of the interpreter.  
+ */
+#define SYSINFO_AUDIOFADE_MPEG  0x0001          /* supported for MPEG audio */
+#define SYSINFO_AUDIOFADE_OGG   0x0002          /* supported for Ogg Vorbis */
+#define SYSINFO_AUDIOFADE_WAV   0x0004                 /* supported for WAV */
+#define SYSINFO_AUDIOFADE_MIDI  0x0008                /* supported for MIDI */
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Integer division operators.  For any compiler that follows ANSI C
+ *   rules, no definitions are required for these routine, since the
+ *   standard definitions below will work properly.  However, if your
+ *   compiler does not follow ANSI standards with respect to integer
+ *   division of negative numbers, you must provide implementations of
+ *   these routines that produce the correct results.
+ *   
+ *   Division must "truncate towards zero," which means that any
+ *   fractional part is dropped from the result.  If the result is
+ *   positive, the result must be the largest integer less than the
+ *   algebraic result: 11/3 yields 3.  If the result is negative, the
+ *   result must be the smallest integer less than the result: (-11)/3
+ *   yields -3.
+ *   
+ *   The remainder must obey the relationship (a/b)*b + a%b == a, for any
+ *   integers a and b (b != 0).
+ *   
+ *   If your compiler does not obey the ANSI rules for the division
+ *   operators, make the following changes in your osxxx.h file
+ *   
+ *   - define the symbol OS_NON_ANSI_DIVIDE in the osxxx.h file
+ *   
+ *   - either define your own macros for os_divide_long() and
+ *   os_remainder_long(), or put actual prototypes for these functions
+ *   into your osxxx.h file and write appropriate implementations of these
+ *   functions in one of your osxxx.c or .cpp files.
+ */
+/* long os_divide_long(long a, long b);    // returns (a/b) with ANSI rules */
+/* long os_remainder_long(long a, long b); // returns (a%b) with ANSI rules */
+
+/* standard definitions for any ANSI compiler */
+#ifndef OS_NON_ANSI_DIVIDE
+#define os_divide_long(a, b)     ((a) / (b))
+#define os_remainder_long(a, b)  ((a) % (b))
+#endif
+
 } // End of namespace TADS
 } // End of namespace Glk
 
diff --git a/engines/glk/tads/osfrobtads.cpp b/engines/glk/tads/osfrobtads.cpp
deleted file mode 100644
index 6dcaadb..0000000
--- a/engines/glk/tads/osfrobtads.cpp
+++ /dev/null
@@ -1,100 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "glk/tads/osfrobtads.h"
-#include "common/file.h"
-
-namespace Glk {
-namespace TADS {
-
-osfildef *osfoprb(const char *fname, os_filetype_t typ) {
-	Common::File f;
-	if (f.open(fname))
-		return f.readStream(f.size());
-	else
-		return nullptr;
-}
-
-osfildef *osfoprwtb(const char *fname, os_filetype_t typ) {
-	Common::DumpFile *df = new Common::DumpFile();
-	if (df->open(fname))
-		return df;
-	delete df;
-	return nullptr;
-}
-
-int osfrb(osfildef *fp, void *buf, size_t count) {
-	return dynamic_cast<Common::ReadStream *>(fp)->read(buf, count);
-}
-
-bool osfwb(osfildef *fp, const void *buf, size_t count) {
-	return dynamic_cast<Common::WriteStream *>(fp)->write(buf, count) != count;
-}
-
-void osfflush(osfildef *fp) {
-	dynamic_cast<Common::WriteStream *>(fp)->flush();
-}
-
-osfildef *osfopwt(const char *fname, os_filetype_t typ) {
-	return osfoprwtb(fname, typ);
-}
-
-int osfseek(osfildef *fp, int ofs, int origin) {
-	return dynamic_cast<Common::SeekableReadStream *>(fp)->seek(ofs, origin);
-}
-
-int osfpos(osfildef *fp) {
-	return dynamic_cast<Common::SeekableReadStream *>(fp)->pos();
-}
-
-char *osfgets(char *buf, size_t count, osfildef *fp) {
-	Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(fp);
-	char *ptr = buf;
-	char c;
-	while (!rs->eos() && --count > 0) {
-		c = rs->readByte();
-		if (c == '\n' || c == '\0')
-			break;
-		*ptr++ = c;
-	}
-
-	*ptr++ = '\0';
-	return buf;
-}
-
-int osfputs(const char *str, osfildef *fp) {
-	return dynamic_cast<Common::WriteStream *>(fp)->write(str, strlen(str)) == strlen(str) ? 0 : -1;
-}
-
-bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz) {
-	Common::String name = !flen ? Common::String(fname) : Common::String(fname, fname + flen);
-
-	if (!Common::File::exists(fname))
-		return false;
-
-	strncpy(buf, name.c_str(), bufsiz - 1);
-	buf[bufsiz - 1] = '\0';
-	return true;
-}
-
-} // End of namespace TADS
-} // End of namespace Glk
diff --git a/engines/glk/tads/osfrobtads.h b/engines/glk/tads/osfrobtads.h
deleted file mode 100644
index d503a7b..0000000
--- a/engines/glk/tads/osfrobtads.h
+++ /dev/null
@@ -1,272 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-/* OS-layer functions and macros.
- *
- * This file does not introduce any curses (or other screen-API)
- * dependencies; it can be used for both the interpreter as well as the
- * compiler.
- */
-
-#ifndef GLK_TADS_OSFROBTADS
-#define GLK_TADS_OSFROBTADS
-
-#include "common/fs.h"
-#include "common/stream.h"
-#include "glk/glk_api.h"
-#include "glk/tads/os_filetype.h"
-
-namespace Glk {
-namespace TADS {
-
-/* Defined for Gargoyle. */
-#define HAVE_STDINT_
-
-#if 0
-#include "common.h"
-#endif
-
-/* Used by the base code to inhibit "unused parameter" compiler warnings. */
-#ifndef VARUSED
-#define VARUSED(var) (void)var
-#endif
-
-/* We assume that the C-compiler is mostly ANSI compatible. */
-#define OSANSI
-
-/* Special function qualifier needed for certain types of callback
- * functions.  This is for old 16-bit systems; we don't need it and
- * define it to nothing. */
-#define OS_LOADDS
-
-/* Unices don't suffer the near/far pointers brain damage (thank God) so
- * we make this a do-nothing macro. */
-#define osfar_t
-
-/* This is used to explicitly discard computed values (some compilers
- * would otherwise give a warning like "computed value not used" in some
- * cases).  Casting to void should work on every ANSI-Compiler. */
-#define DISCARD (void)
-
-/* Copies a struct into another.  ANSI C allows the assignment operator
- * to be used with structs. */
-#define OSCPYSTRUCT(x,y) ((x)=(y))
-
-/* Link error messages into the application. */
-#define ERR_LINK_MESSAGES
-
-/* Program Exit Codes. */
-#define OSEXSUCC 0 /* Successful completion. */
-#define OSEXFAIL 1 /* Failure. */
-
-/* Here we configure the osgen layer; refer to tads2/osgen3.c for more
- * information about the meaning of these macros. */
-#define USE_DOSEXT
-#define USE_NULLSTYPE
-
-/* Theoretical maximum osmalloc() size.
- * Unix systems have at least a 32-bit memory space.  Even on 64-bit
- * systems, 2^32 is a good value, so we don't bother trying to find out
- * an exact value. */
-#define OSMALMAX 0xffffffffL
-
-#define OSFNMAX 255
-
-/**
- * File handle structure for osfxxx functions
- * Note that we need to define it as a Common::Stream since the type is used by
- * TADS for both reading and writing files
- */
-typedef Common::Stream osfildef;
-
-/* Directory handle for searches via os_open_dir() et al. */
-typedef Common::FSNode *osdirhdl_t;
-
-/* file type/mode bits */
-#define OSFMODE_FILE    S_IFREG
-#define OSFMODE_DIR     S_IFDIR
-#define OSFMODE_CHAR    S_IFCHR
-#define OSFMODE_BLK     S_IFBLK
-#define OSFMODE_PIPE    S_IFIFO
-#ifdef S_IFLNK
-#define OSFMODE_LINK    S_IFLNK
-#else
-#define OSFMODE_LINK    0
-#endif
-#ifdef S_IFSOCK
-#define OSFMODE_SOCKET  S_IFSOCK
-#else
-#define OSFMODE_SOCKET  0
-#endif
-
-/* File attribute bits. */
-#define OSFATTR_HIDDEN  0x0001
-#define OSFATTR_SYSTEM  0x0002
-#define OSFATTR_READ    0x0004
-#define OSFATTR_WRITE   0x0008
-
-/* Get a file's stat() type. */
-int osfmode( const char* fname, int follow_links, unsigned long* mode,
-             unsigned long* attr );
-
-#if 0
-/* The maximum width of a line of text.
- *
- * We ignore this, but the base code needs it defined.  If the
- * interpreter is run inside a console or terminal with more columns
- * than the value defined here, weird things will happen, so we go safe
- * and use a large value. */
-#define OS_MAXWIDTH 255
-#endif
-
-/* Disable the Tads swap file; computers have plenty of RAM these days.
- */
-#define OS_DEFAULT_SWAP_ENABLED 0
-
-/* TADS 2 macro/function configuration.  Modern configurations always
- * use the no-macro versions, so these definitions should always be set
- * as shown below. */
-#define OS_MCM_NO_MACRO
-#define ERR_NO_MACRO
-
-/* These values are used for the "mode" parameter of osfseek() to
- * indicate where to seek in the file. */
-#define OSFSK_SET SEEK_SET /* Set position relative to the start of the file. */
-#define OSFSK_CUR SEEK_CUR /* Set position relative to the current file position. */
-#define OSFSK_END SEEK_END /* Set position relative to the end of the file. */
-
-
-/* ============= Functions follow ================ */
-
-/* Allocate a block of memory of the given size in bytes. */
-#define osmalloc malloc
-
-/* Free memory previously allocated with osmalloc(). */
-#define osfree free
-
-/* Reallocate memory previously allocated with osmalloc() or osrealloc(),
- * changing the block's size to the given number of bytes. */
-#define osrealloc realloc
-
-/* Open text file for reading. */
-#define osfoprt(fname,typ) osfoprb(fname,typ)
-
-/* Open text file for writing. */
-osfildef *osfopwt(const char *fname, os_filetype_t typ);
-
-/* Open text file for reading and writing, keeping the file's existing
- * contents if the file already exists or creating a new file if no
- * such file exists. */
-osfildef *osfoprwt(const char *fname, os_filetype_t typ);
-
-/* Open text file for reading/writing.  If the file already exists,
- * truncate the existing contents.  Create a new file if it doesn't
- * already exist. */
-#define osfoprwtt(fname,typ) osfoprwt
-
-/* Open binary file for writing. */
-#define osfopwb(fname,typ) osfoprwtb(fname, typ)
-
-/* Open source file for reading - use the appropriate text or binary
- * mode. */
-#define osfoprs osfoprb
-
-/* Open binary file for reading. */
-inline osfildef *osfoprb(const char *fname, os_filetype_t typ);
-
-/* Open binary file for reading/writing.  If the file already exists,
- * keep the existing contents.  Create a new file if it doesn't already
- * exist. */
-osfildef*
-osfoprwb(const char *fname, os_filetype_t typ);
-
-/* Open binary file for writing.  If the file already exists,
- * truncate the existing contents.  Create a new file if it doesn't
- * already exist. */
-inline osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
-
-/* Get a line of text from a text file. */
-char *osfgets(char *buf, size_t count, osfildef *fp);
-
-/* Write a line of text to a text file. */
-int osfputs(const char *str, osfildef *fp);
-
-/* Write bytes to file. */
-inline bool osfwb(osfildef *fp, const void *buf, size_t count);
-
-/* Flush buffered writes to a file. */
-inline void osfflush(osfildef *fp);
-
-/* Read bytes from file. */
-int osfrb(osfildef *fp, void *buf, size_t count);
-
-/* Read bytes from file and return the number of bytes read. */
-#define osfrbc(fp,buf,bufl) osfrb(fp,buf,bufl)
-
-/* Get the current seek location in the file. */
-inline int osfpos(osfildef *fp);
-
-/* Seek to a location in the file. */
-inline int osfseek(osfildef *fp, int ofs, int origin);
-
-/* Close a file. */
-#define osfcls delete
-
-/* Delete a file. */
-#define osfdel remove
-
-/* Access a file - determine if the file exists.
- *
- * We map this to the access() function.  It should be available in
- * virtually every system out there, as it appears in many standards
- * (SVID, AT&T, POSIX, X/OPEN, BSD 4.3, DOS, MS Windows, maybe more). */
-#define osfacc(fname) (access((fname), F_OK))
-
-/* Rename a file. */
-#define os_rename_file(from, to) (rename(from, to) == 0)
-
-/* Get a file's stat() type. */
-struct os_file_stat_t;
-int os_file_stat( const char* fname, int follow_links,
-                  struct os_file_stat_t* s );
-
-/* Get a character from a file. */
-#define osfgetc fgetc
-
-/* Set busy cursor.
- *
- * We don't have a mouse cursor so there's no need to implement this. */
-#define os_csr_busy(a)
-
-/* Update progress display.
- *
- * We don't provide any kind of "compilation progress display", so we
- * just define this as an empty macro.
- */
-#define os_progress(fname,linenum)
-
-bool os_locate(const char *fname, int flen, const char *arg0, char *buf, size_t bufsiz);
-
-} // End of namespace TADS
-} // End of namespace Glk
-
-#endif
diff --git a/engines/glk/tads/tads.cpp b/engines/glk/tads/tads.cpp
index 5dc400b..61e13cd 100644
--- a/engines/glk/tads/tads.cpp
+++ b/engines/glk/tads/tads.cpp
@@ -21,44 +21,18 @@
  */
 
 #include "glk/tads/tads.h"
+#include "glk/tads/os_glk.h"
 #include "common/config-manager.h"
 #include "common/translation.h"
 
 namespace Glk {
 namespace TADS {
 
-TADS::TADS(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
-	/*
-	 * GLK Initialization
-	 */
-
-	// Open the story window
-	story_win = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
-	if (!story_win)
-		error("fatal: could not open window!\n");
-
-	// get default colors for main window
-	if (!glk_style_measure(story_win, style_Normal, stylehint_TextColor, &mainfg))
-		mainfg = 0;
-
-	if (!glk_style_measure(story_win, style_Normal, stylehint_BackColor, &mainbg))
-		mainbg = 0;
-
-	// get default colors for status window
-	status_win = glk_window_open(story_win, winmethod_Above | winmethod_Fixed, 1,
-		wintype_TextGrid, 0);
+TADS *g_vm;
 
-	if (!glk_style_measure(status_win, style_Normal, stylehint_TextColor, &statusfg))
-		statusfg = 0;
-
-	if (!glk_style_measure(status_win, style_Normal, stylehint_BackColor, &statusbg))
-		statusbg = 0;
-
-	// close status window; reopened on request
-	glk_window_close(status_win, 0);
-	status_win = nullptr;
-
-	glk_set_window(story_win);
+TADS::TADS(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
+	g_vm = this;
+	os_init(nullptr, nullptr, 0, 0, 0);
 }
 
 Common::Error TADS::loadGameData(strid_t file) {
diff --git a/engines/glk/tads/tads.h b/engines/glk/tads/tads.h
index 49f59f5..e4640f0 100644
--- a/engines/glk/tads/tads.h
+++ b/engines/glk/tads/tads.h
@@ -59,6 +59,8 @@ public:
 	virtual Common::Error saveGameData(strid_t file, const Common::String &desc) override;
 };
 
+extern TADS *g_vm;
+
 } // End of namespace TADS
 } // End of namespace Glk
 
diff --git a/engines/glk/tads/tads2/appctx.h b/engines/glk/tads/tads2/appctx.h
index 3b3f499..5f32217 100644
--- a/engines/glk/tads/tads2/appctx.h
+++ b/engines/glk/tads/tads2/appctx.h
@@ -24,6 +24,7 @@
 #define GLK_TADS_TADS2_APPCTX
 
 #include "common/scummsys.h"
+#include "glk/tads/os_frob_tads.h"
 
 namespace Glk {
 namespace TADS {
diff --git a/engines/glk/tads/tads2/built_in.h b/engines/glk/tads/tads2/built_in.h
index f5807e6..5078eda 100644
--- a/engines/glk/tads/tads2/built_in.h
+++ b/engines/glk/tads/tads2/built_in.h
@@ -28,7 +28,9 @@
 #ifndef GLK_TADS_TADS2_BUILT_IN
 #define GLK_TADS_TADS2_BUILT_IN
 
+#include "glk/tads/tads2/built_in.h"
 #include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/list.h"
 #include "glk/tads/tads2/run.h"
 #include "glk/tads/tads2/text_io.h"
 #include "glk/tads/tads2/regex.h"
@@ -65,7 +67,7 @@ struct bifcxdef {
     biffildef bifcxfile[BIFFILMAX];          /* file handles for fopen, etc */
     int       bifcxsafetyr;                 /* file I/O safety level - read */
     int       bifcxsafetyw;                /* file I/O safety level - write */
-    char     *bifcxsavext;        /* saved game extension (null by default) */
+    const char *bifcxsavext;      /* saved game extension (null by default) */
     appctxdef *bifcxappctx;                     /* host application context */
     re_context bifcxregex;          /* regular expression searching context */
 
diff --git a/engines/glk/tads/tads2/character_map.cpp b/engines/glk/tads/tads2/character_map.cpp
index 9191835..82fe865 100644
--- a/engines/glk/tads/tads2/character_map.cpp
+++ b/engines/glk/tads/tads2/character_map.cpp
@@ -25,7 +25,7 @@
 #include "glk/tads/tads2/error.h"
 #include "glk/tads/tads2/os.h"
 #include "glk/tads/tads2/text_io.h"
-#include "glk/tads/osfrobtads.h"
+#include "glk/tads/os_frob_tads.h"
 #include "glk/tads/os_glk.h"
 #include "common/algorithm.h"
 
diff --git a/engines/glk/tads/tads2/command_line.cpp b/engines/glk/tads/tads2/command_line.cpp
new file mode 100644
index 0000000..c6bd5e0
--- /dev/null
+++ b/engines/glk/tads/tads2/command_line.cpp
@@ -0,0 +1,87 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/command_line.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* get a toggle argument */
+int cmdtog(errcxdef *ec, int prv, char *argp, int ofs,
+           void (*usagefn)(errcxdef *))
+{
+    switch(argp[ofs + 1])
+    {
+    case '+':
+        return(TRUE);
+        
+    case '-':
+        return(FALSE);
+        
+    case '\0':
+        return(!prv);
+        
+    default:
+        /* invalid - display usage if we have a callback for it */
+        if (usagefn != 0)
+            (*usagefn)(ec);
+        NOTREACHEDV(int);
+        return 0;
+    }
+}
+
+/* get an argument to a switch */
+char *cmdarg(errcxdef *ec, char ***argpp, int *ip, int argc, int ofs,
+             void (*usagefn)(errcxdef *))
+{
+    char *ret;
+
+    /* 
+     *   check to see if the argument is appended directly to the option;
+     *   if not, look at the next string 
+     */
+    ret = (**argpp) + ofs + 1;
+    if (*ret == '\0')
+    {
+        /* 
+         *   it's not part of this string - get the argument from the next
+         *   string in the vector 
+         */
+        ++(*ip);
+        ++(*argpp);
+        ret = (*ip >= argc ? 0 : **argpp);
+    }
+
+    /* 
+     *   if we didn't find the argument, it's an error - display usage if
+     *   we have a valid usage callback
+     */
+    if ((ret == 0 || *ret == 0) && usagefn != 0)
+        (*usagefn)(ec);
+
+    return ret;
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/command_line.h b/engines/glk/tads/tads2/command_line.h
new file mode 100644
index 0000000..a231fca
--- /dev/null
+++ b/engines/glk/tads/tads2/command_line.h
@@ -0,0 +1,70 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* interface to command line option service routines
+ */
+
+#ifndef GLK_TADS_TADS2_COMMAND_LINE
+#define GLK_TADS_TADS2_COMMAND_LINE
+
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/os_frob_tads.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/*
+ *   Get argument to an option.  Option can be rammed up against option
+ *   letter(s) with no space, or can be separated by a space.  argp is a
+ *   pointer to the pointer to the current position in the argv[] array;
+ *   ip is a pointer to the index in the argv[] array.  Both *argpp and
+ *   *ip are incremented if the next word must be read.  argc is the total
+ *   number of arguments.  ofs gives the number of characters (NOT
+ *   including the '-') in this option flag; most options will have ofs==1
+ *   since they are of the form '-x'.  usagefn is a function to call if
+ *   the parsing fails; it is not expected to return, but should signal an
+ *   error instead.  
+ */
+char *cmdarg(errcxdef *ec, char ***argpp, int *ip, int argc,
+             int ofs, void (*usagefn)(errcxdef*));
+
+
+/*
+ *   Read a toggle argument.  prv is the previous value (prior to this
+ *   switch) of the parameter (TRUE or FALSE).  argp is a pointer to the
+ *   current argument word.  ofs is the length of this option flag, NOT
+ *   including the '-'; most options have ofs==1 since they are of the
+ *   form '-x'.  If the option is followed by '+', the value returned is
+ *   TRUE; if it's followed by '-', the value is FALSE; if followed by
+ *   nothing, the option is the logical inverse of the previous value.  If
+ *   it's followed by any other character, we call the usage callback,
+ *   which is not expected to return, but should signal an error. 
+ */
+int cmdtog(struct errcxdef *ec, int prv, char *argp, int ofs,
+           void (*usagefn)(errcxdef*));
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h
index ab1f86e..7c56b2f 100644
--- a/engines/glk/tads/tads2/error_handling.h
+++ b/engines/glk/tads/tads2/error_handling.h
@@ -61,7 +61,7 @@
 #define GLK_TADS_TADS2_ERROR_HANDLING
 
 #include "glk/tads/tads2/lib.h"
-#include "glk/tads/osfrobtads.h"
+#include "glk/tads/os_frob_tads.h"
 
 namespace Glk {
 namespace TADS {
diff --git a/engines/glk/tads/tads2/error_message.cpp b/engines/glk/tads/tads2/error_message.cpp
new file mode 100644
index 0000000..3cfe8de
--- /dev/null
+++ b/engines/glk/tads/tads2/error_message.cpp
@@ -0,0 +1,86 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/error_handling.h"
+#include "glk/tads/tads2/ltk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+	
+/*--------------------------------- lerini ---------------------------------*/
+/*
+ * lerini - allocate and initialize an error context.  Returns a
+ * pointer to an initialized error context if successful, 0 otherwise.
+ */
+errcxdef *lerini() {
+  errcxdef *errcx;                                         /* error context */
+  
+  /* allocate an error context */
+  if (!(errcx = (errcxdef *)ltk_suballoc(sizeof(errcxdef))))
+  {
+    /* failure */
+    return((errcxdef *)0);
+  }
+
+  /* initialize the error context */
+  errcx->errcxfp  = (osfildef *)0;                  /* no error file handle */
+  errcx->errcxofs = 0;                      /* no offset in argument buffer */
+  errcx->errcxlog = ltk_errlog;                    /* error logging routine */
+  errcx->errcxlgc = errcx;                         /* error logging context */
+
+  /* return the new context */
+  return(errcx);
+}
+
+
+/*--------------------------------- lerfre ---------------------------------*/
+/*
+ * lerfre - FREe error context allocated by errini.
+ */
+void lerfre(errcxdef *errcx) {
+  /* free the context */
+  ltk_subfree(errcx);
+}
+
+
+/*--------------------------------- errmsg ---------------------------------*/
+/*
+ * errmsg - format error message number 'err' into the given buffer.
+ */
+void errmsg(errcxdef *ctx, char *outbuf, int outbufl, uint err) {
+  sprintf(outbuf, "Error #%d occured.", err);
+}
+
+/*--------------------------------- errini ---------------------------------*/
+/*
+ * errini - initialize error system.
+ */
+void errini(errcxdef *ctx, char *arg0) {
+    VARUSED(ctx);
+    VARUSED(arg0);
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/execute_command.cpp b/engines/glk/tads/tads2/execute_command.cpp
new file mode 100644
index 0000000..d38d92b
--- /dev/null
+++ b/engines/glk/tads/tads2/execute_command.cpp
@@ -0,0 +1,3688 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/*
+ * TADS Interpreter Execute user Command
+ *
+ * Function
+ *   Executes a user command after it has been parsed
+ * Notes
+ *   TADS 2.0 version
+ * 
+ *   This module contains the implementation of the entire "turn" sequence,
+ *   which is:
+ * 
+ *     preCommand(actor, verb, dobj-list, prep, iobj)
+ *     verb.verbAction(actor, do, prep, io)
+ *     actor.actorAction( verb, do, prep, io )
+ *     actor.location.roomAction( actor, verb, do, prep, io )
+ *     if ( io ) 
+ *     {
+ *       io.iobjCheck(actor, verb, dobj, prep)
+ *       if (io does not define verIo<Verb> directly)
+ *           io.iobjGen(actor, verb, dobj, prep)
+ *       do.dobjCheck(actor, verb, iobj, prep)
+ *       if (do does not define do<Verb> directly)
+ *           do.dobjGen(actor, verb, iobj, prep)
+ *       io.verIo<Verb>( actor, do )
+ *       if ( noOutput )
+ *       {
+ *         do.verDo<Verb>( actor, io )
+ *         if ( noOutput ) io.io<Verb>( actor, do )
+ *       }
+ *     }
+ *     else if ( do )
+ *     {
+ *       do.dobjCheck(actor, verb, nil, nil)
+ *       if (do does not define do<Verb> directly)
+ *           do.dobjGen(actor, verb, nil, nil)
+ *       do.verDo<Verb>( actor )
+ *       if ( noOutput )do.do<Verb>( actor )
+ *     }
+ *     else
+ *     {
+ *       verb.action( actor )
+ *     }
+ *     postAction(actor, verb, dobj, prep, iobj, error_code)
+ *     daemons
+ *     fuses
+ *     endCommand(actor, verb, dobj-list, prep, iobj, error_code)
+ * 
+ *   If an 'exit' or 'exitobj' is encountered, we skip straight to the
+ *   daemons.  If an abort is encountered, we skip to endCommand.  If
+ *   askio, or askdo is encountered, we skip everything remaining.  Under
+ *   any of these exit scenarios, we return success to our caller.
+ *   
+ *   This module also contains code to set and remove fuses and daemons,
+ *   since they are part of the player turn sequence.
+ * Returns
+ *   0 for success, other for failure.
+ */
+
+#include "glk/tads/tads2/built_in.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/memory_cache_heap.h"
+#include "glk/tads/tads2/run.h"
+#include "glk/tads/tads2/vocabulary.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+/* allocate and initialize a fuse/daemon/notifier array */
+void vocinialo(voccxdef *ctx, vocddef **what, int cnt) {
+    vocddef *p;
+    
+    *what = (vocddef *)mchalo(ctx->voccxerr,
+                              (cnt * sizeof(vocddef)), "vocinialo");
+
+    /* set all object/function entries to MCMONINV to indicate not-in-use */
+    for (p = *what ; cnt ; ++p, --cnt)
+        p->vocdfn = MCMONINV;
+}
+
+/* internal service routine to clear one set of fuses/deamons/alerters */
+static void vocdmn1clr(vocddef *dmn, uint cnt)
+{
+    for ( ; cnt ; --cnt, ++dmn) dmn->vocdfn = MCMONINV;
+}
+
+/* delete all fuses/daemons/alerters */
+void vocdmnclr(voccxdef *ctx)
+{
+    vocdmn1clr(ctx->voccxfus, ctx->voccxfuc);
+    vocdmn1clr(ctx->voccxdmn, ctx->voccxdmc);
+    vocdmn1clr(ctx->voccxalm, ctx->voccxalc);
+}
+
+/* save undo information for a daemon/fuse/notifier */
+static void vocdusav(voccxdef *ctx, vocddef *what)
+{
+    uchar     *p;
+    objucxdef *uc = ctx->voccxundo;
+    ushort     siz = sizeof(what) + sizeof(*what) + 1;
+    
+    /* if we don't need to save undo, quit now */
+    if (uc == 0 || !objuok(uc))
+        return;
+
+    /* reserve space for our record */
+    p = objures(uc, OBJUCLI, siz);
+    
+    /* set up our undo record */
+    *p = VOC_UNDO_DAEMON;
+    memcpy(p + 1, &what, (size_t)sizeof(what));
+    memcpy(p + 1 + sizeof(what), what, (size_t)sizeof(*what));
+    
+    uc->objucxhead += siz;
+}
+
+/* apply undo information for a daemon/fuse/notifier */
+void vocdundo(void *ctx0, uchar *data)
+{
+    voccxdef *ctx = (voccxdef *)ctx0;
+    vocddef  *daemon;
+    objnum    objn;
+    ushort    siz;
+    ushort    wrdsiz;
+    uchar    *p;
+    int       sccnt;
+    objnum    sc;
+    int       len1, len2;
+    prpnum    prp;
+    int       flags;
+    uchar    *wrd;
+
+    switch(*data)
+    {
+    case VOC_UNDO_DAEMON:
+        memcpy(&daemon, data + 1, (size_t)sizeof(daemon));
+        memcpy(daemon, data + 1 + sizeof(daemon), (size_t)sizeof(*daemon));
+        break;
+
+    case VOC_UNDO_NEWOBJ:
+        /* get the object number */
+        objn = osrp2(data + 1);
+
+        /* delete the object's inheritance and vocabulary records */
+        vocdel(ctx, objn);
+        vocidel(ctx, objn);
+
+        /* delete the object */
+        mcmfre(ctx->voccxmem, (mcmon)objn);
+        break;
+
+    case VOC_UNDO_DELOBJ:
+        /* get the object's number and size */
+        objn = osrp2(data + 1);
+        siz = osrp2(data + 3);
+        wrdsiz = osrp2(data + 5);
+
+        /* allocate the object with its original number */
+        p = mcmalonum(ctx->voccxmem, siz, (mcmon)objn);
+
+        /* copy the contents back to the object */
+        memcpy(p, data + 7, (size_t)siz);
+
+        /* get its superclass if it has one */
+        sccnt = objnsc(p);
+        if (sccnt) sc = osrp2(objsc(p));
+
+        /* unlock the object, and create its inheritance records */
+        mcmunlck(ctx->voccxmem, (mcmon)objn);
+        vociadd(ctx, objn, MCMONINV, sccnt, &sc, VOCIFNEW | VOCIFVOC);
+
+        /* restore the words as well */
+        data += 7 + siz;
+        while (wrdsiz)
+        {
+            /* get the lengths from the buffer */
+            len1 = osrp2(data + 2);
+            len2 = osrp2(data + 4);
+
+            /* add the word */
+            vocadd2(ctx, data[0], objn, data[1], data+6, len1,
+                    data+6+len1, len2);
+            
+            /* remove this object from the word size */
+            wrdsiz -= 6 + len1 + len2;
+            data += 6 + len1 + len2;
+        }
+        break;
+
+    case VOC_UNDO_ADDVOC:
+    case VOC_UNDO_DELVOC:
+        flags = *(data + 1);
+        prp = *(data + 2);
+        objn = osrp2(data + 3);
+        wrd = data + 5;
+        if (*data == VOC_UNDO_ADDVOC)
+            vocdel1(ctx, objn, (char *)wrd, prp, FALSE, FALSE, FALSE);
+        else
+            vocadd(ctx, prp, objn, flags, (char *)wrd);
+        break;
+
+    case VOC_UNDO_SETME:
+        ctx->voccxme = osrp2(data + 1);
+        break;
+    }
+}
+
+/* determine size of one of our client undo records */
+ushort OS_LOADDS vocdusz(void *ctx0, uchar *data)
+{
+    VARUSED(ctx0);
+
+    switch(*data)
+    {
+    case VOC_UNDO_DAEMON:
+        /* it's the size of the structures, plus one for the header */
+        return (ushort)((sizeof(vocddef *) + sizeof(vocddef)) + 1);
+
+    case VOC_UNDO_NEWOBJ:
+        /* 2 bytes for the objnum plus 1 for the header */
+        return 2 + 1;
+
+    case VOC_UNDO_DELOBJ:
+        /*
+         *   1 (header) + 2 (objnum) + 2 (size) + 2 (word size) + object
+         *   data size + word size
+         */
+        return osrp2(data+3) + osrp2(data+5) + 1+2+2+2;
+
+    case VOC_UNDO_ADDVOC:
+    case VOC_UNDO_DELVOC:
+        /* 1 (header) + 2 (objnum) + 1 (flags) + 1 (type) + word size */
+        return osrp2(data + 5) + 5;
+
+    default:
+        return 0;
+    }
+}
+
+/* save undo for object creation */
+void vocdusave_newobj(voccxdef *ctx, objnum objn)
+{
+    objucxdef *uc = ctx->voccxundo;
+    uchar     *p;
+
+    p = objures(uc, OBJUCLI, 3);
+    *p = VOC_UNDO_NEWOBJ;
+    oswp2(p+1, objn);
+
+    uc->objucxhead += 3;
+}
+
+/* save undo information for a change in the "Me" object */
+void vocdusave_me(voccxdef *ctx, objnum old_me)
+{
+    uchar     *p;
+    objucxdef *uc = ctx->voccxundo;
+
+    /* if we don't need to save undo, there's nothing to do */
+    if (uc == 0 || !objuok(uc))
+        return;
+
+    /* reserve space for our record */
+    p = objures(uc, OBJUCLI, 3);
+    *p = VOC_UNDO_SETME;
+    oswp2(p+1, old_me);
+
+    /* absorb the space */
+    uc->objucxhead += 3;
+}
+
+/* callback context structure */
+struct delobj_cb_ctx
+{
+    uchar *p;
+};
+
+/*
+ *   Iteration callback to write vocabulary words for an object being
+ *   deleted to an undo stream, so that they can be restored if the
+ *   deletion is undone. 
+ */
+static void delobj_cb(void *ctx0, vocdef *voc, vocwdef *vocw)
+{
+    struct delobj_cb_ctx *ctx = (struct delobj_cb_ctx *)ctx0;
+    uchar *p = ctx->p;
+    
+    /* write this object's header to the stream */
+    p[0] = vocw->vocwtyp;
+    p[1] = vocw->vocwflg;
+    oswp2(p+2, voc->voclen);
+    oswp2(p+4, voc->vocln2);
+
+    /* write the words as well */
+    memcpy(p+6, voc->voctxt, (size_t)(voc->voclen + voc->vocln2));
+
+    /* advance the pointer */
+    ctx->p += 6 + voc->voclen + voc->vocln2;
+}
+
+/* save undo for object deletion */
+void vocdusave_delobj(voccxdef *ctx, objnum objn)
+{
+    objucxdef *uc = ctx->voccxundo;
+    uchar     *p;
+    uchar     *objp;
+    uint       siz;
+    int        wrdsiz;
+    int        wrdcnt;
+    struct delobj_cb_ctx fnctx;
+
+    /* figure out how much we need to save */
+    objp = mcmlck(ctx->voccxmem, (mcmon)objn);
+    siz = objfree(objp);
+
+    /* figure the word size */
+    voc_count(ctx, objn, 0, &wrdcnt, &wrdsiz);
+
+    /*
+     *   we need to store an additional 6 bytes (2-length1, 2-length2,
+     *   1-type, 1-flags) for each word 
+     */
+    wrdsiz += wrdcnt*6;
+
+    /* set up the undo header */
+    p = objures(uc, OBJUCLI, (ushort)(7 + siz + wrdsiz));
+    *p = VOC_UNDO_DELOBJ;
+    oswp2(p+1, objn);
+    oswp2(p+3, siz);
+    oswp2(p+5, wrdsiz);
+
+    /* save the object's data */
+    memcpy(p+7, objp, (size_t)siz);
+
+    /* write the words */
+    fnctx.p = p+7 + siz;
+    voc_iterate(ctx, objn, delobj_cb, &fnctx);
+
+    /* unlock the object and advance the undo pointer */
+    mcmunlck(ctx->voccxmem, (mcmon)objn);
+    uc->objucxhead += 7 + siz + wrdsiz;
+}
+
+/* save undo for word creation */
+void vocdusave_addwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags,
+                      char *wrd)
+{
+    ushort     wrdsiz;
+    uchar     *p;
+    objucxdef *uc = ctx->voccxundo;
+
+    /* figure out how much space we need, and reserve it */
+    wrdsiz = osrp2(wrd);
+    p = objures(uc, OBJUCLI, (ushort)(5 + wrdsiz));
+
+    *p = VOC_UNDO_ADDVOC;
+    *(p+1) = flags;
+    *(p+2) = (uchar)typ;
+    oswp2(p+3, objn);
+    memcpy(p+5, wrd, (size_t)wrdsiz);
+
+    uc->objucxhead += 5 + wrdsiz;
+}
+
+/* save undo for word deletion */
+void vocdusave_delwrd(voccxdef *ctx, objnum objn, prpnum typ, int flags,
+                      char *wrd)
+{
+    ushort     wrdsiz;
+    uchar     *p;
+    objucxdef *uc = ctx->voccxundo;
+
+    /* figure out how much space we need, and reserve it */
+    wrdsiz = osrp2(wrd);
+    p = objures(uc, OBJUCLI, (ushort)(5 + wrdsiz));
+
+    *p = VOC_UNDO_DELVOC;
+    *(p+1) = flags;
+    *(p+2) = (uchar)typ;
+    oswp2(p+3, objn);
+    memcpy(p+5, wrd, (size_t)wrdsiz);
+
+    uc->objucxhead += 5 + wrdsiz;
+}
+                      
+
+
+/* set a fuse/daemon/notifier */
+void vocsetfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop,
+              uint tm, runsdef *val, int err)
+{
+	int      slots = 0;
+    
+    if (what == ctx->voccxdmn)
+        slots = ctx->voccxdmc;
+    else if (what == ctx->voccxalm)
+        slots = ctx->voccxalc;
+    else if (what == ctx->voccxfus)
+        slots = ctx->voccxfuc;
+    else
+        errsig(ctx->voccxerr, ERR_BADSETF);
+    
+    /* find a free slot, and set up our fuse/daemon */
+    for ( ; slots ; ++what, --slots)
+    {
+        if (what->vocdfn == MCMONINV)
+        {
+            /* save an undo record for this slot before changing */
+            vocdusav(ctx, what);
+            
+            /* record the information */
+            what->vocdfn = func;
+            if (val != 0)
+                OSCPYSTRUCT(what->vocdarg, *val);
+            what->vocdprp = prop;
+            what->vocdtim = tm;
+
+            /* 
+             *   the fuse/notifier/daemon is set - no need to look further
+             *   for an open slot 
+             */
+            return;
+        }
+    }
+
+    /* we didn't find an open slot - signal the appropriate error */
+    errsig(ctx->voccxerr, err);
+}
+
+/* remove a fuse/daemon/notifier */
+void vocremfd(voccxdef *ctx, vocddef *what, objnum func, prpnum prop,
+              runsdef *val, int err)
+{
+	int      slots = 0;
+    
+    if (what == ctx->voccxdmn) slots = ctx->voccxdmc;
+    else if (what == ctx->voccxalm) slots = ctx->voccxalc;
+    else if (what == ctx->voccxfus) slots = ctx->voccxfuc;
+    else errsig(ctx->voccxerr, ERR_BADREMF);
+    
+    /* find the slot with this same fuse/daemon/notifier, and remove it */
+    for ( ; slots ; ++what, --slots)
+    {
+        if (what->vocdfn == func
+            && what->vocdprp == prop
+            && (!val || (val->runstyp == what->vocdarg.runstyp
+                         && !memcmp(&val->runsv, &what->vocdarg.runsv,
+                                    (size_t)datsiz(val->runstyp,
+                                                   &val->runsv)))))
+        {
+            /* save an undo record for this slot before changing */
+            vocdusav(ctx, what);
+
+            what->vocdfn = MCMONINV;
+            return;
+        }
+    }
+
+/*    errsig(ctx->voccxerr, err); <<<harmless - don't signal it>>> */
+}
+
+/*
+ *   Count one or more turns - burn all fuses down by the given number of
+ *   turns.  Execute any fuses that expire within the given interval, but
+ *   not any that expire at the end of the last turn counted here.  (If
+ *   incrementing by one turn only, no fuses will be executed.)  If the
+ *   do_fuses flag is false, fuses are simply deleted if they burn down
+ *   within the interval.  
+ */
+void vocturn(voccxdef *ctx, int turncnt, int do_fuses)
+{
+    vocddef *p;
+    int      i;
+    int      do_exe;
+
+    while (turncnt--)
+    {
+        /* presume we won't find anything to execute */
+        do_exe = FALSE;
+        
+        /* go through notifiers, looking for fuse-type notifiers */
+        for (i = ctx->voccxalc, p = ctx->voccxalm ; i ; ++p, --i)
+        {
+            if (p->vocdfn != MCMONINV
+                && p->vocdtim != VOCDTIM_EACH_TURN
+                && p->vocdtim != 0)
+            {
+                /* save an undo record for this slot before changing */
+                vocdusav(ctx, p);
+                
+                if (--(p->vocdtim) == 0)
+                    do_exe = TRUE;
+            }
+        }
+        
+        /* now go through the fuses */
+        for (i = ctx->voccxfuc, p = ctx->voccxfus ; i ; ++p, --i)
+        {
+            if (p->vocdfn != MCMONINV && p->vocdtim != 0)
+            {
+                /* save an undo record for this slot before changing */
+                vocdusav(ctx, p);
+
+                if (--(p->vocdtim) == 0)
+                    do_exe = TRUE;
+            }
+        }
+
+        /*
+         *   if we'll be doing more, and anything burned down, run
+         *   current fuses before going on to the next turn 
+         */
+        if ((!do_fuses || turncnt) && do_exe)
+            exefuse(ctx, do_fuses);
+    }
+}
+
+/*
+ *   display a default error message for a verb/dobj/iobj combo.
+ *   The message is "I don't know how to <verb.sdesc> <dobj.thedesc>" if
+ *   the dobj is present, and "I don't know how to <verb.sdesc> anything
+ *   <prep.sdesc> <iobj.thedesc>" if the iobj is present.  Such a message
+ *   is displayed when the objects in the command don't handle the verb
+ *   (i.e., don't have any methods for verification of the verb:  they
+ *   lack verDo<verb> or verIo<verb>).
+ */
+static void exeperr(voccxdef *ctx, objnum verb, objnum dobj,
+                    objnum prep, objnum iobj)
+{
+    if (ctx->voccxper2 != MCMONINV)
+    {
+        runpobj(ctx->voccxrun, iobj);
+        runpobj(ctx->voccxrun, prep);
+        runpobj(ctx->voccxrun, dobj);
+        runpobj(ctx->voccxrun, verb);
+        runfn(ctx->voccxrun, ctx->voccxper2, 4);
+        return;
+    }
+    
+    vocerr(ctx, VOCERR(110), "I don't know how to ");
+    runppr(ctx->voccxrun, verb, PRP_SDESC, 0);
+    
+    if (dobj != MCMONINV)
+    {
+        vocerr(ctx, VOCERR(111), " ");
+        runppr(ctx->voccxrun, dobj, PRP_THEDESC, 0);
+    }
+    else
+    {
+        vocerr(ctx, VOCERR(112), " anything ");
+        if (prep != MCMONINV)
+            runppr(ctx->voccxrun, prep, PRP_SDESC, 0);
+        else
+            vocerr(ctx, VOCERR(113), "to");
+        vocerr(ctx, VOCERR(114), " ");
+        runppr(ctx->voccxrun, iobj, PRP_THEDESC, 0);
+    }
+    vocerr(ctx, VOCERR(115), ".");
+}
+
+
+/*
+ *   Execute daemons 
+ */
+void exedaem(voccxdef *ctx)
+{
+    runcxdef *rcx = ctx->voccxrun;
+    vocddef  *daemon;
+    int       i;
+    runsdef   val;
+    int       err;
+
+    for (i = ctx->voccxdmc, daemon = ctx->voccxdmn ; i ; ++daemon, --i)
+    {
+        if (daemon->vocdfn != MCMONINV)
+        {
+            objnum thisd = daemon->vocdfn;
+            
+            ERRBEGIN(ctx->voccxerr)
+
+            OSCPYSTRUCT(val, daemon->vocdarg);
+            runpush(rcx, val.runstyp, &val);
+            runfn(rcx, thisd, 1);
+            
+            ERRCATCH(ctx->voccxerr, err)
+                if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
+                    errrse(ctx->voccxerr);
+            ERREND(ctx->voccxerr)
+        }
+    }
+    for (i = ctx->voccxalc, daemon = ctx->voccxalm ; i ; ++daemon, --i)
+    {
+        if (daemon->vocdfn != MCMONINV
+            && daemon->vocdtim == VOCDTIM_EACH_TURN)
+        {
+            ERRBEGIN(ctx->voccxerr)
+
+            runppr(rcx, daemon->vocdfn, daemon->vocdprp, 0);
+            
+            ERRCATCH(ctx->voccxerr, err)
+                if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
+                    errrse(ctx->voccxerr);
+            ERREND(ctx->voccxerr)
+        }
+    }
+}
+
+/*
+ *   Execute any pending fuses.  Return TRUE if any fuses were executed,
+ *   FALSE otherwise.  
+ */
+int exefuse(voccxdef *ctx, int do_run)
+{
+    runcxdef *rcx = ctx->voccxrun;
+    vocddef  *daemon;
+    int       i;
+    int       found = FALSE;
+    runsdef   val;
+    int       err;
+
+    /* first, execute any expired function-based fuses */
+    for (i = ctx->voccxfuc, daemon = ctx->voccxfus ; i ; ++daemon, --i)
+    {
+        if (daemon->vocdfn != MCMONINV && daemon->vocdtim == 0)
+        {
+            objnum thisf = daemon->vocdfn;
+         
+            found = TRUE;
+            ERRBEGIN(ctx->voccxerr)
+
+            /* save an undo record for this slot before changing */
+            vocdusav(ctx, daemon);
+
+            /* remove the fuse prior to running  */
+            daemon->vocdfn = MCMONINV;
+
+            if (do_run)
+            {
+                OSCPYSTRUCT(val, daemon->vocdarg);
+                runpush(rcx, val.runstyp, &val);
+                runfn(rcx, thisf, 1);
+            }
+            
+            ERRCATCH(ctx->voccxerr, err)
+                if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
+                    errrse(ctx->voccxerr);
+            ERREND(ctx->voccxerr)
+        }
+    }
+
+    /* next, execute any expired method-based notifier fuses */
+    for (i = ctx->voccxalc, daemon = ctx->voccxalm ; i ; ++daemon, --i)
+    {
+        if (daemon->vocdfn != MCMONINV && daemon->vocdtim == 0)
+        {
+            objnum thisa = daemon->vocdfn;
+
+            found = TRUE;
+            ERRBEGIN(ctx->voccxerr)
+
+            /* save an undo record for this slot before changing */
+            vocdusav(ctx, daemon);
+
+            /* delete it prior to running it */
+            daemon->vocdfn = MCMONINV;
+
+            if (do_run)
+                runppr(rcx, thisa, daemon->vocdprp, 0);
+            
+            ERRCATCH(ctx->voccxerr, err)
+                if (err != ERR_RUNEXIT && err != ERR_RUNEXITOBJ)
+                    errrse(ctx->voccxerr);
+            ERREND(ctx->voccxerr)
+        }
+    }
+
+    /* return true if we found any expired fuses */
+    return found;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Find the action routine template for a verb.  Fills in *tplofs with
+ *   the offset of the template property within the verb object, and fills
+ *   in actofs with the offset of the "action" property within the verb
+ *   object.  Sets *tplofs to zero if there's no template, and sets
+ *   *actofs to zero if there's no action routine.
+ */
+static void exe_get_tpl(voccxdef *ctx, objnum verb,
+                        uint *tplofs, uint *actofs)
+{
+    /* look up the new-style template first */
+    *tplofs = objgetap(ctx->voccxmem, verb, PRP_TPL2, (objnum *)0, FALSE);
+
+    /* if there's no new-style template, look up the old-style template */
+    if (*tplofs == 0)
+        *tplofs = objgetap(ctx->voccxmem, verb, PRP_TPL, (objnum *)0, FALSE);
+
+    /* also look to see if the verb has an Action method */
+    *actofs = objgetap(ctx->voccxmem, verb, PRP_ACTION, (objnum *)0, FALSE);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Execute fuses and daemons.  Returns zero on success, or ERR_ABORT if
+ *   'abort' was thrown during execution.  
+ */
+int exe_fuses_and_daemons(voccxdef *ctx, int err, int do_fuses,
+                          objnum actor, objnum verb,
+                          vocoldef *dobj_list, int dobj_cnt,
+                          objnum prep, objnum iobj)
+{
+    int err2;
+
+    /* presume no error */
+    err2 = 0;
+    
+    /* execute fuses and daemons if desired - trap any errors that occur */
+    if (do_fuses)
+    {
+        ERRBEGIN(ctx->voccxerr)
+        {
+            /* execute daemons */
+            exedaem(ctx);
+            
+            /* execute fuses */
+            (void)exefuse(ctx, TRUE);
+        }
+        ERRCATCH(ctx->voccxerr, err2)
+        {
+            /* 
+             *   if 'abort' was invoked, ignore it, since it's now had the
+             *   desired effect of skipping any remaining fuses and
+             *   daemons; resignal any other error 
+             */
+            if (err2 != ERR_RUNABRT)
+                errrse(ctx->voccxerr);
+            
+            /* replace any previous error with the new error code */
+            err = err2;
+        }
+        ERREND(ctx->voccxerr);
+    }
+
+    /* execute endCommand if it's defined */
+    if (ctx->voccxendcmd != MCMONINV)
+    {
+        /* push the arguments */
+        runpnum(ctx->voccxrun, err);
+        runpobj(ctx->voccxrun, iobj);
+        runpobj(ctx->voccxrun, prep);
+        voc_push_vocoldef_list(ctx, dobj_list, dobj_cnt);
+        runpobj(ctx->voccxrun, verb);
+        runpobj(ctx->voccxrun, actor);
+
+        /* call endCommand */
+        runfn(ctx->voccxrun, ctx->voccxendcmd, 6);
+    }
+
+    /* return the error status */
+    return err;
+}
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   execute iobjGen/dobjGen methods, if appropriate 
+ */
+static int exegen(voccxdef *ctx, objnum obj, prpnum genprop,
+                  prpnum verprop, prpnum actprop)
+{
+    int     hasgen;                                 /* has xobjGen property */
+    objnum  genobj;                         /* object with xobjGen property */
+    int     hasver;                               /* has verXoVerb property */
+    objnum  verobj;                       /* object with verXoVerb property */
+    int     hasact;                                  /* has xoVerb property */
+    objnum  actobj;                          /* object with xoVerb property */
+    
+    /* ignore it if there's no object here */
+    if (obj == MCMONINV) return(FALSE);
+
+    /* look up the xobjGen property, and ignore if not present */
+    hasgen = objgetap(ctx->voccxmem, obj, genprop, &genobj, FALSE);
+    if (!hasgen) return(FALSE);
+
+    /* look up the verXoVerb and xoVerb properties */
+    hasver = objgetap(ctx->voccxmem, obj, verprop, &verobj, FALSE);
+    hasact = objgetap(ctx->voccxmem, obj, actprop, &actobj, FALSE);
+
+    /* ignore if verXoVerb or xoVerb "overrides" xobjGen */
+    if ((hasver && !bifinh(ctx, vocinh(ctx, genobj), verobj))
+        || (hasact && !bifinh(ctx, vocinh(ctx, genobj), actobj)))
+        return FALSE;
+    
+    /* all conditions are met - execute dobjGen */
+    return TRUE;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Save "again" information for a direct or indirect object 
+ */
+static void exe_save_again_obj(vocoldef *againv, const vocoldef *objv,
+                               char **bufp)
+{
+    /* if there's an object, save it */
+    if (objv != 0)
+    {
+        /* copy the object information structure */
+        memcpy(againv, objv, sizeof(*againv));
+
+        /* copy the original command words to the "again" buffer */
+        if (objv->vocolfst != 0 && objv->vocollst != 0)
+        {
+            size_t copylen;
+            
+            /* 
+             *   Compute the length of the entire list.  The words are
+             *   arranged consecutively in the buffer, separated by null
+             *   bytes, so we must copy everything from the first word to
+             *   the start of the last word, plus the length of the last
+             *   word, plus the last word's trailing null byte.  
+             */
+            copylen = objv->vocollst - objv->vocolfst
+                      + strlen(objv->vocollst) + 1;
+
+            /* copy the text */
+            memcpy(*bufp, objv->vocolfst, copylen);
+
+            /* 
+             *   set the new structure to point into the copy, not the
+             *   original 
+             */
+            againv->vocolfst = *bufp;
+            againv->vocollst = *bufp + (objv->vocollst - objv->vocolfst);
+
+            /* skip past the space we've consumed in the buffer */
+            *bufp += copylen;
+        }
+    }
+    else
+    {
+        /* there's nothing to save - just set the object ID to invalid */
+        againv->vocolobj = MCMONINV;
+    }
+}
+
+/*
+ *   Restore an "again" object previously saved.  Note that we must copy
+ *   the saved data to our 2-element arrays so that we can insert a
+ *   terminating element after each restored element - other code
+ *   occasionally expects these structures to be stored in the standard
+ *   object list array format.  Returns a pointer to the restored object
+ *   list, which is the same as the first argument.  
+ */
+static vocoldef *exe_restore_again_obj(vocoldef again_array[2],
+                                       const vocoldef *saved_obj)
+{
+    /* copy the saved object into the first array element */
+    memcpy(&again_array[0], saved_obj, sizeof(again_array[0]));
+
+    /* clear the second element to indicate the end of the object list */
+    again_array[1].vocolobj = MCMONINV;
+    again_array[1].vocolflg = 0;
+
+    /* return a pointer to the first array element */
+    return &again_array[0];
+}
+
+/* ------------------------------------------------------------------------ */
+/* 
+ *   Execute a single command.  'recursive' indicates whether the routine
+ *   is being called for normal command processing or as a recursive call
+ *   from within the game; if this flag is true, we'll bypass certain
+ *   operations that are only appropriate for normal direct player
+ *   commands: we won't remember the command for "again" processing, we
+ *   won't do end-of-turn processing, and we won't reset the system stack
+ *   before each function invocation.  
+ */
+static int exe1cmd(voccxdef *ctx, objnum actor, objnum verb, vocoldef *dobjv,
+                   objnum *prepptr, vocoldef *iobjv, int endturn, uchar *tpl,
+                   int newstyle, int recursive,
+                   int validate_dobj, int validate_iobj,
+                   vocoldef *dobj_list, int cur_dobj_idx, int dobj_cnt,
+                   int show_multi_prefix, int multi_flags)
+{
+    objnum    loc;
+    int       err;
+    runcxdef *rcx = ctx->voccxrun;
+    objnum    prep = *prepptr;
+    objnum    dobj = (dobjv != 0 ? dobjv->vocolobj : MCMONINV);
+    objnum    iobj = (iobjv != 0 ? iobjv->vocolobj : MCMONINV);
+    int       tplflags;
+    int       dobj_first;
+    objnum    old_tio_actor;
+    vocoldef *old_ctx_dobj;
+    vocoldef *old_ctx_iobj;
+    objnum    old_verb;
+    objnum    old_actor;
+    objnum    old_prep;
+    int       do_fuses;
+    int       do_postact;
+    vocoldef  again_dobj[2];
+    vocoldef  again_iobj[2];
+
+    /* presume no error will occur */
+    err = 0;
+
+    /* 
+     *   Presume we'll run fuses and daemons if this is the end of the
+     *   turn.  We only do fuses and daemons once per command, even if the
+     *   command contains multiple objects; 'endturn' will be true only
+     *   when this is the last object of the command.  
+     */
+    do_fuses = endturn;
+
+    /* presume we will call postAction */
+    do_postact = TRUE;
+
+    /* remember the original tio-level actor setting */
+    old_tio_actor = tiogetactor(ctx->voccxtio);
+
+    /* remember the original command settings (in case this is recursive) */
+    old_actor = ctx->voccxactor;
+    old_verb = ctx->voccxverb;
+    old_prep = ctx->voccxprep;
+    old_ctx_dobj = ctx->voccxdobj;
+    old_ctx_iobj = ctx->voccxiobj;
+
+    /* the default actor is Me */
+    if (actor == MCMONINV)
+        actor = ctx->voccxme;
+
+    /* if command is "again", get information from previous command */
+    if (verb == ctx->voccxvag)
+    {
+        /* it's "again" - repeat the last command */
+        actor = ctx->voccxlsa;
+        verb  = ctx->voccxlsv;
+        dobj  = ctx->voccxlsd.vocolobj;
+        iobj  = ctx->voccxlsi.vocolobj;
+        prep  = ctx->voccxlsp;
+        tpl   = ctx->voccxlst;
+        newstyle = ctx->voccxlssty;
+
+        /* 
+         *   If we have a direct or indirect object, restore the full
+         *   object information structure pointers (in particular, this
+         *   restores the word lists).
+         */
+        if (dobj != MCMONINV)
+            dobjv = exe_restore_again_obj(again_dobj, &ctx->voccxlsd);
+        if (iobj != MCMONINV)
+            iobjv = exe_restore_again_obj(again_iobj, &ctx->voccxlsi);
+
+        /*
+         *   make sure the command is repeatable: there must have been a
+         *   verb, and the objects specified must still be accessible 
+         */
+        if (verb == MCMONINV)
+        {
+            /* 
+             *   if the last command was lost due to an object deletion,
+             *   show the message "you can't repeat that command";
+             *   otherwise, show the message "there's no command to
+             *   repeat" 
+             */
+            if ((ctx->voccxflg & VOCCXAGAINDEL) != 0)
+                vocerr(ctx, VOCERR(27), "You can't repeat that command.");
+            else
+                vocerr(ctx, VOCERR(26), "There's no command to repeat.");
+
+            /* flush the output and return failure */
+            tioflush(ctx->voccxtio);
+            return 0;
+        }
+        else if ((dobj != MCMONINV &&
+                  !vocchkaccess(ctx, dobj, PRP_VALIDDO, 0, actor, verb))
+                 || (iobj != MCMONINV &&
+                     !vocchkaccess(ctx, iobj, PRP_VALIDIO, 0, actor, verb))
+                 || !vocchkaccess(ctx, actor, PRP_VALIDACTOR, 0, actor, verb))
+        {
+            vocerr(ctx, VOCERR(27), "You can't repeat that command.");
+            tioflush(ctx->voccxtio);
+            return(0);
+        }
+    }
+    else
+    {
+        /* verify the direct object if present */
+        if (validate_dobj
+            && dobj != MCMONINV
+            && !vocchkaccess(ctx, dobj, PRP_VALIDDO, 0, actor, verb))
+        {
+            /* generate an appropriate message */
+            if (vocchkvis(ctx, dobj, actor))
+            {
+                /* it's visible but not accessible */
+                vocnoreach(ctx, &dobj, 1, actor, verb, prep,
+                           PRP_DODEFAULT, FALSE, 0, 0, 1);
+            }
+            else
+            {
+                /* it's not even visible */
+                if (recursive)
+                    vocerr(ctx, VOCERR(39), "You don't see that here.");
+                else
+                    vocerr(ctx, VOCERR(38),
+                           "You don't see that here any more.");
+            }
+            
+            /* indicate the error */
+            return ERR_PRS_VAL_DO_FAIL;
+        }
+        
+        /* verify the indirect object if present */
+        if (validate_iobj
+            && iobj != MCMONINV
+            && !vocchkaccess(ctx, iobj, PRP_VALIDIO, 0, actor, verb))
+        {
+            /* generate the error message */
+            if (vocchkvis(ctx, iobj, actor))
+            {
+                /* it's visible but not accessible */
+                vocnoreach(ctx, &iobj, 1, actor, verb, prep,
+                           PRP_IODEFAULT, FALSE, 0, 0, 1);
+            }
+            else
+            {
+                /* it's not even visible */
+                if (recursive)
+                    vocerr(ctx, VOCERR(39), "You don't see that here.");
+                else
+                    vocerr(ctx, VOCERR(38),
+                           "You don't see that here any more.");
+            }
+            
+            /* indicate the error */
+            return ERR_PRS_VAL_IO_FAIL;
+        }
+
+        /* 
+         *   save the command, unless this is a recursive call from the
+         *   game, so that we can repeat this command if the next is
+         *   "again" 
+         */
+        if (!recursive)
+        {
+            char *dst;
+            
+            /* save the command parameters */
+            ctx->voccxlsa = actor;
+            ctx->voccxlsv = verb;
+            ctx->voccxlsp = prep;
+            ctx->voccxlssty = newstyle;
+            if (tpl != 0)
+                memcpy(ctx->voccxlst, tpl,
+                       (size_t)(newstyle ? VOCTPL2SIZ : VOCTPLSIZ));
+
+            /* set up to write into the "again" word buffer */
+            dst = ctx->voccxagainbuf;
+
+            /* save the direct object information */
+            exe_save_again_obj(&ctx->voccxlsd, dobjv, &dst);
+
+            /* save the indirect object information */
+            exe_save_again_obj(&ctx->voccxlsi, iobjv, &dst);
+
+            /* 
+             *   clear the flag indicating that "again" was lost due to
+             *   object deletion, because we obviously have a valid
+             *   "again" at this point 
+             */
+            ctx->voccxflg &= ~VOCCXAGAINDEL;
+        }
+    }
+    
+    /* remember the flags */
+    tplflags = (tpl != 0 && newstyle ? voctplflg(tpl) : 0);
+    dobj_first = (tplflags & VOCTPLFLG_DOBJ_FIRST);
+
+    /* set up actor for tio subsystem - format strings need to know */
+    tiosetactor(ctx->voccxtio, actor);
+
+    /* store current dobj and iobj vocoldef's for later reference */
+    ctx->voccxdobj = dobjv;
+    ctx->voccxiobj = iobjv;
+
+    /* store the rest of the current command objects for reference */
+    ctx->voccxactor = actor;
+    ctx->voccxverb = verb;
+    ctx->voccxprep = prep;
+    
+    ERRBEGIN(ctx->voccxerr)
+
+    /* reset the run-time context if this is a top-level call */
+    if (!recursive)
+        runrst(rcx);
+
+    /* 
+     *   if this is the first object, invoke the game's preCommand
+     *   function, passing the list of all of the direct objects 
+     */
+    if (cur_dobj_idx == 0 && ctx->voccxprecmd != MCMONINV)
+    {
+        /* push the arguments: actor, verb, dobj-list, prep, iobj */
+        runpobj(rcx, iobj);
+        runpobj(rcx, prep);
+        voc_push_vocoldef_list(ctx, dobj_list, dobj_cnt);
+        runpobj(rcx, verb);
+        runpobj(rcx, actor);
+
+        /* catch errors specially for preCommand */
+//        ERRBEGIN(ctx->voccxerr)
+//        {
+            /* invoke preCommand */
+            runfn(rcx, ctx->voccxprecmd, 5);
+//        }
+//        ERRCATCH(ctx->voccxerr, err)
+//        {
+            /* 
+             *   if the error was 'exit', translate it to EXITPRECMD so
+             *   that we handle the outer loop correctly (exiting from
+             *   preCommand skips execution for all subsequent objects,
+             *   but doesn't skip fuses and daemons) 
+             */
+            if (err == ERR_RUNEXIT)
+                errsig(ctx->voccxerr, ERR_RUNEXITPRECMD);
+
+            /* no special handling - just resignal the error */
+            errrse(ctx->voccxerr);
+//        }
+//        ERREND(ctx->voccxerr);
+    }
+
+    /* show the pre-object prefix if the caller instructed us to do so */
+    voc_multi_prefix(ctx, dobj, show_multi_prefix, multi_flags,
+                     cur_dobj_idx, dobj_cnt);
+
+    /* 
+     *   check to see if the verb has verbAction defined - if so, invoke
+     *   the method
+     */
+    if (objgetap(ctx->voccxmem, verb, PRP_VERBACTION, (objnum *)0, FALSE))
+    {
+        /* call verb.verbAction(actor, dobj, prep, iobj) */
+        runpobj(rcx, iobj);
+        runpobj(rcx, prep);
+        runpobj(rcx, dobj);
+        runpobj(rcx, actor);
+        runppr(rcx, verb, PRP_VERBACTION, 4);
+    }
+
+    /* invoke cmdActor.actorAction(verb, dobj, prep, iobj) */
+    runpobj(rcx, iobj);
+    runpobj(rcx, prep);
+    runpobj(rcx, dobj);
+    runpobj(rcx, verb);
+    runppr(rcx, actor, PRP_ACTORACTION, 4);
+
+    /* reset the run-time context if this is a top-level call */
+    if (!recursive)
+        runrst(rcx);
+
+    /* invoke actor.location.roomAction(actor, verb, dobj, prep, iobj) */
+    runppr(rcx, actor, PRP_LOCATION, 0);
+    if (runtostyp(rcx) == DAT_OBJECT)
+    {
+        loc = runpopobj(rcx);
+
+        /* reset the run-time context if this is a top-level call */
+        if (!recursive)
+            runrst(rcx);
+
+        /* call roomAction */
+        runpobj(rcx, iobj);
+        runpobj(rcx, prep);
+        runpobj(rcx, dobj);
+        runpobj(rcx, verb);
+        runpobj(rcx, actor);
+        runppr(rcx, loc, PRP_ROOMACTION, 5);
+    }
+    else
+    {
+        /* the location isn't an object, so discard it */
+        rundisc(rcx);
+    }
+    
+    /* if there's an indirect object, execute iobjCheck */
+    if (iobj != MCMONINV)
+    {
+        /* reset the run-time context if this is a top-level call */
+        if (!recursive)
+            runrst(rcx);
+
+        /* invoke iobjCheck */
+        runpobj(rcx, prep);
+        runpobj(rcx, dobj);
+        runpobj(rcx, verb);
+        runpobj(rcx, actor);
+        runppr(rcx, iobj, PRP_IOBJCHECK, 4);
+    }
+
+    /*
+     *   If there's an indirect object, and the indirect object doesn't
+     *   directly define io<Verb>, call iobj.iobjGen(actor, verb, dobj,
+     *   prep) 
+     */
+    if (iobj != MCMONINV
+        && exegen(ctx, iobj, PRP_IOBJGEN, voctplvi(tpl), voctplio(tpl)))
+    {
+        /* reset the run-time context if this is a top-level call */
+        if (!recursive)
+            runrst(rcx);
+
+        /* invoke iobjGen */
+        runpobj(rcx, prep);
+        runpobj(rcx, dobj);
+        runpobj(rcx, verb);
+        runpobj(rcx, actor);
+        runppr(rcx, iobj, PRP_IOBJGEN, 4);
+    }
+
+    /* if there's an direct object, execute dobjCheck */
+    if (dobj != MCMONINV)
+    {
+        /* reset the run-time context if this is a top-level call */
+        if (!recursive)
+            runrst(rcx);
+
+        /* invoke dobjCheck */
+        runpobj(rcx, prep);
+        runpobj(rcx, iobj);
+        runpobj(rcx, verb);
+        runpobj(rcx, actor);
+        runppr(rcx, dobj, PRP_DOBJCHECK, 4);
+    }
+
+    /* 
+     *   If there's a direct object, and the direct object doesn't
+     *   directly define do<Verb>, call dobj.dobjGen(actor, verb, iobj,
+     *   prep) 
+     */
+    if (dobj != MCMONINV
+        && exegen(ctx, dobj, PRP_DOBJGEN, voctplvd(tpl), voctpldo(tpl)))
+    {
+        /* reset the run-time context if this is a top-level call */
+        if (!recursive)
+            runrst(rcx);
+
+        /* invoke dobjGen */
+        runpobj(rcx, prep);
+        runpobj(rcx, iobj);
+        runpobj(rcx, verb);
+        runpobj(rcx, actor);
+        runppr(rcx, dobj, PRP_DOBJGEN, 4);
+    }
+
+    /* reset the hidden-text flag */
+    tiohide(ctx->voccxtio);
+    tioshow(ctx->voccxtio);
+
+    /*
+     *   Now do what needs to be done, depending on the sentence structure:
+     *   
+     *      No objects ==> cmdVerb.action( cmdActor )
+     *   
+     *      Direct object only ==> cmdDobj.verDo<Verb>( actor )
+     *.                            cmdDobj.do<Verb>( actor )
+     *   
+     *      Indirect + direct ==> cmdDobj.verDo<Verb>( actor, cmdIobj )
+     *.                           cmdIobj.verIo<Verb>( actor, cmdDobj )
+     *.                           cmdIobj.io<Verb>( actor, cmdDobj ) 
+     */
+    if (dobj == MCMONINV)
+    {
+        /* reset the stack for top-level calls */
+        if (!recursive)
+            runrst(rcx);
+
+        /* invoke verb.action */
+        runpobj(rcx, actor);
+        runppr(rcx, verb, PRP_ACTION, 1);
+    }
+    else if (iobj == MCMONINV)
+    {
+        if (!objgetap(ctx->voccxmem, dobj, voctplvd(tpl), (objnum *)0, FALSE))
+        {
+            /* display the error */
+            exeperr(ctx, verb, dobj, MCMONINV, MCMONINV);
+
+            /* note that verDoVerb failed */
+            err = ERR_PRS_NO_VERDO;
+
+            /* we're done with this command */
+            goto skipToFuses;
+        }
+        
+        /* reset the stack for top-level calls */
+        if (!recursive)
+            runrst(rcx);
+
+        /* invoke dobj.verDoVerb */
+        runpobj(rcx, actor);
+        runppr(rcx, dobj, voctplvd(tpl), 1);
+
+        /* check for an error message from verDoVerb */
+        if (!tioshow(ctx->voccxtio))
+        {
+            /* reset the stack for top-level calls */
+            if (!recursive)
+                runrst(rcx);
+
+            /* dobj.verDoVerb displayed no output - process dobj.doVerb */
+            runpobj(rcx, actor);
+            runppr(rcx, dobj, voctpldo(tpl), 1);
+        }
+        else
+        {
+            /* note that verDoVerb failed */
+            err = ERR_PRS_VERDO_FAIL;
+        }
+    }
+    else
+    {
+        /* check to see if the verDoVerb and verIoVerb methods exist */
+        if (!objgetap(ctx->voccxmem, dobj, voctplvd(tpl), (objnum *)0, FALSE))
+        {
+            /* no verDoVerb method - show a default message */
+            exeperr(ctx, verb, dobj, MCMONINV, MCMONINV);
+
+            /* note the error */
+            err = ERR_PRS_NO_VERDO;
+
+            /* skip to the end of the turn */
+            goto skipToFuses;
+        }
+        else if (!objgetap(ctx->voccxmem, iobj, voctplvi(tpl), (objnum *)0,
+                           FALSE))
+        {
+            /* no verIoVerb method - show a default mesage */
+            exeperr(ctx, verb, MCMONINV, prep, iobj);
+
+            /* note the error */
+            err = ERR_PRS_NO_VERIO;
+
+            /* skip to the end of the turn */
+            goto skipToFuses;
+        }
+
+        /* reset the stack for top-level calls */
+        if (!recursive)
+            runrst(rcx);
+
+        /* call verDoVerb(actor [,iobj]) */
+        if (!dobj_first)
+            runpobj(rcx, iobj);
+        runpobj(rcx, actor);
+        runppr(rcx, dobj, voctplvd(tpl), (dobj_first ? 1 : 2));
+
+        /* check for error output from verDoVerb */
+        if (!tioshow(ctx->voccxtio))
+        {
+            /* reset the stack for top-level calls */
+            if (!recursive)
+                runrst(rcx);
+
+            /* no error from verDoVerb - call verIoVerb(actor [,dobj]) */
+            if (dobj_first)
+                runpobj(rcx, dobj);
+            runpobj(rcx, actor);
+            runppr(rcx, iobj, voctplvi(tpl), (dobj_first ? 2 : 1));
+
+            /* check for error output from verIoVerb */
+            if (!tioshow(ctx->voccxtio))
+            {
+                /* reset the stack for top-level calls */
+                if (!recursive)
+                    runrst(rcx);
+                
+                /* no error from verDoVerb or verIoVerb - call ioVerb */
+                runpobj(rcx, dobj);
+                runpobj(rcx, actor);
+                runppr(rcx, iobj, voctplio(tpl), 2);
+            }
+            else
+            {
+                /* note the error */
+                err = ERR_PRS_VERIO_FAIL;
+            }
+        }
+        else
+        {
+            /* note the error */
+            err = ERR_PRS_VERDO_FAIL;
+        }
+    }
+    
+  skipToFuses:
+    ERRCATCH(ctx->voccxerr, err)
+    {
+        /* if askIo was invoked, get the preposition from the error stack */
+        if (err == ERR_RUNASKI)
+            *prepptr = errargint(0);
+
+        /*
+         *   If we executed 'abort', we'll skip straight to endCommand.
+         *   
+         *   If we executed askDo or askIo, we won't execute anything
+         *   more, because the command is being interrupted.
+         *   
+         *   If 'exit' or 'exitobj' was executed, proceed through
+         *   postAction and subsequent steps.
+         *   
+         *   If any error occurred other than 'exit' or 'exitobj' being
+         *   invoked, resignal the error.
+         *   
+         *   We don't need to do anything more at this point if 'exit' was
+         *   invoked, because 'exit' merely skips to the end-of-turn
+         *   phase, which is where we'll go next from here.
+         *   
+         *   If 'exitobj' was invoked, we don't want to return an error at
+         *   all, since we just want to skip the remainder of the normal
+         *   processing for the current object and proceed to the next
+         *   object (in a command with multiple direct objects).  
+         */
+        if (err == ERR_RUNABRT)
+        {
+            /* 
+             *   aborting - we're going to call postAction, but we're not
+             *   going to execute fuses and daemons 
+             */
+            do_fuses = FALSE;
+            endturn = TRUE;
+        }
+        else if (err == ERR_RUNASKD || err == ERR_RUNASKI)
+        {
+            /* we're going to skip all end-of-turn action */
+            do_fuses = FALSE;
+            do_postact = FALSE;
+            endturn = FALSE;
+        }
+        else if (err == ERR_RUNEXIT)
+        {
+            /* 
+             *   Proceed with the remainder of the processing for this
+             *   turn, but retain the error code to return to our caller,
+             *   so they know that the rest of the turn is to be skipped.
+             *   
+             *   In addition, set 'do_fuses' to true, since we want to go
+             *   directly to the fuse and daemon processing for this turn,
+             *   regardless of whether any other objects are present
+             *   (because we'll skip any remaining objects).  
+             */
+            endturn = TRUE;
+            do_fuses = TRUE;
+        }
+        else if (err == ERR_RUNEXITPRECMD)
+        {
+            /* 
+             *   exited from preCommand - end the turn, but do not run the
+             *   postAction routine 
+             */
+            do_fuses = TRUE;
+            do_postact = FALSE;
+            endturn = TRUE;
+        }
+        else if (err == ERR_RUNEXITOBJ)
+        {
+            /* 
+             *   Proceed with the remainder of processing for this turn -
+             *   we want to proceed to the next object, if any, and
+             *   process it as normal.  We don't need to update 'endturn'
+             *   or 'do_fuses', since we want to do all of those in the
+             *   normal fashion.  
+             */
+        }
+        else
+        {
+            /*
+             *   We can't handle any other errors.  Restore the enclosing
+             *   command context, and resignal the error.  
+             */
+
+            /* restore the previous tio actor setting */
+            tiosetactor(ctx->voccxtio, old_tio_actor);
+
+            /* restore the original context iobj and dobj settings */
+            ctx->voccxdobj = old_ctx_dobj;
+            ctx->voccxiobj = old_ctx_iobj;
+
+            /* restore the original context command objects */
+            ctx->voccxactor = old_actor;
+            ctx->voccxverb = old_verb;
+            ctx->voccxprep = old_prep;
+
+            /* resignal the error */
+            errrse(ctx->voccxerr);
+        }
+    }
+    ERREND(ctx->voccxerr);
+
+    /*
+     *   If desired, call postAction(actor, verb, dobj, prep, iobj,
+     *   error_status).  
+     */
+    if (do_postact && ctx->voccxpostact != MCMONINV)
+    {
+        int err2;
+        
+        ERRBEGIN(ctx->voccxerr)
+        {
+            /* push the arguments */
+            runpnum(rcx, err);
+            runpobj(rcx, iobj);
+            runpobj(rcx, prep);
+            runpobj(rcx, dobj);
+            runpobj(rcx, verb);
+            runpobj(rcx, actor);
+
+            /* invoke postAction */
+            runfn(rcx, ctx->voccxpostact, 6);
+        }
+        ERRCATCH(ctx->voccxerr, err2)
+        {
+            /* remember the new error condition */
+            err = err2;
+
+            /* if we're aborting, skip fuses and daemons */
+            if (err == ERR_RUNABRT)
+            {
+                endturn = TRUE;
+                do_fuses = FALSE;
+            }
+        }
+        ERREND(ctx->voccxerr);
+    }
+    
+    /* restore the original context iobj and dobj settings */
+    ctx->voccxdobj = old_ctx_dobj;
+    ctx->voccxiobj = old_ctx_iobj;
+
+    /* restore the original context command objects */
+    ctx->voccxverb = old_verb;
+    ctx->voccxprep = old_prep;
+
+    /* reset the stack for top-level calls */
+    if (!recursive)
+        runrst(rcx);
+
+    /* 
+     *   If this is the end of the turn, execute fuses and daemons.  Skip
+     *   fuses on recursive calls, since we want to count them as part of
+     *   the enclosing turn.  
+     */
+    if (endturn && !recursive)
+    {
+        /* catch errors so that we can restore the actor globals */
+        ERRBEGIN(ctx->voccxerr)
+        {
+            /* run fuses, daemons, and endCommand */
+            err = exe_fuses_and_daemons(ctx, err, do_fuses, actor, verb,
+                                        dobj_list, dobj_cnt, prep, iobj);
+        }
+        ERRCLEAN(ctx->voccxerr)
+        {
+            /* restore the previous actor globals */
+            ctx->voccxactor = old_actor;
+            tiosetactor(ctx->voccxtio, old_tio_actor);
+        }
+        ERRENDCLN(ctx->voccxerr);
+    }
+
+    /* restore the previous actor globals */
+    ctx->voccxactor = old_actor;
+    tiosetactor(ctx->voccxtio, old_tio_actor);
+
+    /* success */
+    return err;
+}
+
+
+/*
+ *   saveit stores the current direct object list in 'it' or 'them'.
+ */
+static void exesaveit(voccxdef *ctx, vocoldef *dolist)
+{
+    int       cnt;
+    int       i;
+    int       dbg = ctx->voccxflg & VOCCXFDBG;
+    runcxdef *rcx = ctx->voccxrun;
+
+    cnt = voclistlen(dolist);
+    if (cnt == 1)
+    {
+        /*
+         *   check to make sure they're not referring to a number or a
+         *   string; if so, it doesn't make any sense to save it 
+         */
+        if (dolist[0].vocolflg == VOCS_STR
+            || dolist[0].vocolflg == VOCS_NUM)
+        {
+            /* 
+             *   As of 2.5.11, don't clear 'it' on a number or string;
+             *   rather, just leave it as it was from the prior command.
+             *   Players will almost never expect a number or string to have
+             *   anything to do with pronoun antecedents, and in fact some
+             *   players reported finding it confusing to have the antecedant
+             *   implied by the second-most-recent command disappear when the
+             *   most recent command used a number of string.  
+             */
+#if 0
+            /* save a nil 'it' */
+            ctx->voccxit = MCMONINV;
+            if (dbg)
+                tioputs(ctx->voccxtio,
+                        ".. setting 'it' to nil (strObj/numObj)\\n");
+#endif
+
+            /* we're done */
+            return;
+        }
+
+        /* save 'it' */
+        ctx->voccxit = dolist[0].vocolobj;
+        ctx->voccxthc = 0;
+
+        if (dbg)
+        {
+            tioputs(ctx->voccxtio, ".. setting it: ");
+            runppr(rcx, ctx->voccxit, PRP_SDESC, 0);
+            tioputs(ctx->voccxtio, "\\n");
+        }
+
+        /* set "him" if appropriate */
+        runppr(rcx, ctx->voccxit, PRP_ISHIM, 0);
+        if (runtostyp(rcx) == DAT_TRUE)
+        {
+            ctx->voccxhim = ctx->voccxit;
+            if (dbg)
+                tioputs(ctx->voccxtio,
+                        "... [setting \"him\" to same object]\\n");
+        }
+        rundisc(rcx);
+
+        /* set "her" if appropriate */
+        runppr(rcx, ctx->voccxit, PRP_ISHER, 0);
+        if (runtostyp(rcx) == DAT_TRUE)
+        {
+            ctx->voccxher = ctx->voccxit;
+            if (dbg)
+                tioputs(ctx->voccxtio,
+                        "... [setting \"her\" to same object]\\n");
+        }
+        rundisc(rcx);
+    }
+    else if (cnt > 1)
+    {
+        ctx->voccxthc = cnt;
+        ctx->voccxit  = MCMONINV;
+        if (dbg) tioputs(ctx->voccxtio, ".. setting \"them\": [");
+        for (i = 0 ; i < cnt ; ++i)
+        {
+            ctx->voccxthm[i] = dolist[i].vocolobj;
+            if (dbg)
+            {
+				static char *STR1 = ", ";
+				static char *STR2 = "]\\n";
+
+                runppr(rcx, dolist[i].vocolobj, PRP_SDESC, 0);
+                tioputs(ctx->voccxtio, i+1 < cnt ? STR1 : STR2);
+            }
+        }
+    }
+}
+
+/* display a multiple-object prefix */
+void voc_multi_prefix(voccxdef *ctx, objnum objn,
+                      int show_prefix, int multi_flags,
+                      int cur_index, int count)
+{
+    runcxdef *rcx = ctx->voccxrun;
+
+    /* if the object is invalid, ignore it */
+    if (objn == MCMONINV)
+        return;
+
+    /* 
+     *   if there's a prefixdesc method defined, call it rather than the
+     *   older multisdesc (or even older sdesc) approach 
+     */
+    if (objgetap(ctx->voccxmem, objn, PRP_PREFIXDESC,
+                 (objnum *)0, FALSE) != 0)
+    {
+        runsdef val;
+
+        /* push the word flags */
+        runpnum(rcx, multi_flags);
+
+        /* 
+         *   push the object count and the current index (adjusted to a
+         *   1-based value) 
+         */
+        runpnum(rcx, count);
+        runpnum(rcx, cur_index + 1);
+        
+        /* push the 'show' flag */
+        val.runstyp = runclog(show_prefix);
+        runpush(rcx, val.runstyp, &val);
+
+        /* call the method */
+        runppr(rcx, objn, PRP_PREFIXDESC, 4);
+
+        /* we're done */
+        return;
+    }
+
+    /* 
+     *   if we're not showing the prefix, don't use the multisdesc/sdesc
+     *   display 
+     */
+    if (!show_prefix)
+        return;
+    
+    /*
+     *   use multisdesc if defined (for compatibility with older games,
+     *   use sdesc if multisdesc doesn't exist for this object) 
+     */
+    if (objgetap(ctx->voccxmem, objn, PRP_MULTISDESC,
+                 (objnum *)0, FALSE) == 0)
+    {
+        /* there's no multisdesc defined - use the plain sdesc */
+        runppr(rcx, objn, PRP_SDESC, 0);
+    }
+    else
+    {
+        /* multisdesc is defined - use it */
+        runppr(rcx, objn, PRP_MULTISDESC, 0);
+    }
+
+    /* show the colon */
+    vocerr_info(ctx, VOCERR(120), ": ");
+}
+
+/* execute command for each object in direct object list */
+static int exeloop(voccxdef *ctx, objnum actor, objnum verb,
+                   vocoldef *dolist, objnum *prep, vocoldef *iobj,
+                   int multi_flags, uchar *tpl, int newstyle)
+{
+    runcxdef *rcx = ctx->voccxrun;
+    int       err;
+    int       i;
+    int       dobj_cnt;
+    int       exec_cnt;
+    vocoldef *dobj;
+
+    /* 
+     *   count the direct objects; we'll iterate over the direct objects,
+     *   so we execute the command once per direct object 
+     */
+    exec_cnt = dobj_cnt = (dolist != 0 ? voclistlen(dolist) : 0);
+
+    /* 
+     *   if there are no direct objects, we still must execute the command
+     *   once 
+     */
+    if (exec_cnt < 1)
+        exec_cnt = 1;
+
+    /*
+     *   If we have multiple direct objects, or we're using "all" with
+     *   just one direct object, check with the verb to see if multiple
+     *   words are acceptable: call verb.rejectMultiDobj, and see what it
+     *   returns; if it returns true, don't allow multiple words, and
+     *   expect that rejectMultiDobj displayed an error message.
+     *   Otherwise, proceed.  
+     */
+    if (((multi_flags & VOCS_ALL) != 0 || dobj_cnt > 1)
+        && dolist && dolist[0].vocolobj != MCMONINV)
+    {
+        int typ;
+
+        ERRBEGIN(ctx->voccxerr)
+            runrst(rcx);
+            if (!prep || *prep == MCMONINV)
+                runpnil(rcx);
+            else
+                runpobj(rcx, *prep);
+            runppr(rcx, verb, PRP_REJECTMDO, 1);
+            typ = runtostyp(rcx);
+            rundisc(rcx);
+        ERRCATCH(ctx->voccxerr, err)
+            if (err == ERR_RUNEXIT || err == ERR_RUNEXITOBJ
+                || err == ERR_RUNABRT)
+                return err;
+            else
+                errrse(ctx->voccxerr);
+        ERREND(ctx->voccxerr)
+
+        /* if they returned 'true', don't bother continuing */
+        if (typ == DAT_TRUE)
+            return 0;
+    }
+
+    /* 
+     *   execute the command the required number of times 
+     */
+    for (i = 0 ; i < exec_cnt ; ++i)
+    {
+        int show_multi_prefix;
+
+        /* get the current direct object, if we have one */
+        dobj = (dolist != 0 ? &dolist[i] : 0);
+
+        /*
+         *   If we have a number or string, set the current one in
+         *   numObj/strObj 
+         */
+        if (dolist != 0)
+        {
+            if (dolist[i].vocolflg == VOCS_STR)
+            {
+                /* it's a string - set strObj.value */
+                vocsetobj(ctx, ctx->voccxstr, DAT_SSTRING,
+                          dolist[i].vocolfst + 1, &dolist[i], &dolist[i]);
+            }
+            else if (dolist[i].vocolflg == VOCS_NUM)
+            {
+                long v1, v2;
+
+                /* it's a number - set numObj.value */
+                v1 = atol(dolist[i].vocolfst);
+                oswp4s(&v2, v1);
+                vocsetobj(ctx, ctx->voccxnum, DAT_NUMBER, &v2,
+                          &dolist[i], &dolist[i]);
+            }
+        }
+
+        /*
+         *   For cases where we have a bunch of direct objects (or even
+         *   one when "all" was used), we want to preface the output from
+         *   each iteration with the name of the object we're acting on
+         *   currently.  In other cases, there is no prefix.  
+         */
+        show_multi_prefix = ((multi_flags != 0 || dobj_cnt > 1) && dobj != 0);
+
+        /* 
+         *   Execute the command for this object.  For every object except
+         *   the first, re-validate the direct and indirect objects.
+         *   There's no need to re-validate the objects on the first
+         *   object in a command, because that will already have been done
+         *   during object resolution. 
+         */
+        err = exe1cmd(ctx, actor, verb, dobj, prep, iobj,
+                      (i + 1 == exec_cnt), tpl, newstyle, FALSE,
+                      i != 0, i != 0, dolist, i, dobj_cnt,
+                      show_multi_prefix, multi_flags);
+
+        /* check the error - ignore any verification failures */
+        switch(err)
+        {
+        case ERR_PRS_VERDO_FAIL:
+        case ERR_PRS_VERIO_FAIL:
+        case ERR_PRS_NO_VERDO:
+        case ERR_PRS_NO_VERIO:
+        case ERR_RUNEXITOBJ:
+        case ERR_RUNEXIT:
+            /* ignore the error and continue */
+            err = 0;
+            break;
+
+        case ERR_RUNEXITPRECMD:
+            /* 
+             *   exited from preCommand - skip execution of subsequent
+             *   objects, but return success 
+             */
+            return 0;
+
+        case 0:
+            /* no error; continue */
+            break;
+
+        default:
+            /* anything else stops this command */
+            return err;
+        }
+
+        /* flush output */
+        tioflush(ctx->voccxtio);
+    }
+
+    /* success */
+    return 0;
+}
+
+/*
+ *   Execute a command recursively.  Game code can call this routine
+ *   (indirectly through a built-in function) to execute a command, using
+ *   all of the same steps that would be applied for the command if the
+ *   player had typed it.
+ */
+int execmd_recurs(voccxdef *ctx, objnum actor, objnum verb,
+                  objnum dobj, objnum prep, objnum iobj,
+                  int validate_dobj, int validate_iobj)
+{
+    int       err;
+    int       newstyle;
+    uchar     tpl[VOCTPL2SIZ];
+    vocoldef  dobjv;
+    vocoldef  iobjv;
+    voccxdef  ctx_copy;
+    runsdef *orig_sp;
+    runsdef *orig_bp;
+
+    /*
+     *   Save the stack and base pointers as they are on entry.  Since
+     *   exe1cmd() is being called recursively, it won't automatically clear
+     *   the stack after it's done as it would at the top level; this means
+     *   that an aborted frame can be left on the stack if we throw an
+     *   'exit' or 'abort' in the course of executing the command.  To make
+     *   sure we don't leave any aborted frames on the stack before
+     *   returning to our caller, we simply need to restore the stack and
+     *   frame pointers on the way out as they were on the way in.  
+     */
+    orig_sp = ctx->voccxrun->runcxsp;
+    orig_bp = ctx->voccxrun->runcxbp;
+
+    /* make a copy of the voc context, so that changes aren't permanent */
+    ctx_copy = *ctx;
+    ctx = &ctx_copy;
+
+    /* 
+     *   there are no unknown words in the recursive command, since the
+     *   command was prepared directly from resolved objects 
+     */
+    ctx->voccxunknown = 0;
+
+    /* set up the vocoldef structure for the direct object, if present */
+    if (dobj != MCMONINV)
+    {
+        dobjv.vocolobj = dobj;
+        dobjv.vocolfst = dobjv.vocollst = "";
+        dobjv.vocolflg = 0;
+    }
+
+    /* set up the vocoldef structure for the indirect object, if present */
+    if (iobj != MCMONINV)
+    {
+        iobjv.vocolobj = iobj;
+        iobjv.vocolfst = iobjv.vocollst = "";
+        iobjv.vocolflg = 0;
+    }
+    
+    /* figure out which template we need, based on the objects provided */
+    if (dobj == MCMONINV)
+    {
+        uint actofs;
+        uint tplofs;
+        
+        /* 
+         *   No objects were provided - use the verb's "action" method.
+         *   Make sure that there is in fact an "action" method. 
+         */
+        exe_get_tpl(ctx, verb, &tplofs, &actofs);
+        if (actofs != 0)
+        {
+            /* execute the "action" method */
+            err = exe1cmd(ctx, actor, verb, 0, &prep, 0, FALSE,
+                          0, FALSE, TRUE, validate_dobj, validate_iobj,
+                          0, 0, 0, FALSE, 0);
+        }
+        else
+        {
+            /* indicate that the sentence structure wasn't understood */
+            err = ERR_PRS_SENT_UNK;
+        }
+    }
+    else if (iobj == MCMONINV)
+    {
+        /* 
+         *   No indirect object was provided, but a direct object is
+         *   present - use the one-object template.  First, look up the
+         *   template.  
+         */
+        if (voctplfnd(ctx, verb, MCMONINV, tpl, &newstyle))
+        {
+            /* execute the command */
+            err = exe1cmd(ctx, actor, verb, &dobjv, &prep, 0, FALSE,
+                          tpl, newstyle, TRUE, validate_dobj, validate_iobj,
+                          &dobjv, 0, 1, FALSE, 0);
+        }
+        else
+        {
+            /* indicate that the sentence structure wasn't understood */
+            err = ERR_PRS_SENT_UNK;
+        }
+    }
+    else
+    {
+        /* 
+         *   Both a direct and indirect object were provided - find the
+         *   two-object template for the given preposition.
+         */
+        if (voctplfnd(ctx, verb, prep, tpl, &newstyle))
+        {
+            /* execute the command */
+            err = exe1cmd(ctx, actor, verb, &dobjv, &prep, &iobjv, FALSE,
+                          tpl, newstyle, TRUE, validate_dobj, validate_iobj,
+                          &dobjv, 0, 1, FALSE, 0);
+        }
+        else
+        {
+            /* indicate that the sentence structure wasn't understood */
+            err = ERR_PRS_SENT_UNK;
+        }
+    }
+
+    /* 
+     *   if the error was EXITPRECMD, change it to EXIT - EXITPRECMD is a
+     *   special flag indicating that we exited from a preCommand
+     *   function, which is different than normal exiting internally but
+     *   not to the game 
+     */
+    if (err == ERR_RUNEXITPRECMD)
+        err = ERR_RUNEXIT;
+
+    /*
+     *   restore the original stack and base pointers, to ensure that we
+     *   don't leave any aborted frames on the stack 
+     */
+    ctx->voccxrun->runcxsp = orig_sp;
+    ctx->voccxrun->runcxbp = orig_bp;
+
+    /* return the result code */
+    return err;
+}
+
+
+/*
+ *   Check for ALL, ANY, or THEM in the list - use multi-mode if found,
+ *   even if we have only one object.  Returns a combination of any of the
+ *   VOCS_ALL, VOCS_ANY, or VOCS_THEM flags that we find.  
+ */
+static int check_for_multi(vocoldef *dolist)
+{
+    int dolen;
+    int i;
+    int result;
+
+    /* presume we won't find any flags */
+    result = 0;
+
+    /* 
+     *   scan the list for ALL, ANY, or THEM flags, combining any such
+     *   flags we find into the result 
+     */
+    dolen = voclistlen(dolist);
+    for (i = 0 ; i < dolen ; ++i)
+        result |= (dolist[i].vocolflg & (VOCS_ALL | VOCS_ANY | VOCS_THEM));
+
+    /* return the result */
+    return result;
+}
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Try running the preparseCmd user function.  Returns 0 if the
+ *   function doesn't exist or returns 'true', ERR_PREPRSCMDCAN if it
+ *   returns 'nil' (and thus wants to cancel the command), and
+ *   ERR_PREPRSCMDREDO if it returns a list (and thus wants to redo the
+ *   command). 
+ */
+int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt,
+                     uchar **preparse_list)
+{
+    uchar    listbuf[VOCBUFSIZ + 2 + 3*VOCBUFSIZ];
+    int      i;
+    uchar   *p;
+    size_t   len;
+    runsdef  val;
+    int      typ;
+    int      err;
+
+    /* if there's no preparseCmd, keep processing */
+    if (ctx->voccxppc == MCMONINV)
+        return 0;
+    
+    /* build a list of the words */
+    for (p = listbuf + 2, i = 0 ; i < wrdcnt ; ++i)
+    {
+        char *src;
+        int add_quote;
+        
+        /* check for strings - they require special handling */
+        if (cmd[i][0] == '"')
+        {
+            /* 
+             *   it's a string - what follows is a run-time style string,
+             *   with a length prefix followed by the text of the string 
+             */
+            len = osrp2(cmd[i] + 1) - 2;
+            src = cmd[i] + 3;
+
+            /* add quotes to the result */
+            add_quote = TRUE;
+        }
+        else
+        {
+            /* ordinary word - copy directly */
+            src = (char *)cmd[i];
+
+            /* it's a null-terminated string */
+            len = strlen(src);
+
+            /* don't add quotes to the result */
+            add_quote = FALSE;
+        }
+
+        /* write the type prefix */
+        *p++ = DAT_SSTRING;
+
+        /* write the length prefix */
+        oswp2(p, len + 2 + (add_quote ? 2 : 0));
+        p += 2;
+
+        /* add an open quote if necessary */
+        if (add_quote)
+            *p++ = '"';
+
+        /* copy the text */
+        memcpy(p, src, len);
+        p += len;
+
+        /* add the closing quote if necessary */
+        if (add_quote)
+            *p++ = '"';
+    }
+    
+    /* set the length of the whole list */
+    len = p - listbuf;
+    oswp2(listbuf, len);
+    
+    /* push the list as the argument, and call the user's preparseCmd */
+    val.runstyp = DAT_LIST;
+    val.runsv.runsvstr = listbuf;
+    runpush(ctx->voccxrun, DAT_LIST, &val);
+
+    /* presume that no error will occur */
+    err = 0;
+
+    /* catch errors that occur within preparseCmd */
+    ERRBEGIN(ctx->voccxerr)
+    {
+        /* call preparseCmd */
+        runfn(ctx->voccxrun, ctx->voccxppc, 1);
+    }
+    ERRCATCH(ctx->voccxerr, err)
+    {
+        /* 
+         *   if it's abort/exit/exitobj, just return it; for any other
+         *   errors, just re-throw the same error 
+         */
+        switch(err)
+        {
+        case ERR_RUNABRT:
+        case ERR_RUNEXIT:
+        case ERR_RUNEXITOBJ:
+            /* simply return these errors to the caller */
+            break;
+
+        default:
+            /* re-throw anything else */
+            errrse(ctx->voccxerr);
+        }
+    }
+    ERREND(ctx->voccxerr);
+
+    /* if an error occurred, return the error code */
+    if (err != 0)
+        return err;
+
+    /* get the result */
+    typ = runtostyp(ctx->voccxrun);
+    
+    /* if they returned a list, it's a new command to execute */
+    if (typ == DAT_LIST)
+    {
+        /* get the list and give it to the caller */
+        *preparse_list = runpoplst(ctx->voccxrun);
+        
+        /* 
+         *   indicate that the command is to be reparsed with the new word
+         *   list 
+         */
+        return ERR_PREPRSCMDREDO;
+    }
+
+    /* for any other type, we don't need the value, so discard it */
+    rundisc(ctx->voccxrun);
+
+    /* if the result is nil, don't process this command further */
+    if (typ == DAT_NIL)
+        return ERR_PREPRSCMDCAN;
+    else
+        return 0;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   Call parseAskobjIndirect 
+ */
+static void voc_askobj_indirect(voccxdef *ctx, vocoldef *dolist,
+                                objnum actor, objnum verb, objnum prep)
+{
+    int cnt;
+    int i;
+    size_t len;
+    uchar *lstp;
+    
+    /*
+     *   Generate the direct object list argument.  This argument is a
+     *   list of lists.  For each noun phrase, we generate one sublist in
+     *   the main list.  Each sublist itself consists of three
+     *   sub-sublists: first, a list of strings giving the words in the
+     *   noun phrase; second, a list of the objects matching the noun
+     *   phrase; third, a list of the flags for the matching objects.
+     *   
+     *   So, if the player typed "put red box and blue ball", we might
+     *   generate a list something like this:
+     *   
+     *   [ [ ['red', 'box'], [redBox1, redBox2], [0, 0] ], [ ['blue',
+     *   'ball'], [blueBall], [0, 0] ] ] 
+     */
+
+    /*
+     *   First, figure out how much space we need for this list of lists
+     *   of lists.  Scan the direct object list for distinct noun phrases
+     *   - we need one sublist for each distinct noun phrase.
+     */
+    cnt = voclistlen(dolist);
+    for (len = 0, i = 0 ; i < cnt ; )
+    {
+        char *p;
+        size_t curlen;
+        int j;
+            
+        /* 
+         *   we need the sublist type prefix (one byte) plus the sublist
+         *   length prefix (two bytes), plus the type and length prefixes
+         *   (one plus two bytes) for each of the three sub-sublist 
+         */
+        len += (1+2) + 3*(1+2);
+
+        /* 
+         *   we need space to store the strings for the words in this noun
+         *   phrase 
+         */
+        for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ;
+             p += curlen + 1)
+        {
+            /* 
+             *   add in the space needed for this string element in the
+             *   sub-sublist - we need one byte for the type prefix, two
+             *   bytes for the length prefix, and the bytes for the string
+             *   itself 
+             */
+            curlen = strlen(p);
+            len += (1+2) + curlen;
+        }
+
+        /* 
+         *   scan each object for this same noun phrase (i.e., for which
+         *   the vocabulary words are the same) 
+         */
+        for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
+             ++j)
+        {
+            /* 
+             *   Add in space for this object in the sub-sublist for the
+             *   current noun phrase.  If this object is nil, we need only
+             *   one byte for the type; otherwise, we need one byte for
+             *   the type prefix plus two bytes for the object ID.  
+             */
+            if (dolist[i].vocolobj == MCMONINV)
+                len += 1;
+            else
+                len += (1 + 2);
+            
+            /*
+             *   Add in space for the flags sub-sublist for the current
+             *   object.  We need one byte for the type and four for the
+             *   integer value.  
+             */
+            len += (1 + 4);
+        }
+
+        /* skip to the next distinct noun phrase */
+        i = j;
+    }
+
+    /* allocate the list */
+    lstp = voc_push_list_siz(ctx, len);
+
+    /* 
+     *   Go through our object array again, and this time actually build
+     *   the list.  
+     */
+    for (i = 0 ; i < cnt ; )
+    {
+        char *p;
+        uchar *subp;
+        uchar *subsubp;
+        size_t curlen;
+        int j;
+
+        /* start the sublist with the type prefix */
+        *lstp++ = DAT_LIST;
+
+        /* leave a placeholder for our length prefix */
+        subp = lstp;
+        lstp += 2;
+
+        /* start the sub-sublist with the word strings */
+        *lstp++ = DAT_LIST;
+        subsubp = lstp;
+        lstp += 2;
+
+        /* store the word strings in the sub-sublist */
+        for (p = dolist[i].vocolfst ; p != 0 && p <= dolist[i].vocollst ;
+             p += curlen + 1)
+        {
+            /* get this string's length */
+            curlen = strlen(p);
+
+            /* store the type and length prefixes */
+            *lstp++ = DAT_SSTRING;
+            oswp2(lstp, curlen + 2);
+            lstp += 2;
+
+            /* store the string */
+            memcpy(lstp, p, curlen);
+            lstp += curlen;
+        }
+
+        /* fix up the string sub-sublist length */
+        oswp2(subsubp, lstp - subsubp);
+
+        /* start the second sub-sublist, for the objects */
+        *lstp++ = DAT_LIST;
+        subsubp = lstp;
+        lstp += 2;
+
+        /* write each object */
+        for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
+             ++j)
+        {
+            /* 
+             *   if this object isn't nil, write it to the sub-sublist;
+             *   otherwise, just put nil in the sub-sublist 
+             */
+            if (dolist[j].vocolobj != MCMONINV)
+            {
+                *lstp++ = DAT_OBJECT;
+                oswp2(lstp, dolist[j].vocolobj);
+                lstp += 2;
+            }
+            else
+            {
+                /* no object - just store nil */
+                *lstp++ = DAT_NIL;
+            }
+        }
+
+        /* fix up the object sub-sublist length */
+        oswp2(subsubp, lstp - subsubp);
+
+        /* start the third sub-sublist, for the flags */
+        *lstp++ = DAT_LIST;
+        subsubp = lstp;
+        lstp += 2;
+
+        /* write each object's flags */
+        for (j = i ; j < cnt && dolist[j].vocolfst == dolist[i].vocolfst ;
+             ++j)
+        {
+            /* write the flags */
+            *lstp++ = DAT_NUMBER;
+            oswp4s(lstp, dolist[j].vocolflg);
+            lstp += 4;
+        }
+
+        /* fix up the flag sub-sublist length */
+        oswp2(subsubp, lstp - subsubp);
+
+        /* skip to the start of the next distinct noun phrase */
+        i = j;
+
+        /* fix up the sublist length */
+        oswp2(subp, lstp - subp);
+    }
+
+    /* push the prep, verb, and actor arguments */
+    runpobj(ctx->voccxrun, prep);
+    runpobj(ctx->voccxrun, verb);
+    runpobj(ctx->voccxrun,
+            (objnum)(actor == MCMONINV ? ctx->voccxme : actor));
+
+    /* call the function */
+    runfn(ctx->voccxrun, ctx->voccxpask3, 4);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   execmd() - executes a user's command given the verb's verb and
+ *   preposition words, a list of nouns to be used as indirect objects,
+ *   and a list to be used for direct objects.  The globals cmdActor and
+ *   cmdPrep should already be set.  This routine tries to find a template
+ *   for the verb which matches the player's command.  If no template
+ *   matches, we try (using default objects and, if that fails, requests
+ *   to the player for objects) to fill in any missing information in the
+ *   player's command.  If that still fails, we will say we don't
+ *   understand the sentence and leave it at that.  
+ */
+int execmd(voccxdef *ctx, objnum actor, objnum prep,
+           char *vverb, char *vprep, vocoldef *dolist, vocoldef *iolist,
+           char **cmd, int *typelist,
+           char *cmdbuf, int wrdcnt, uchar **preparse_list, int *next_word)
+{
+    objnum    verb;
+    objnum    iobj;
+    int       multi_flags = 0;
+    vocwdef  *n;
+    int       cnt;
+    vocoldef *newnoun;
+    int       next;
+    char     *exenewcmd;
+    char     *donewcmd;
+    char     *ionewcmd;
+    char     *exenewbuf;
+    char     *donewbuf;
+    char     *ionewbuf;
+    char    **exenewlist;
+    char    **donewlist;
+    char    **ionewlist;
+    int      *exenewtype;
+    int      *donewtype;
+    int      *ionewtype;
+    vocoldef *dolist1;
+    vocoldef *iolist1;
+    uchar     tpl[VOCTPL2SIZ];
+    int       foundtpl;        /* used to determine success of tpl searches */
+    runcxdef *rcx = ctx->voccxrun;
+    uint      tplofs;                          /* offset of template object */
+    uint      actofs;                        /* offset of 'action' property */
+    int       askflags;                /* flag for what we need to ask user */
+    int       newstyle;   /* flag indicating new-style template definitions */
+    int       tplflags;
+    int       err;
+    uchar    *save_sp;
+
+    /* run preparseCmd */
+    switch(try_preparse_cmd(ctx, cmd, wrdcnt, preparse_list))
+    {
+    case 0:
+        /* proceed with the command */
+        break;
+
+    case ERR_PREPRSCMDCAN:
+        /* command cancelled */
+        return 0;
+
+    case ERR_RUNEXIT:
+    case ERR_RUNABRT:
+    case ERR_RUNEXITOBJ:
+        /* abort/exit/exitobj - treat this the same as command cancellation */
+        return 0;
+
+    case ERR_PREPRSCMDREDO:
+        /* redo the command - so indicate to the caller */
+        return ERR_PREPRSCMDREDO;
+    }
+
+    /* look up the verb based on the verb and verb-prep */
+    n = vocffw(ctx, vverb, (int)strlen(vverb),
+               vprep, (vprep ? (int)strlen(vprep) : 0), PRP_VERB,


Commit: bc87d740f55cfd0d283c6342abf641dfd0ff2e42
    https://github.com/scummvm/scummvm/commit/bc87d740f55cfd0d283c6342abf641dfd0ff2e42
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS2: Yet more volumous code additions

Changed paths:
  A engines/glk/tads/os_banners.cpp
  A engines/glk/tads/os_banners.h
  A engines/glk/tads/os_parse.cpp
  A engines/glk/tads/os_parse.h
  A engines/glk/tads/tads2/get_string.cpp
    engines/glk/module.mk
    engines/glk/tads/os_buffer.cpp
    engines/glk/tads/os_glk.cpp
    engines/glk/tads/os_glk.h
    engines/glk/tads/tads2/debug.h
    engines/glk/tads/tads2/error_message.cpp
    engines/glk/tads/tads2/file_io.cpp
    engines/glk/tads/tads2/ltk.cpp
    engines/glk/tads/tads2/output.cpp
    engines/glk/tads/tads2/post_compilation.cpp
    engines/glk/tads/tads2/post_compilation.h
    engines/glk/tads/tads2/runtime_driver.cpp
    engines/glk/tads/tads2/text_io.h


diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 1ce2458..db930f2 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -92,9 +92,11 @@ MODULE_OBJS := \
 	scott/detection.o \
 	scott/scott.o \
 	tads/detection.o \
+	tads/os_banners.o \
 	tads/os_buffer.o \
 	tads/os_glk.o \
 	tads/os_frob_tads.o \
+	tads/os_parse.o \
 	tads/tads.o \
 	tads/tads2/built_in.o \
 	tads/tads2/character_map.o \
@@ -106,6 +108,7 @@ MODULE_OBJS := \
 	tads/tads2/error_message.o \
 	tads/tads2/execute_command.o \
 	tads/tads2/file_io.o \
+	tads/tads2/get_string.o \
 	tads/tads2/line_source_file.o \
 	tads/tads2/list.o \
 	tads/tads2/ltk.o \
diff --git a/engines/glk/tads/os_banners.cpp b/engines/glk/tads/os_banners.cpp
new file mode 100644
index 0000000..577e9f3
--- /dev/null
+++ b/engines/glk/tads/os_banners.cpp
@@ -0,0 +1,812 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/os_banners.h"
+#include "glk/tads/os_buffer.h"
+#include "glk/tads/tads.h"
+#include "glk/glk_api.h"
+#include "glk/window_text_buffer.h"
+
+namespace Glk {
+namespace TADS {
+
+struct os_banner_t;
+struct banner_contents_t;
+typedef os_banner_t *osbanid_t;
+typedef banner_contents_t *contentid_t;
+
+/* for tracking banner windows */
+struct os_banner_t {
+    uint id;                      /* unique identifier */
+    uint valid;                   /* banner status */
+
+    osbanid_t prev;                 /* previous sibling */
+    osbanid_t next;                 /* next sibling */
+    osbanid_t children;             /* child's descendents */
+    osbanid_t parent;               /* child's parent */
+
+    uint method;                  /* glk window method */
+    uint size;                    /* glk window size */
+    uint type;                    /* glk window type */
+    uint status;                  /* glk status style */
+
+    uint cheight;                 /* glk char height */
+    uint cwidth;                  /* glk char width */
+
+    uint fgcolor;                 /* foreground color */
+    uint bgcolor;                 /* background color */
+    uint fgcustom;                /* custom colors */
+    uint bgcustom;
+    uint bgtrans;
+
+    contentid_t contents;           /* window contents */
+    uint style;                   /* active Glk style value */
+    uint newline;                 /* active newline */
+    uint move, x, y;              /* active cursor position */
+
+    winid_t win;                    /* glk window object */
+};
+
+/* for reprinting banner contents */
+struct banner_contents_t {
+    osbanid_t banner;               /* content owner */
+    contentid_t next;               /* next set of contents */
+
+    uint style;                   /* stored contents style */
+    uint newline;                 /* stored newline */
+    uint move, x, y;              /* stored cursor position */
+
+    char *chars;
+    uint len;
+};
+
+static osbanid_t os_banners = NULL;
+static uint os_banner_count = 999;
+
+extern winid_t mainwin;
+extern winid_t statuswin;
+
+extern uint mainfg;
+extern uint mainbg;
+
+extern uint statusfg;
+extern uint statusbg;
+
+void banner_contents_display(contentid_t contents);
+
+/* Implementation-specific functions for managing banner windows */
+/*
+    os_banner_init();
+    os_banner_insert();
+    os_banner_styles_apply();
+    os_banner_styles_reset();
+    os_banners_close();
+    os_banners_open();
+    os_banners_redraw();
+*/
+
+osbanid_t os_banner_init() {
+    osbanid_t instance;
+    instance = (osbanid_t)malloc(sizeof(os_banner_t));
+    if (!instance)
+        return 0;
+
+    instance->id = ++ os_banner_count;
+    instance->valid = 1;
+
+    instance->prev = 0;
+    instance->next = 0;
+    instance->children = 0;
+    instance->parent = 0;
+
+    instance->method = 0;
+    instance->size = 0;
+    instance->type = 0;
+    instance->status = 0;
+
+    instance->cheight = 0;
+    instance->cwidth = 0;
+
+    instance->contents = 0;
+    instance->style = style_Normal;
+    instance->newline = 0;
+    instance->move = 0;
+    instance->x = 0;
+    instance->y = 0;
+
+    instance->win = 0;
+
+    return instance;
+}
+
+osbanid_t os_banner_insert(osbanid_t parent, uint operation, osbanid_t other,
+                           uint method, uint size, uint type, uint status)
+{
+    if (!parent || !(parent->valid))
+        return 0;
+
+    if (operation == OS_BANNER_BEFORE || operation == OS_BANNER_AFTER)
+        if (!other || !(other->valid) || !(other->parent == parent))
+            operation = OS_BANNER_LAST;
+
+    osbanid_t baby = os_banner_init();
+    if (!baby)
+        return 0;
+    baby->parent = parent;
+
+    if (!(parent->children))
+    {
+        parent->children = baby;
+    }
+    else
+    {
+        osbanid_t child = parent->children;
+
+        switch (operation)
+        {
+            case OS_BANNER_FIRST:
+                parent->children = baby;
+                baby->next = child;
+                child->prev = baby;
+                break;
+
+            case OS_BANNER_BEFORE:
+                while (child != other && child->next)
+                    child = child->next;
+
+                if (child->prev)
+                {
+                    child->prev->next = baby;
+                    baby->prev = child->prev;
+                }
+                else
+                {
+                    parent->children = baby;
+                }
+
+                baby->next = child;
+                child->prev = baby;
+                break;
+
+            case OS_BANNER_LAST:
+                while(child->next)
+                    child = child->next;
+
+                baby->prev = child;
+                child->next = baby;
+                break;
+
+            case OS_BANNER_AFTER:
+                while (child != other && child->next)
+                    child = child->next;
+
+                if (child->next)
+                {
+                    child->next->prev = baby;
+                    baby->next = child->next;
+                }
+
+                baby->prev = child;
+                child->next = baby;
+                break;
+
+            default: break;
+        }
+    }
+
+    baby->method = method;
+    baby->size = size;
+    baby->type = type;
+    baby->status = status;
+
+    return baby;
+}
+
+void os_banner_styles_apply (osbanid_t banner)
+{
+    if (!banner || !(banner->valid))
+        return;
+
+    uint propval = banner->status ? 0 : (banner->type == wintype_TextGrid ? 0 : 1);
+    uint bgcustom = banner->bgtrans ? banner->bgcolor : banner->bgcustom;
+
+    /* font style: monospace for text grid and tab aligned buffers, else proportional */
+    g_vm->glk_stylehint_set(banner->type, style_Alert, stylehint_Proportional, propval);
+    g_vm->glk_stylehint_set(banner->type, style_Subheader, stylehint_Proportional, propval);
+    g_vm->glk_stylehint_set(banner->type, style_Emphasized, stylehint_Proportional, propval);
+    g_vm->glk_stylehint_set(banner->type, style_Normal, stylehint_Proportional, propval);
+    g_vm->glk_stylehint_set(banner->type, style_User1, stylehint_Proportional, propval);
+    g_vm->glk_stylehint_set(banner->type, style_User2, stylehint_Proportional, propval);
+
+    /* foreground color: user1 reverse, user2 custom */
+    g_vm->glk_stylehint_set(banner->type, style_Alert, stylehint_TextColor, banner->fgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_Subheader, stylehint_TextColor, banner->fgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_Emphasized, stylehint_TextColor, banner->fgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_Normal, stylehint_TextColor, banner->fgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_User1, stylehint_TextColor, banner->bgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_User2, stylehint_TextColor, banner->fgcustom);
+
+    /* background color: user1 reverse, user2 custom */
+    g_vm->glk_stylehint_set(banner->type, style_Alert, stylehint_BackColor, banner->bgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_Subheader, stylehint_BackColor, banner->bgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_Emphasized, stylehint_BackColor, banner->bgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_Normal, stylehint_BackColor, banner->bgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_User1, stylehint_BackColor, banner->fgcolor);
+    g_vm->glk_stylehint_set(banner->type, style_User2, stylehint_BackColor, bgcustom);
+
+}
+
+void os_banner_styles_reset (void)
+{
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Alert, stylehint_Proportional);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Subheader, stylehint_Proportional);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Emphasized, stylehint_Proportional);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Normal, stylehint_Proportional);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_User1, stylehint_Proportional);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_User2, stylehint_Proportional);
+
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Alert, stylehint_TextColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Subheader, stylehint_TextColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Emphasized, stylehint_TextColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Normal, stylehint_TextColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_User1, stylehint_TextColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_User2, stylehint_TextColor);
+
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Alert, stylehint_BackColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Subheader, stylehint_BackColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Emphasized, stylehint_BackColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_Normal, stylehint_BackColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_User1, stylehint_BackColor);
+    g_vm->glk_stylehint_clear(wintype_AllTypes, style_User2, stylehint_BackColor);
+
+#ifdef GARGLK
+    /* reset our default colors with a superfluous hint */
+    g_vm->glk_stylehint_set(wintype_AllTypes, style_Normal, stylehint_TextColor, mainfg);
+    g_vm->glk_stylehint_set(wintype_AllTypes, style_Normal, stylehint_BackColor, mainbg);
+#endif /* GARGLK */
+}
+
+void os_banners_close(osbanid_t banner)
+{
+    if (!banner)
+        return;
+
+    os_banners_close(banner->children);
+    os_banners_close(banner->next);
+
+    if (banner->win && (banner->win != mainwin))
+    {
+        g_vm->glk_window_close(banner->win, 0);
+        banner->win = 0;
+    }
+}
+
+void os_banners_open(osbanid_t banner)
+{
+    if (!banner)
+        return;
+
+    if (banner->valid)
+    {
+        if (banner->size && banner->parent && banner->parent->win)
+        {
+            os_banner_styles_apply(banner);
+            banner->win = g_vm->glk_window_open(banner->parent->win, banner->method,
+                                          banner->size, banner->type, banner->id);
+            banner_contents_display(banner->contents);
+        }
+        os_banners_open(banner->children);
+    }
+
+    os_banners_open(banner->next);
+}
+
+void os_banners_redraw()
+{
+    if (!os_banners)
+        return;
+
+    os_banners_close(os_banners);
+    os_banners_open(os_banners);
+    os_banner_styles_reset();
+}
+
+/* Implementation-specific functions for managing banner contents */
+/*
+    banner_contents_init();
+    banner_contents_insert();
+    banner_contents_display();
+    banner_contents_clear();
+*/
+
+contentid_t banner_contents_init(void)
+{
+    contentid_t instance;
+    instance = (contentid_t)malloc(sizeof(banner_contents_t));
+    if (!instance)
+        return 0;
+
+    instance->banner = 0;
+    instance->next = 0;
+
+    instance->style = style_Normal;
+    instance->newline = 0;
+    instance->move = 0;
+    instance->x = 0;
+    instance->y = 0;
+
+    instance->chars = 0;
+    instance->len = 0;
+
+    return instance;
+}
+
+void banner_contents_insert(contentid_t contents, const char *txt, size_t len)
+{
+    if (!contents)
+        return;
+
+    contents->chars = (char *)malloc(sizeof(char) * (len + 1));
+    if (!(contents->chars))
+        return;
+
+    memcpy(contents->chars, txt, len);
+
+    contents->chars[len] = '\0';
+    contents->len = len;
+}
+
+void banner_contents_display(contentid_t contents)
+{
+    if (!contents || !(contents->banner))
+        return;
+
+    winid_t win = contents->banner->win;
+    uint len = contents->len;
+
+    g_vm->glk_set_window(win);
+
+    if (contents->newline)
+    {
+        char ch = '\n';
+        os_put_buffer(&ch, 1);
+    }
+    
+    if (len && (contents->chars[len-1] == '\n'))
+    {
+        len --;
+        contents->banner->newline = 1;
+    }
+    else
+    {
+        contents->banner->newline = 0;
+    }
+
+    if (contents->move)
+    {
+        g_vm->glk_window_move_cursor(win, contents->x, contents->y);
+        contents->banner->move = 0;
+        contents->banner->x = 0;
+        contents->banner->y = 0;
+    }
+
+    g_vm->glk_set_style(contents->style);
+    os_put_buffer(contents->chars, len);
+    g_vm->glk_set_window(mainwin);
+    banner_contents_display(contents->next);
+}
+
+void banner_contents_clear(contentid_t contents)
+{
+    if (!contents)
+        return;
+
+    banner_contents_clear(contents->next);
+
+    if (contents->chars)
+        free(contents->chars);
+
+    free(contents);
+}
+
+/* Banner API functions */
+
+void *os_banner_create(void *parent, int where, void *other, int wintype,
+                       int align, int siz, int siz_units,
+                       unsigned long style)
+{
+    osbanid_t gparent = (osbanid_t)parent;
+    osbanid_t gbanner;
+    uint gwinmeth = 0;
+    uint gwinsize = siz;
+    uint gwintype = 0;
+    uint gstatus = (style & OS_BANNER_STYLE_TAB_ALIGN);
+
+    if (gparent && !(gparent->valid))
+        return 0;
+
+    if (!os_banners)
+    {
+        os_banners = os_banner_init();
+        if (!os_banners)
+            return 0;
+        os_banners->win = mainwin;
+    }
+
+    if (!gparent)
+        gparent = os_banners;
+
+    switch (wintype)
+    {
+        case OS_BANNER_TYPE_TEXT: gwintype = wintype_TextBuffer; break;
+        case OS_BANNER_TYPE_TEXTGRID: gwintype = wintype_TextGrid; break;
+        default: gwintype = wintype_TextGrid; break;
+    }
+
+    switch (align)
+    {
+        case OS_BANNER_ALIGN_TOP: gwinmeth = winmethod_Above; break;
+        case OS_BANNER_ALIGN_BOTTOM: gwinmeth = winmethod_Below; break;
+        case OS_BANNER_ALIGN_LEFT: gwinmeth = winmethod_Left; break;
+        case OS_BANNER_ALIGN_RIGHT: gwinmeth = winmethod_Right; break;
+        default: gwinmeth = winmethod_Above; break;
+    }
+
+    switch (siz_units)
+    {
+        case OS_BANNER_SIZE_PCT: gwinmeth |= winmethod_Proportional; break;
+        case OS_BANNER_SIZE_ABS: gwinmeth |= winmethod_Fixed; break;
+        default: gwinmeth |= winmethod_Fixed; break;
+    }
+
+    gbanner = os_banner_insert(gparent, where, (osbanid_t)other, gwinmeth, gwinsize, gwintype, gstatus);
+
+    if (gbanner)
+    {
+        gbanner->fgcolor = gstatus ? statusbg : mainfg;
+        gbanner->bgcolor = gstatus ? statusfg : mainbg;
+        gbanner->fgcustom = gbanner->fgcolor;
+        gbanner->bgcustom = gbanner->bgcolor;
+        gbanner->bgtrans = 1;
+    }
+
+    os_banners_redraw();
+
+    return gbanner;
+}
+
+void os_banner_set_size(void *banner_handle, int siz, int siz_units, int is_advisory)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+
+    if (!banner || !banner->valid)
+        return;
+
+    uint gwinsize = siz;
+    uint gwinmeth = 0;
+
+    gwinmeth = banner->method &
+        (winmethod_Above | winmethod_Below |
+         winmethod_Left  | winmethod_Right);
+
+    switch (siz_units)
+    {
+        case OS_BANNER_SIZE_PCT: gwinmeth |= winmethod_Proportional; break;
+        case OS_BANNER_SIZE_ABS: gwinmeth |= winmethod_Fixed; break;
+        default: gwinmeth |= winmethod_Fixed; break;
+    }
+
+    banner->method = gwinmeth;
+    banner->size = gwinsize;
+
+    os_banners_redraw();
+}
+
+void os_banner_size_to_contents(void *banner_handle)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+
+    if (!banner || !banner->valid || !banner->win)
+        return;
+
+#ifdef GARGLK
+    if (banner->type == wintype_TextBuffer)
+    {
+		TextBufferWindow *win = dynamic_cast<TextBufferWindow *>(banner->win);
+		assert(win);
+        int size = win->_scrollMax;
+        if (win->_numChars)
+            size++;
+        os_banner_set_size(banner, size, OS_BANNER_SIZE_ABS, 0);
+    }
+#endif /* GARGLK */
+}
+
+void os_banner_delete(void *banner_handle)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !(banner->valid))
+        return;
+
+    banner->valid = 0;
+    os_banners_redraw();
+
+    if (banner->parent && banner->parent->children == banner)
+        banner->parent->children = banner->next;
+
+    if (banner->next)
+        banner->next->prev = banner->prev;
+
+    if (banner->prev)
+        banner->prev->next = banner->next;
+
+    banner_contents_clear(banner->contents);
+
+    free(banner);
+}
+
+void os_banner_orphan(void *banner_handle)
+{
+    os_banner_delete(banner_handle);
+}
+
+int os_banner_getinfo(void *banner_handle, os_banner_info_t *info)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !banner->valid || !banner->win)
+        return 1;
+
+    //winid_t win = banner->win;
+    uint gwintype = banner->type;
+    uint gwinmeth = banner->method;
+    uint gstyletab = banner->status;
+
+    if (gwinmeth & winmethod_Above)
+        info->align = OS_BANNER_ALIGN_TOP;
+    if (gwinmeth & winmethod_Below)
+        info->align = OS_BANNER_ALIGN_BOTTOM;
+    if (gwinmeth & winmethod_Left)
+        info->align = OS_BANNER_ALIGN_LEFT;
+    if (gwinmeth & winmethod_Right)
+        info->align = OS_BANNER_ALIGN_RIGHT;
+
+    info->style = gstyletab ? OS_BANNER_STYLE_TAB_ALIGN : 0;
+
+    g_vm->glk_window_get_size(banner->win, &(banner->cwidth), &(banner->cheight));
+
+    info->rows = banner->cheight;
+    info->columns = banner->cwidth;
+
+    info->pix_width = 0;
+    info->pix_height = 0;
+
+    info->os_line_wrap = gstyletab ? 0 : (gwintype == wintype_TextBuffer);
+
+    return 1;
+}
+
+int os_banner_get_charwidth(void *banner_handle)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !banner->valid || !banner->win)
+        return 0;
+
+    g_vm->glk_window_get_size(banner->win, &(banner->cwidth), &(banner->cheight));
+
+    return banner->cwidth;
+}
+
+int os_banner_get_charheight(void *banner_handle)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !banner->valid || !banner->win)
+        return 0;
+
+    g_vm->glk_window_get_size(banner->win, &(banner->cwidth), &(banner->cheight));
+
+    return banner->cheight;
+}
+
+void os_banner_clear(void *banner_handle)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !banner->valid)
+        return;
+
+    if (banner->win)
+    {
+        winid_t win = banner->win;
+        g_vm->glk_window_clear(win);
+    }
+
+    banner_contents_clear(banner->contents);
+    banner->contents = 0;
+    banner->newline = 0;
+    banner->move = 0;
+    banner->x = 0;
+    banner->y = 0;
+}
+
+void os_banner_disp(void *banner_handle, const char *txt, size_t len)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !banner->valid || !banner->win)
+        return;
+
+    contentid_t update = banner_contents_init();
+    if (!update)
+        return;
+    update->banner = banner;
+
+    if (!(banner->contents))
+    {
+        banner->contents = update;
+    }
+    else
+    {
+        contentid_t contents = banner->contents;
+        while (contents->next)
+            contents = contents->next;
+        contents->next = update;
+    }
+
+    update->style = banner->style;
+    update->newline = banner->newline;
+    update->move = banner->move;
+    update->x = banner->x;
+    update->y = banner->y;
+
+    banner_contents_insert(update, txt, len);
+    banner_contents_display(update);
+}
+
+void os_banner_goto(void *banner_handle, int row, int col)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !banner->valid || !banner->win)
+        return;
+
+    if (banner->type == wintype_TextGrid)
+    {
+        banner->move = 1;
+        banner->x = col;
+        banner->y = row;
+    }
+}
+
+void os_banner_set_attr(void *banner_handle, int attr)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !banner->valid)
+        return;
+
+    if (attr & OS_ATTR_BOLD && attr & OS_ATTR_ITALIC)
+        banner->style = style_Alert;
+    else if (attr & OS_ATTR_BOLD)
+        banner->style = style_Subheader;
+    else if (attr & OS_ATTR_ITALIC)
+        banner->style = style_Emphasized;
+    else
+        banner->style = style_Normal;
+}
+
+void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg)
+{
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !banner->valid)
+        return;
+
+    uint reversed = 0;
+    uint normal = 0;
+    uint transparent = 0;
+
+    /* evaluate parameters */
+
+    if (os_color_is_param(fg))
+    {
+        switch(fg)
+        {
+            case OS_COLOR_P_TEXTBG:
+            case OS_COLOR_P_STATUSBG:
+                reversed = 1;
+                break;
+
+            case OS_COLOR_P_TEXT:
+            case OS_COLOR_P_STATUSLINE:
+            case OS_COLOR_P_INPUT:
+                normal = 1;
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    if (os_color_is_param(bg))
+    {
+        switch (bg)
+        {
+            case OS_COLOR_P_TRANSPARENT:
+                transparent = 1;
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    /* choose a style */
+
+    if (normal && transparent)
+        banner->style = style_Normal;
+    else if (reversed)
+        banner->style = style_User1;
+    else
+        banner->style = style_User2;
+
+    /* process our custom color */
+
+    if (banner->style == style_User2)
+    {
+        /* store current settings */
+        uint oldfg = banner->fgcustom;
+        uint oldbg = banner->bgcustom;
+        uint oldtr = banner->bgtrans;
+
+        /* reset custom color parameters */
+        banner->fgcustom = banner->fgcolor;
+        banner->bgcustom = banner->bgcolor;
+        banner->bgtrans = 1;
+
+        if (!normal)
+            banner->fgcustom = fg;
+
+        if (!transparent)
+        {
+            banner->bgcustom = bg;
+            banner->bgtrans = 0;
+        }
+
+        if (!(banner->fgcustom == oldfg
+            && banner->bgcustom == oldbg
+            && banner->bgtrans == oldtr))
+            os_banners_redraw();
+    }
+}
+
+void os_banner_set_screen_color(void *banner_handle, os_color_t color) {
+    osbanid_t banner = (osbanid_t)banner_handle;
+    if (!banner || !banner->valid)
+        return;
+
+    if (!(os_color_is_param(color)))
+        banner->bgcolor = color;
+
+    os_banners_redraw();
+}
+
+void os_banner_flush(void *banner_handle) {}
+void os_banner_start_html(void *banner_handle) {}
+void os_banner_end_html(void *banner_handle) {}
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_banners.h b/engines/glk/tads/os_banners.h
new file mode 100644
index 0000000..f518d8e
--- /dev/null
+++ b/engines/glk/tads/os_banners.h
@@ -0,0 +1,631 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* TADS OS interface file type definitions
+ *
+ * Defines certain datatypes used in the TADS operating system interface
+ */
+
+#ifndef GLK_TADS_OS_BANNERS
+#define GLK_TADS_OS_BANNERS
+
+#include "common/scummsys.h"
+#include "glk/tads/os_glk.h"
+
+namespace Glk {
+namespace TADS {
+
+/* ------------------------------------------------------------------------ */
+/*
+ *   External Banner Interface.  This interface provides the ability to
+ *   divide the display window into multiple sub-windows, each with its own
+ *   independent contents.
+ *   
+ *   To determine where a new banner is displayed, we look at the banners as
+ *   a tree, rooted at the "main window," the special banner that the system
+ *   automatically creates initially for the main game text.  We start by
+ *   allocating the entire display (or the entire application window, if
+ *   we're running on a GUI system) to the main window.  We then traverse
+ *   the tree, starting with the root window's children.  For each child
+ *   window, we allocate space for the child out of the parent window's
+ *   area, according to the child's alignment and size settings, and deduct
+ *   this space from the parent window's size.  We then lay out the children
+ *   of the child.
+ *   
+ *   For each banner window, we take its requested space out of the parent
+ *   window's area by starting at the edge of the parent window rectangle as
+ *   indicated by the banner's alignment, and taking the requested `width
+ *   (for a left/right banner) or height (for a top/bottom banner), limiting
+ *   to the available width/height in the parent window's space.  Give the
+ *   banner the full extent of the parent's space in its other dimension (so
+ *   a left/right banner gets the full height of the parent space, and a
+ *   top/bottom banner gets the full width).
+ *   
+ *   Note that the layout proceeds exclusively down the tree (i.e., from the
+ *   root to children to grandchildren, and so on).  It *appears* that a
+ *   child affects its parent, because of the deduction step: a child
+ *   acquires screen space by carving out a chunk of its parent.  The right
+ *   way to think about this, though, is that the parent's full area is the
+ *   union of the parent window and all of its children; when viewed this
+ *   way, the parent's full area is fully determined the instant the parent
+ *   is laid out, and never changes as its children are laid out.  Note in
+ *   particular that a child can never make a parent larger; the only thing
+ *   a child can do to a parent is carve out a chunk of the parent for
+ *   itself, which doesn't affect the boundaries of the union of the parent
+ *   plus its children.
+ *   
+ *   Note also that if the banner has a border, and the implementation
+ *   actually draws borders, the border must be drawn for the *full* area of
+ *   the banner, as defined above.  For example, suppose we have two
+ *   borders: banner A is a child of the main window, is top-aligned, and
+ *   has a border.  Banner B is a child of banner A, right-aligned, with no
+ *   border.  Obviously, without considering banner B, banner A's space runs
+ *   across the entire width of the main window, so its border (at the
+ *   bottom of its area) runs across the entire width of the main window.
+ *   Banner B carves out some space from A's right side for itself, so
+ *   banner A's actual on-screen area runs from the left edge of the main
+ *   window to banner B's left edge.  However, even though banner A itself
+ *   no longer runs the full width of the main window, banner A's *full*
+ *   area - that is, the union of banner A's on-screen area and all of its
+ *   children's full areas - does still run the entire width of the main
+ *   window, hence banner A's border must still run the full width of the
+ *   main window.  The simple way of looking at this is that a banner's
+ *   border is always to be drawn exactly the same way, regardless of
+ *   whether or not the banner has children - simply draw the banner as it
+ *   would be drawn if the banner had no children.
+ *   
+ *   Each time a banner is added or removed, we must recalculate the layout
+ *   of the remaining banners and main text area.  The os_banner_xxx()
+ *   implementation is responsible for this layout refiguring.
+ *   
+ *   The entire external banner window interface is optional, although the
+ *   functions must at least be defined as dummies to avoid linker errors
+ *   when building.  If a platform doesn't implement this feature,
+ *   os_banner_create() should simply return null, and the other routines
+ *   can do nothing.  
+ */
+
+/* 
+ *   Create a banner window.  'info' gives the desired parameters for the new
+ *   banner.
+ *   
+ *   Note that certain requested parameter settings might or might not be
+ *   respected, depending on the capabilities of the platform and user
+ *   preferences.  os_banner_getinfo() can be used after creation to
+ *   determine which parameter settings are actually used in the new banner.
+ *   
+ *   'parent' gives the parent of this banner; this is the banner handle of
+ *   another banner window, or null.  If 'parent' is null, then the new
+ *   banner is a child of the main window, which the system creates
+ *   automatically at startup and which contains the main input/output
+ *   transcript.  The new banner's on-screen area is carved out of the
+ *   parent's space, according to the alignment and size settings of the new
+ *   window, so this determines how the window is laid out on the screen.
+ *   
+ *   'where' is OS_BANNER_FIRST to make the new window the first child of its
+ *   parent; OS_BANNER_LAST to make it the last child of its parent;
+ *   OS_BANNER_BEFORE to insert it immediately before the existing banner
+ *   identified by handle in 'other'; or OS_BANNER_AFTER to insert
+ *   immediately after 'other'.  When BEFORE or AFTER is used, 'other' must
+ *   be another child of the same parent; if it is not, the routine should
+ *   act as though 'where' were given as OS_BANNER_LAST.
+ *   
+ *   'other' is a banner handle for an existing banner window.  This is used
+ *   to specify the relative position among children of the new banner's
+ *   parent, if 'where' is either OS_BANNER_BEFORE or OS_BANNER_AFTER.  If
+ *   'where' is OS_BANNER_FIRST or OS_BANNER_LAST, 'other' is ignored.
+ *   
+ *   'wintype' is the type of the window.  This is one of the
+ *   OS_BANNER_TYPE_xxx codes indicating what kind of window is desired.
+ *   
+ *   'align' is the banner's alignment, given as an OS_BANNER_ALIGN_xxx
+ *   value.  Top/bottom banners are horizontal: they run across the full
+ *   width of the existing main text area.  Left/right banners are vertical:
+ *   they run down the full height of the existing main text area.
+ *   
+ *   'siz' is the requested size of the new banner.  The meaning of 'siz'
+ *   depends on the value of 'siz_units', which can be OS_BANNER_SIZE_PCT to
+ *   set the size as a percentage of the REMAINING space, or
+ *   OS_BANNER_SIZE_ABS to set an absolute size in the "natural" units of the
+ *   window.  The natural units vary by window type: for text and text grid
+ *   windows, this is in rows/columns of '0' characters in the default font
+ *   for the window.  Note that when OS_BANNER_SIZE_ABS is used in a text or
+ *   text grid window, the OS implementation MUST add the space needed for
+ *   margins and borders when determining the actual pixel size of the
+ *   window; in other words, the window should be large enough that it can
+ *   actually display the given number or rows or columns.
+ *   
+ *   The size is interpreted as a width or height according to the window's
+ *   orientation.  For a TOP or BOTTOM banner, the size is the height; for a
+ *   LEFT or RIGHT banner, the size is the width.  A banner has only one
+ *   dimension's size given, since the other dimension's size is determined
+ *   automatically by the layout rules.
+ *   
+ *   Note that the window's size can be changed later using
+ *   banner_size_to_contents() or banner_set_size().
+ *   
+ *   'style' is a combination of OS_BANNER_STYLE_xxx flags - see below.  The
+ *   style flags give the REQUESTED style for the banner, which might or
+ *   might not be respected, depending on the platform's capabilities, user
+ *   preferences, and other factors.  os_banner_getinfo() can be used to
+ *   determine which style flags are actually used.
+ *   
+ *   Returns the "handle" to the new banner window, which is an opaque value
+ *   that is used in subsequent os_banner_xxx calls to operate on the window.
+ *   Returns null if the window cannot be created.  An implementation is not
+ *   required to support this functionality at all, and can subset it if it
+ *   does support it (for example, an implementation could support only
+ *   top/bottom-aligned banners, but not left/right-aligned), so callers must
+ *   be prepared for this routine to return null.  
+ */
+void *os_banner_create(void *parent, int where, void *other, int wintype,
+                       int align, int siz, int siz_units,
+                       unsigned long style);
+
+
+/*
+ *   insertion positions 
+ */
+#define OS_BANNER_FIRST   1
+#define OS_BANNER_LAST    2
+#define OS_BANNER_BEFORE  3
+#define OS_BANNER_AFTER   4
+
+/*
+ *   banner types 
+ */
+
+/* 
+ *   Normal text stream window.  This is a text stream that behaves
+ *   essentially like the main text window: text is displayed to this
+ *   through os_banner_disp(), always in a stream-like fashion by adding new
+ *   text to the end of any exiting text.
+ *   
+ *   Systems that use proportional fonts should usually simply use the same
+ *   font they use by default in the main text window.  However, note that
+ *   the OS_BANNER_STYLE_TAB_ALIGN style flag might imply that a fixed-pitch
+ *   font should be used even when proportional fonts are available, because
+ *   a fixed-pitch font will allow the calling code to rely on using spaces
+ *   to align text within the window.  
+ */
+#define OS_BANNER_TYPE_TEXT       1
+
+/* 
+ *   "Text grid" window.  This type of window is similar to an normal text
+ *   window (OS_BANNER_TYPE_TEXT), but is guaranteed to arrange its text in
+ *   a regular grid of character cells, all of the same size.  This means
+ *   that the output position can be moved to an arbitrary point within the
+ *   window at any time, so the calling program can precisely control the
+ *   layout of the text in the window.
+ *   
+ *   Because the output position can be moved to arbitrary positions in the
+ *   window, it is possible to overwrite text previously displayed.  When
+ *   this happens, the old text is completely obliterated by the new text,
+ *   leaving no trace of the overwritten text.
+ *   
+ *   In order to guarantee that character cells are all the same size, this
+ *   type of window does not allow any text attributes.  The implementation
+ *   should simply ignore any attempts to change text attributes in this
+ *   type of window.  However, colors can be used to the same degree they
+ *   can be used in an ordinary text window.
+ *   
+ *   To guarantee the regular spacing of character cells, all
+ *   implementations must use fixed-pitch fonts for these windows.  This
+ *   applies even to platforms where proportional fonts are available.  
+ */
+#define OS_BANNER_TYPE_TEXTGRID   2
+
+
+/* 
+ *   banner alignment types 
+ */
+#define OS_BANNER_ALIGN_TOP       0
+#define OS_BANNER_ALIGN_BOTTOM    1
+#define OS_BANNER_ALIGN_LEFT      2
+#define OS_BANNER_ALIGN_RIGHT     3
+
+/*
+ *   size units 
+ */
+#define OS_BANNER_SIZE_PCT  1
+#define OS_BANNER_SIZE_ABS  2
+
+
+/* 
+ *   banner style flags 
+ */
+
+/* 
+ *   The banner has a visible border; this indicates that a line is to be
+ *   drawn to separate the banner from the adjacent window or windows
+ *   "inside" the banner.  So, a top-aligned banner will have its border
+ *   drawn along its bottom edge; a left-aligned banner will show a border
+ *   along its right edge; and so forth.
+ *   
+ *   Note that character-mode platforms generally do NOT respect the border
+ *   style, since doing so takes up too much screen space.  
+ */
+#define OS_BANNER_STYLE_BORDER     0x00000001
+
+/*
+ *   The banner has a vertical/horizontal scrollbar.  Character-mode
+ *   platforms generally do not support scrollbars.  
+ */
+#define OS_BANNER_STYLE_VSCROLL    0x00000002
+#define OS_BANNER_STYLE_HSCROLL    0x00000004
+
+/* 
+ *   Automatically scroll the banner vertically/horizontally whenever new
+ *   text is displayed in the window.  In other words, whenever
+ *   os_banner_disp() is called, scroll the window so that the text that the
+ *   new cursor position after the new text is displayed is visible in the
+ *   window.
+ *   
+ *   Note that this style is independent of the presence of scrollbars.
+ *   Even if there are no scrollbars, we can still scroll the window's
+ *   contents programmatically.
+ *   
+ *   Implementations can, if desired, keep an internal buffer of the
+ *   window's contents, so that the contents can be recalled via the
+ *   scrollbars if the text displayed in the banner exceeds the space
+ *   available in the banner's window on the screen.  If the implementation
+ *   does keep such a buffer, we recommend the following method for managing
+ *   this buffer.  If the AUTO_VSCROLL flag is not set, then the banner's
+ *   contents should be truncated at the bottom when the contents overflow
+ *   the buffer; that is, once the banner's internal buffer is full, any new
+ *   text that the calling program attempts to add to the banner should
+ *   simply be discarded.  If the AUTO_VSCROLL flag is set, then the OLDEST
+ *   text should be discarded instead, so that the most recent text is
+ *   always retained.  
+ */
+#define OS_BANNER_STYLE_AUTO_VSCROLL 0x00000008
+#define OS_BANNER_STYLE_AUTO_HSCROLL 0x00000010
+
+/*
+ *   Tab-based alignment is required/supported.  On creation, this is a hint
+ *   to the implementation that is sometimes necessary to determine what
+ *   kind of font to use in the new window, for non-HTML platforms.  If this
+ *   flag is set on creation, the caller is indicating that it wants to use
+ *   <TAB> tags to align text in the window.
+ *   
+ *   Character-mode implementations that use a single font with fixed pitch
+ *   can simply ignore this.  These implementations ALWAYS have a working
+ *   <TAB> capability, because the portable output formatter provides <TAB>
+ *   interpretation for a fixed-pitch window.
+ *   
+ *   Full HTML TADS implementations can also ignore this.  HTML TADS
+ *   implementations always have full <TAB> support via the HTML
+ *   parser/renderer.
+ *   
+ *   Text-only implementations on GUI platforms (i.e., implementations that
+ *   are not based on the HTML parser/renderer engine in HTML TADS, but
+ *   which run on GUI platforms with proportionally-spaced text) should use
+ *   this flag to determine the font to display.  If this flag is NOT set,
+ *   then the caller doesn't care about <TAB>, and the implementation is
+ *   free to use a proportionally-spaced font in the window if desired.
+ *   
+ *   When retrieving information on an existing banner, this flag indicates
+ *   that <TAB> alignment is actually supported on the window.  
+ */
+#define OS_BANNER_STYLE_TAB_ALIGN 0x00000020
+
+/*
+ *   Use "MORE" mode in this window.  By default, a banner window should
+ *   happily allow text to overflow the vertical limits of the window; the
+ *   only special thing that should happen on overflow is that the window
+ *   should be srolled down to show the latest text, if the auto-vscroll
+ *   style is set.  With this flag, though, a banner window acts just like
+ *   the main text window: when the window fills up vertically, we show a
+ *   MORE prompt (using appropriate system conventions), and wait for the
+ *   user to indicate that they're ready to see more text.  On most systems,
+ *   the user acknowledges a MORE prompt by pressing a key or scrolling with
+ *   the mouse, but it's up to the system implementor to decide what's
+ *   appropriate for the system.
+ *   
+ *   Note that MORE mode in ANY banner window should generally override all
+ *   other user input focus.  In other words, if the game in the main window
+ *   would like to read a keystroke from the user, but one of the banner
+ *   windows is pausing with a MORE prompt, any keyboard input should be
+ *   directed to the banner paused at the MORE prompt, not to the main
+ *   window; the main window should not receive any key events until the MORE
+ *   prompt has been removed.
+ *   
+ *   This style requires the auto-vscroll style.  Implementations should
+ *   assume auto-vscroll when this style is set.  This style can be ignored
+ *   with text grid windows.  
+ */
+#define OS_BANNER_STYLE_MOREMODE  0x00000040
+
+/*
+ *   This banner is a horizontal/vertical "strut" for sizing purposes.  This
+ *   means that the banner's content size is taken into account when figuring
+ *   the content size of its *parent* banner.  If the banner has the same
+ *   orientation as the parent, its content size is added to its parent's
+ *   internal content size to determine the parent's overall content size.
+ *   If the banner's orientation is orthogonal to the parent's, then the
+ *   parent's overall content size is the larger of the parent's internal
+ *   content size and this banner's content size.  
+ */
+#define OS_BANNER_STYLE_HSTRUT    0x00000080
+#define OS_BANNER_STYLE_VSTRUT    0x00000100
+
+
+/* 
+ *   Delete a banner.  This removes the banner from the display, which
+ *   requires recalculating the entire screen's layout to reallocate this
+ *   banner's space to other windows.  When this routine returns, the banner
+ *   handle is invalid and can no longer be used in any os_banner_xxx
+ *   function calls.  
+ *   
+ *   If the banner has children, the children will no longer be displayed,
+ *   but will remain valid in memory until deleted.  A child window's
+ *   display area always comes out of its parent's space, so once the parent
+ *   is gone, a child has no way to acquire any display space; resizing the
+ *   child won't help, since it simply has no way to obtain any screen space
+ *   once its parent has been deleted.  Even though the window's children
+ *   will become invisible, their banner handles will remain valid; the
+ *   caller is responsible for explicitly deleting the children even after
+ *   deleting their parent.  
+ */
+void os_banner_delete(void *banner_handle);
+
+/*
+ *   "Orphan" a banner.  This tells the osifc implementation that the caller
+ *   wishes to sever all of its ties with the banner (as part of program
+ *   termination, for example), but that the calling program does not
+ *   actually require that the banner's on-screen display be immediately
+ *   removed.
+ *   
+ *   The osifc implementation can do one of two things:
+ *   
+ *   1.  Simply call os_banner_delete().  If the osifc implementation
+ *   doesn't want to do anything extra with the banner, it can simply delete
+ *   the banner, since the caller has no more use for it.
+ *   
+ *   2.  Take ownership of the banner.  If the osifc implementation wishes
+ *   to continue displaying the final screen configuration after a program
+ *   has terminated, it can simply take over the banner and leave it on the
+ *   screen.  The osifc subsystem must eventually delete the banner itself
+ *   if it takes this routine; for example, if the osifc subsystem allows
+ *   another client program to be loaded into the same window after a
+ *   previous program has terminated, it would want to delete any orphaned
+ *   banners from the previous program when loading a new program.  
+ */
+void os_banner_orphan(void *banner_handle);
+
+/*
+ *   Banner information structure.  This is filled in by the system-specific
+ *   implementation in os_banner_getinfo().  
+ */
+struct os_banner_info_t
+{
+    /* alignment */
+    int align;
+
+    /* style flags - these indicate the style flags actually in use */
+    unsigned long style;
+
+    /* 
+     *   Actual on-screen size of the banner, in rows and columns.  If the
+     *   banner is displayed in a proportional font or can display multiple
+     *   fonts of different sizes, this is approximated by the number of "0"
+     *   characters in the window's default font that will fit in the
+     *   window's display area.  
+     */
+    int rows;
+    int columns;
+
+    /*
+     *   Actual on-screen size of the banner in pixels.  This is meaningful
+     *   only for full HTML interpreter; for text-only interpreters, these
+     *   are always set to zero.
+     *   
+     *   Note that even if we're running on a GUI operating system, these
+     *   aren't meaningful unless this is a full HTML interpreter.  Text-only
+     *   interpreters should always set these to zero, even on GUI OS's.  
+     */
+    int pix_width;
+    int pix_height;
+
+    /* 
+     *   OS line wrapping flag.  If this is set, the window uses OS-level
+     *   line wrapping because the window uses a proportional font, so the
+     *   caller does not need to (and should not) perform line breaking in
+     *   text displayed in the window.
+     *   
+     *   Note that OS line wrapping is a PERMANENT feature of the window.
+     *   Callers can note this information once and expect it to remain
+     *   fixed through the window's lifetime.  
+     */
+    int os_line_wrap;
+};
+typedef struct os_banner_info_t os_banner_info_t;
+
+/* 
+ *   Get information on the banner - fills in the information structure with
+ *   the banner's current settings.  Note that this should indicate the
+ *   ACTUAL properties of the banner, not the requested properties; this
+ *   allows callers to determine how the banner is actually displayed, which
+ *   depends upon the platform's capabilities and user preferences.
+ *   
+ *   Returns true if the information was successfully obtained, false if
+ *   not.  This can return false if the underlying OS window has already
+ *   been closed by a user action, for example.  
+ */
+int os_banner_getinfo(void *banner_handle, os_banner_info_t *info);
+
+/* 
+ *   Get the character width/height of the banner, for layout purposes.  This
+ *   gives the size of the banner in character cells.
+ *   
+ *   These are not meaningful when the underlying window uses a proportional
+ *   font or varying fonts of different sizes.  When the size of text varies
+ *   in the window, the OS layer is responsible for word-wrapping and other
+ *   layout, in which case these simply return zero.
+ *   
+ *   Note that these routines might appear to be redundant with the 'rows'
+ *   and 'columns' information returned from os_banner_getinfo(), but these
+ *   have two important distinctions.  First, these routines return only the
+ *   width and height information, so they can be implemented with less
+ *   overhead than os_banner_getinfo(); this is important because formatters
+ *   might need to call these routines frequently while formatting text.
+ *   Second, these routines are not required to return an approximation for
+ *   windows using proportional fonts, as os_banner_getinfo() does; these can
+ *   simply return zero when a proportional font is in use.  
+ */
+int os_banner_get_charwidth(void *banner_handle);
+int os_banner_get_charheight(void *banner_handle);
+
+/* clear the contents of a banner */
+void os_banner_clear(void *banner_handle);
+
+/* 
+ *   Display output on a banner.  Writes the output to the window on the
+ *   display at the current output position.
+ *   
+ *   The following special characters should be recognized and handled:
+ *   
+ *   '\n' - newline; move output position to the start of the next line.
+ *   
+ *   '\r' - move output position to start of current line; subsequent text
+ *   overwrites any text previously displayed on the current line.  It is
+ *   permissible to delete the old text immediately on seeing the '\r',
+ *   rather than waiting for additional text to actually overwrite it.
+ *   
+ *   All other characters should simply be displayed as ordinary printing
+ *   text characters.  Note that tab characters should not be passed to this
+ *   routine, but if they are, they can simply be treated as ordinary spaces
+ *   if desired.  Other control characters (backspace, escape, etc) should
+ *   never be passed to this routine; the implementation is free to ignore
+ *   any control characters not listed above.
+ *   
+ *   If any text displayed here overflows the current boundaries of the
+ *   window on the screen, the text MUST be "clipped" to the current window
+ *   boundaries; in other words, anything this routine tries to display
+ *   outside of the window's on-screen rectangle must not actually be shown
+ *   on the screen.
+ *   
+ *   Text overflowing the display boundaries MUST also be retained in an
+ *   internal buffer.  This internal buffer can be limited to the actual
+ *   maximum display size of the terminal screen or application window, if
+ *   desired.  It is necessary to retain clipped text, because this allows a
+ *   window to be expanded to the size of its contents AFTER the contents
+ *   have already been displayed.
+ *   
+ *   If the banner does its own line wrapping, it must indicate this via the
+ *   os_line_wrap flag in the os_banner_getinfo() return data.  If the
+ *   banner doesn't indicate this flag, then it must not do any line
+ *   wrapping at all, even if the caller attempts to write text beyond the
+ *   right edge of the window - any text overflowing the width of the window
+ *   must simply be clipped.
+ *   
+ *   Text grid banners must ALWAYS clip - these banners should never perform
+ *   any line wrapping.  
+ */
+void os_banner_disp(void *banner_handle, const char *txt, size_t len);
+
+/*
+ *   Set the text attributes in a banner, for subsequent text displays.
+ *   'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values. 
+ */
+void os_banner_set_attr(void *banner_handle, int attr);
+
+/* 
+ *   Set the text color in a banner, for subsequent text displays.  The 'fg'
+ *   and 'bg' colors are given as RGB or parameterized colors; see the
+ *   definition of os_color_t for details.
+ *   
+ *   If the underlying renderer is HTML-enabled, then this should not be
+ *   used; the appropriate HTML code should simply be displayed to the
+ *   banner instead.  
+ */
+void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg);
+
+/* 
+ *   Set the screen color in the banner - this is analogous to the screen
+ *   color in the main text area.
+ *   
+ *   If the underlying renderer is HTML-enabled, then this should not be
+ *   used; the HTML <BODY> tag should be used instead.  
+ */
+void os_banner_set_screen_color(void *banner_handle, os_color_t color);
+
+/* flush output on a banner */
+void os_banner_flush(void *banner_handle);
+
+/*
+ *   Set the banner's size.  The size has the same meaning as in
+ *   os_banner_create().
+ *   
+ *   'is_advisory' indicates whether the sizing is required or advisory only.
+ *   If this flag is false, then the size should be set as requested.  If
+ *   this flag is true, it means that the caller intends to call
+ *   os_banner_size_to_contents() at some point, and that the size being set
+ *   now is for advisory purposes only.  Platforms that support
+ *   size-to-contents may simply ignore advisory sizing requests, although
+ *   they might want to ensure that they have sufficient off-screen buffer
+ *   space to keep track of the requested size of display, so that the
+ *   information the caller displays in preparation for calling
+ *   size-to-contents will be retained.  Platforms that do not support
+ *   size-to-contents should set the requested size even when 'is_advisory'
+ *   is true.  
+ */
+void os_banner_set_size(void *banner_handle, int siz, int siz_units,
+                        int is_advisory);
+
+/* 
+ *   Set the banner to the size of its current contents.  This can be used
+ *   to set the banner's size after some text (or other material) has been
+ *   displayed to the banner, so that the size can be set according to the
+ *   banner's actual space requirements.
+ *   
+ *   This changes the banner's "requested size" to match the current size.
+ *   Subsequent calls to os_banner_getinfo() will thus indicate a requested
+ *   size according to the size set here.  
+ */
+void os_banner_size_to_contents(void *banner_handle);
+
+/* 
+ *   Turn HTML mode on/off in the banner window.  If the underlying renderer
+ *   doesn't support HTML, these have no effect.  
+ */
+void os_banner_start_html(void *banner_handle);
+void os_banner_end_html(void *banner_handle);
+
+/*
+ *   Set the output coordinates in a text grid window.  The grid window is
+ *   arranged into character cells numbered from row zero, column zero for
+ *   the upper left cell.  This function can only be used if the window was
+ *   created with type OS_BANNER_TYPE_TEXTGRID; the request should simply be
+ *   ignored by other window types.
+ *   
+ *   Moving the output position has no immediate effect on the display, and
+ *   does not itself affect the "content size" for the purposes of
+ *   os_banner_size_to_contents().  This simply sets the coordinates where
+ *   any subsequent text is displayed.  
+ */
+void os_banner_goto(void *banner_handle, int row, int col);
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/os_buffer.cpp b/engines/glk/tads/os_buffer.cpp
index 0f00e9d..539a379 100644
--- a/engines/glk/tads/os_buffer.cpp
+++ b/engines/glk/tads/os_buffer.cpp
@@ -21,6 +21,7 @@
  */
 
 #include "glk/tads/os_buffer.h"
+#include "glk/tads/os_parse.h"
 #include "glk/tads/tads.h"
 
 namespace Glk {
@@ -50,10 +51,6 @@ char *os_fill_buffer(char *buf, size_t len)
 static uint32 *input = 0;
 static uint max = 0;
 
-extern uint os_parse_chars(const char *buf, uint buflen, uint32 *out, uint outlen);
-
-extern uint os_prepare_chars(uint32 *buf, uint buflen, char *out, uint outlen);
-
 void os_put_buffer(const char *buf, size_t len) {
     uint *out;
     uint outlen;
@@ -66,7 +63,7 @@ void os_put_buffer(const char *buf, size_t len) {
         return;
     outlen = len;
 
-    outlen = os_parse_chars(buf, len, out, outlen);
+    outlen = os_parse_chars((const unsigned char *)buf, len, out, outlen);
 
     if (outlen)
         g_vm->glk_put_buffer_uni(out, outlen);
@@ -81,13 +78,13 @@ void os_get_buffer(char *buf, size_t len, size_t init) {
     max = len;
 
     if (init)
-        os_parse_chars(buf, init + 1, input, len);
+        os_parse_chars((const unsigned char *)buf, init + 1, input, len);
 
     g_vm->glk_request_line_event_uni(mainwin, input, len - 1, init);
 }
 
 char *os_fill_buffer(char *buf, size_t len) {
-    uint res = os_prepare_chars(input, len, buf, max);
+    uint res = os_prepare_chars(input, len, (unsigned char *)buf, max);
     buf[res] = '\0';
 
     free(input);
diff --git a/engines/glk/tads/os_glk.cpp b/engines/glk/tads/os_glk.cpp
index 5565086..884703c 100644
--- a/engines/glk/tads/os_glk.cpp
+++ b/engines/glk/tads/os_glk.cpp
@@ -44,6 +44,11 @@ uint mainbg;
 uint statusfg;
 uint statusbg;
 
+int G_os_pagelength;
+int G_os_linewidth;
+int G_os_moremode;
+char G_os_gamename[OSFNMAX];
+
 /* ------------------------------------------------------------------------ */
 
 /* 
@@ -993,10 +998,106 @@ long os_get_sys_clock_ms() {
 	return g_system->getMillis();
 }
 
+void os_xlat_html4(unsigned int html4_char, char *result, size_t result_len) {
+	/* Return all standard Latin-1 characters as-is */
+	if (html4_char <= 128 || (html4_char >= 160 && html4_char <= 255))
+		result[0] = (unsigned char)html4_char;
+	else {
+		switch (html4_char) {
+		case 130:                                      /* single back quote */
+			result[0] = '`'; break;
+		case 132:                                      /* double back quote */
+			result[0] = '\"'; break;
+		case 153:                                             /* trade mark */
+			strcpy(result, "(tm)"); return;
+		case 140:                                            /* OE ligature */
+		case 338:                                            /* OE ligature */
+			strcpy(result, "OE"); return;
+		case 339:                                            /* oe ligature */
+			strcpy(result, "oe"); return;
+		case 159:                                                   /* Yuml */
+			result[0] = (char)255;
+		case 376:                                        /* Y with diaresis */
+			result[0] = 'Y'; break;
+		case 352:                                           /* S with caron */
+			result[0] = 'S'; break;
+		case 353:                                           /* s with caron */
+			result[0] = 's'; break;
+		case 150:                                                /* en dash */
+		case 8211:                                               /* en dash */
+			result[0] = '-'; break;
+		case 151:                                                /* em dash */
+		case 8212:                                               /* em dash */
+			strcpy(result, "--"); return;
+		case 145:                                      /* left single quote */
+		case 8216:                                     /* left single quote */
+			result[0] = '`'; break;
+		case 146:                                     /* right single quote */
+		case 8217:                                    /* right single quote */
+		case 8218:                                    /* single low-9 quote */
+			result[0] = '\''; break;
+		case 147:                                      /* left double quote */
+		case 148:                                     /* right double quote */
+		case 8220:                                     /* left double quote */
+		case 8221:                                    /* right double quote */
+		case 8222:                                    /* double low-9 quote */
+			result[0] = '\"'; break;
+		case 8224:                                                /* dagger */
+		case 8225:                                         /* double dagger */
+		case 8240:                                        /* per mille sign */
+			result[0] = ' '; break;
+		case 139:                       /* single left-pointing angle quote */
+		case 8249:                      /* single left-pointing angle quote */
+			result[0] = '<'; break;
+		case 155:                      /* single right-pointing angle quote */
+		case 8250:                     /* single right-pointing angle quote */
+			result[0] = '>'; break;
+		case 8482:                                           /* small tilde */
+			result[0] = '~'; break;
+
+		default:
+			/* unmappable character - return space */
+			result[0] = (unsigned char)' ';
+		}
+	}
+	result[1] = 0;
+}
+
 #ifndef os_tzset
 void os_tzset() {}
 #endif
 
+void os_nonstop_mode(int flag) {}
+
+void os_advise_load_charmap(const char *id, const char *ldesc, const char *sysinfo) {}
+
+void os_gen_charmap_filename(char *filename, char *internal_id, char *argv0) {}
+
+int os_input_dialog(int icon_id, const char *prompt, int standard_button_set,
+	const char **buttons, int button_count, int default_index, int cancel_index) {
+	// CUrrently unsupported
+	return 0;
+}
+
+void os_flush() {
+	g_vm->glk_tick();
+}
+
+char *os_strlwr(char *s) {
+	for (char *p = s; *p; ++p)
+		*p = tolower(*p);
+	return s;
+}
+
+void os_expause() {
+#ifdef USE_EXPAUSE
+	os_printz("(Strike any key to exit...)");
+	os_flush();
+	os_waitc();
+#endif /* USE_EXPAUSE */
+}
+
+void os_plain(void) {}
 
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
index 237502a..7b76876 100644
--- a/engines/glk/tads/os_glk.h
+++ b/engines/glk/tads/os_glk.h
@@ -42,6 +42,7 @@ namespace TADS {
 #define OSPATHURL "/"
 #define OSPATHSEP ':'
 #define OS_NEWLINE_SEQ "\n"
+#define DBG_OFF
 
 /* maximum width (in characters) of a line of text */
 #define OS_MAXWIDTH  255
@@ -2802,8 +2803,7 @@ void os_xlat_html4(unsigned int html4_char,
  *   This parameter is provided so that the system code can look for
  *   mapping files in the original TADS executables directory, if desired.
  */
-void os_gen_charmap_filename(char *filename, char *internal_id,
-                             char *argv0);
+void os_gen_charmap_filename(char *filename, char *internal_id, char *argv0);
 
 /*
  *   Receive notification that a character mapping file has been loaded.
@@ -2912,599 +2912,6 @@ void os_get_charmap(char *mapname, int charmap_id);
  */
 #define OS_CHARMAP_CMDLINE     4
 
-
-/* ------------------------------------------------------------------------ */
-/*
- *   External Banner Interface.  This interface provides the ability to
- *   divide the display window into multiple sub-windows, each with its own
- *   independent contents.
- *   
- *   To determine where a new banner is displayed, we look at the banners as
- *   a tree, rooted at the "main window," the special banner that the system
- *   automatically creates initially for the main game text.  We start by
- *   allocating the entire display (or the entire application window, if
- *   we're running on a GUI system) to the main window.  We then traverse
- *   the tree, starting with the root window's children.  For each child
- *   window, we allocate space for the child out of the parent window's
- *   area, according to the child's alignment and size settings, and deduct
- *   this space from the parent window's size.  We then lay out the children
- *   of the child.
- *   
- *   For each banner window, we take its requested space out of the parent
- *   window's area by starting at the edge of the parent window rectangle as
- *   indicated by the banner's alignment, and taking the requested `width
- *   (for a left/right banner) or height (for a top/bottom banner), limiting
- *   to the available width/height in the parent window's space.  Give the
- *   banner the full extent of the parent's space in its other dimension (so
- *   a left/right banner gets the full height of the parent space, and a
- *   top/bottom banner gets the full width).
- *   
- *   Note that the layout proceeds exclusively down the tree (i.e., from the
- *   root to children to grandchildren, and so on).  It *appears* that a
- *   child affects its parent, because of the deduction step: a child
- *   acquires screen space by carving out a chunk of its parent.  The right
- *   way to think about this, though, is that the parent's full area is the
- *   union of the parent window and all of its children; when viewed this
- *   way, the parent's full area is fully determined the instant the parent
- *   is laid out, and never changes as its children are laid out.  Note in
- *   particular that a child can never make a parent larger; the only thing
- *   a child can do to a parent is carve out a chunk of the parent for
- *   itself, which doesn't affect the boundaries of the union of the parent
- *   plus its children.
- *   
- *   Note also that if the banner has a border, and the implementation
- *   actually draws borders, the border must be drawn for the *full* area of
- *   the banner, as defined above.  For example, suppose we have two
- *   borders: banner A is a child of the main window, is top-aligned, and
- *   has a border.  Banner B is a child of banner A, right-aligned, with no
- *   border.  Obviously, without considering banner B, banner A's space runs
- *   across the entire width of the main window, so its border (at the
- *   bottom of its area) runs across the entire width of the main window.
- *   Banner B carves out some space from A's right side for itself, so
- *   banner A's actual on-screen area runs from the left edge of the main
- *   window to banner B's left edge.  However, even though banner A itself
- *   no longer runs the full width of the main window, banner A's *full*
- *   area - that is, the union of banner A's on-screen area and all of its
- *   children's full areas - does still run the entire width of the main
- *   window, hence banner A's border must still run the full width of the
- *   main window.  The simple way of looking at this is that a banner's
- *   border is always to be drawn exactly the same way, regardless of
- *   whether or not the banner has children - simply draw the banner as it
- *   would be drawn if the banner had no children.
- *   
- *   Each time a banner is added or removed, we must recalculate the layout
- *   of the remaining banners and main text area.  The os_banner_xxx()
- *   implementation is responsible for this layout refiguring.
- *   
- *   The entire external banner window interface is optional, although the
- *   functions must at least be defined as dummies to avoid linker errors
- *   when building.  If a platform doesn't implement this feature,
- *   os_banner_create() should simply return null, and the other routines
- *   can do nothing.  
- */
-
-/* 
- *   Create a banner window.  'info' gives the desired parameters for the new
- *   banner.
- *   
- *   Note that certain requested parameter settings might or might not be
- *   respected, depending on the capabilities of the platform and user
- *   preferences.  os_banner_getinfo() can be used after creation to
- *   determine which parameter settings are actually used in the new banner.
- *   
- *   'parent' gives the parent of this banner; this is the banner handle of
- *   another banner window, or null.  If 'parent' is null, then the new
- *   banner is a child of the main window, which the system creates
- *   automatically at startup and which contains the main input/output
- *   transcript.  The new banner's on-screen area is carved out of the
- *   parent's space, according to the alignment and size settings of the new
- *   window, so this determines how the window is laid out on the screen.
- *   
- *   'where' is OS_BANNER_FIRST to make the new window the first child of its
- *   parent; OS_BANNER_LAST to make it the last child of its parent;
- *   OS_BANNER_BEFORE to insert it immediately before the existing banner
- *   identified by handle in 'other'; or OS_BANNER_AFTER to insert
- *   immediately after 'other'.  When BEFORE or AFTER is used, 'other' must
- *   be another child of the same parent; if it is not, the routine should
- *   act as though 'where' were given as OS_BANNER_LAST.
- *   
- *   'other' is a banner handle for an existing banner window.  This is used
- *   to specify the relative position among children of the new banner's
- *   parent, if 'where' is either OS_BANNER_BEFORE or OS_BANNER_AFTER.  If
- *   'where' is OS_BANNER_FIRST or OS_BANNER_LAST, 'other' is ignored.
- *   
- *   'wintype' is the type of the window.  This is one of the
- *   OS_BANNER_TYPE_xxx codes indicating what kind of window is desired.
- *   
- *   'align' is the banner's alignment, given as an OS_BANNER_ALIGN_xxx
- *   value.  Top/bottom banners are horizontal: they run across the full
- *   width of the existing main text area.  Left/right banners are vertical:
- *   they run down the full height of the existing main text area.
- *   
- *   'siz' is the requested size of the new banner.  The meaning of 'siz'
- *   depends on the value of 'siz_units', which can be OS_BANNER_SIZE_PCT to
- *   set the size as a percentage of the REMAINING space, or
- *   OS_BANNER_SIZE_ABS to set an absolute size in the "natural" units of the
- *   window.  The natural units vary by window type: for text and text grid
- *   windows, this is in rows/columns of '0' characters in the default font
- *   for the window.  Note that when OS_BANNER_SIZE_ABS is used in a text or
- *   text grid window, the OS implementation MUST add the space needed for
- *   margins and borders when determining the actual pixel size of the
- *   window; in other words, the window should be large enough that it can
- *   actually display the given number or rows or columns.
- *   
- *   The size is interpreted as a width or height according to the window's
- *   orientation.  For a TOP or BOTTOM banner, the size is the height; for a
- *   LEFT or RIGHT banner, the size is the width.  A banner has only one
- *   dimension's size given, since the other dimension's size is determined
- *   automatically by the layout rules.
- *   
- *   Note that the window's size can be changed later using
- *   banner_size_to_contents() or banner_set_size().
- *   
- *   'style' is a combination of OS_BANNER_STYLE_xxx flags - see below.  The
- *   style flags give the REQUESTED style for the banner, which might or
- *   might not be respected, depending on the platform's capabilities, user
- *   preferences, and other factors.  os_banner_getinfo() can be used to
- *   determine which style flags are actually used.
- *   
- *   Returns the "handle" to the new banner window, which is an opaque value
- *   that is used in subsequent os_banner_xxx calls to operate on the window.
- *   Returns null if the window cannot be created.  An implementation is not
- *   required to support this functionality at all, and can subset it if it
- *   does support it (for example, an implementation could support only
- *   top/bottom-aligned banners, but not left/right-aligned), so callers must
- *   be prepared for this routine to return null.  
- */
-void *os_banner_create(void *parent, int where, void *other, int wintype,
-                       int align, int siz, int siz_units,
-                       unsigned long style);
-
-
-/*
- *   insertion positions 
- */
-#define OS_BANNER_FIRST   1
-#define OS_BANNER_LAST    2
-#define OS_BANNER_BEFORE  3
-#define OS_BANNER_AFTER   4
-
-/*
- *   banner types 
- */
-
-/* 
- *   Normal text stream window.  This is a text stream that behaves
- *   essentially like the main text window: text is displayed to this
- *   through os_banner_disp(), always in a stream-like fashion by adding new
- *   text to the end of any exiting text.
- *   
- *   Systems that use proportional fonts should usually simply use the same
- *   font they use by default in the main text window.  However, note that
- *   the OS_BANNER_STYLE_TAB_ALIGN style flag might imply that a fixed-pitch
- *   font should be used even when proportional fonts are available, because
- *   a fixed-pitch font will allow the calling code to rely on using spaces
- *   to align text within the window.  
- */
-#define OS_BANNER_TYPE_TEXT       1
-
-/* 
- *   "Text grid" window.  This type of window is similar to an normal text
- *   window (OS_BANNER_TYPE_TEXT), but is guaranteed to arrange its text in
- *   a regular grid of character cells, all of the same size.  This means
- *   that the output position can be moved to an arbitrary point within the
- *   window at any time, so the calling program can precisely control the
- *   layout of the text in the window.
- *   
- *   Because the output position can be moved to arbitrary positions in the
- *   window, it is possible to overwrite text previously displayed.  When
- *   this happens, the old text is completely obliterated by the new text,
- *   leaving no trace of the overwritten text.
- *   
- *   In order to guarantee that character cells are all the same size, this
- *   type of window does not allow any text attributes.  The implementation
- *   should simply ignore any attempts to change text attributes in this
- *   type of window.  However, colors can be used to the same degree they
- *   can be used in an ordinary text window.
- *   
- *   To guarantee the regular spacing of character cells, all
- *   implementations must use fixed-pitch fonts for these windows.  This
- *   applies even to platforms where proportional fonts are available.  
- */
-#define OS_BANNER_TYPE_TEXTGRID   2
-
-
-/* 
- *   banner alignment types 
- */
-#define OS_BANNER_ALIGN_TOP       0
-#define OS_BANNER_ALIGN_BOTTOM    1
-#define OS_BANNER_ALIGN_LEFT      2
-#define OS_BANNER_ALIGN_RIGHT     3
-
-/*
- *   size units 
- */
-#define OS_BANNER_SIZE_PCT  1
-#define OS_BANNER_SIZE_ABS  2
-
-
-/* 
- *   banner style flags 
- */
-
-/* 
- *   The banner has a visible border; this indicates that a line is to be
- *   drawn to separate the banner from the adjacent window or windows
- *   "inside" the banner.  So, a top-aligned banner will have its border
- *   drawn along its bottom edge; a left-aligned banner will show a border
- *   along its right edge; and so forth.
- *   
- *   Note that character-mode platforms generally do NOT respect the border
- *   style, since doing so takes up too much screen space.  
- */
-#define OS_BANNER_STYLE_BORDER     0x00000001
-
-/*
- *   The banner has a vertical/horizontal scrollbar.  Character-mode
- *   platforms generally do not support scrollbars.  
- */
-#define OS_BANNER_STYLE_VSCROLL    0x00000002
-#define OS_BANNER_STYLE_HSCROLL    0x00000004
-
-/* 
- *   Automatically scroll the banner vertically/horizontally whenever new
- *   text is displayed in the window.  In other words, whenever
- *   os_banner_disp() is called, scroll the window so that the text that the
- *   new cursor position after the new text is displayed is visible in the
- *   window.
- *   
- *   Note that this style is independent of the presence of scrollbars.
- *   Even if there are no scrollbars, we can still scroll the window's
- *   contents programmatically.
- *   
- *   Implementations can, if desired, keep an internal buffer of the
- *   window's contents, so that the contents can be recalled via the
- *   scrollbars if the text displayed in the banner exceeds the space
- *   available in the banner's window on the screen.  If the implementation
- *   does keep such a buffer, we recommend the following method for managing
- *   this buffer.  If the AUTO_VSCROLL flag is not set, then the banner's
- *   contents should be truncated at the bottom when the contents overflow
- *   the buffer; that is, once the banner's internal buffer is full, any new
- *   text that the calling program attempts to add to the banner should
- *   simply be discarded.  If the AUTO_VSCROLL flag is set, then the OLDEST
- *   text should be discarded instead, so that the most recent text is
- *   always retained.  
- */
-#define OS_BANNER_STYLE_AUTO_VSCROLL 0x00000008
-#define OS_BANNER_STYLE_AUTO_HSCROLL 0x00000010
-
-/*
- *   Tab-based alignment is required/supported.  On creation, this is a hint
- *   to the implementation that is sometimes necessary to determine what
- *   kind of font to use in the new window, for non-HTML platforms.  If this
- *   flag is set on creation, the caller is indicating that it wants to use
- *   <TAB> tags to align text in the window.
- *   
- *   Character-mode implementations that use a single font with fixed pitch
- *   can simply ignore this.  These implementations ALWAYS have a working
- *   <TAB> capability, because the portable output formatter provides <TAB>
- *   interpretation for a fixed-pitch window.
- *   
- *   Full HTML TADS implementations can also ignore this.  HTML TADS
- *   implementations always have full <TAB> support via the HTML
- *   parser/renderer.
- *   
- *   Text-only implementations on GUI platforms (i.e., implementations that
- *   are not based on the HTML parser/renderer engine in HTML TADS, but
- *   which run on GUI platforms with proportionally-spaced text) should use
- *   this flag to determine the font to display.  If this flag is NOT set,
- *   then the caller doesn't care about <TAB>, and the implementation is
- *   free to use a proportionally-spaced font in the window if desired.
- *   
- *   When retrieving information on an existing banner, this flag indicates
- *   that <TAB> alignment is actually supported on the window.  
- */
-#define OS_BANNER_STYLE_TAB_ALIGN 0x00000020
-
-/*
- *   Use "MORE" mode in this window.  By default, a banner window should
- *   happily allow text to overflow the vertical limits of the window; the
- *   only special thing that should happen on overflow is that the window
- *   should be srolled down to show the latest text, if the auto-vscroll
- *   style is set.  With this flag, though, a banner window acts just like
- *   the main text window: when the window fills up vertically, we show a
- *   MORE prompt (using appropriate system conventions), and wait for the
- *   user to indicate that they're ready to see more text.  On most systems,
- *   the user acknowledges a MORE prompt by pressing a key or scrolling with
- *   the mouse, but it's up to the system implementor to decide what's
- *   appropriate for the system.
- *   
- *   Note that MORE mode in ANY banner window should generally override all
- *   other user input focus.  In other words, if the game in the main window
- *   would like to read a keystroke from the user, but one of the banner
- *   windows is pausing with a MORE prompt, any keyboard input should be
- *   directed to the banner paused at the MORE prompt, not to the main
- *   window; the main window should not receive any key events until the MORE
- *   prompt has been removed.
- *   
- *   This style requires the auto-vscroll style.  Implementations should
- *   assume auto-vscroll when this style is set.  This style can be ignored
- *   with text grid windows.  
- */
-#define OS_BANNER_STYLE_MOREMODE  0x00000040
-
-/*
- *   This banner is a horizontal/vertical "strut" for sizing purposes.  This
- *   means that the banner's content size is taken into account when figuring
- *   the content size of its *parent* banner.  If the banner has the same
- *   orientation as the parent, its content size is added to its parent's
- *   internal content size to determine the parent's overall content size.
- *   If the banner's orientation is orthogonal to the parent's, then the
- *   parent's overall content size is the larger of the parent's internal
- *   content size and this banner's content size.  
- */
-#define OS_BANNER_STYLE_HSTRUT    0x00000080
-#define OS_BANNER_STYLE_VSTRUT    0x00000100
-
-
-/* 
- *   Delete a banner.  This removes the banner from the display, which
- *   requires recalculating the entire screen's layout to reallocate this
- *   banner's space to other windows.  When this routine returns, the banner
- *   handle is invalid and can no longer be used in any os_banner_xxx
- *   function calls.  
- *   
- *   If the banner has children, the children will no longer be displayed,
- *   but will remain valid in memory until deleted.  A child window's
- *   display area always comes out of its parent's space, so once the parent
- *   is gone, a child has no way to acquire any display space; resizing the
- *   child won't help, since it simply has no way to obtain any screen space
- *   once its parent has been deleted.  Even though the window's children
- *   will become invisible, their banner handles will remain valid; the
- *   caller is responsible for explicitly deleting the children even after
- *   deleting their parent.  
- */
-void os_banner_delete(void *banner_handle);
-
-/*
- *   "Orphan" a banner.  This tells the osifc implementation that the caller
- *   wishes to sever all of its ties with the banner (as part of program
- *   termination, for example), but that the calling program does not
- *   actually require that the banner's on-screen display be immediately
- *   removed.
- *   
- *   The osifc implementation can do one of two things:
- *   
- *   1.  Simply call os_banner_delete().  If the osifc implementation
- *   doesn't want to do anything extra with the banner, it can simply delete
- *   the banner, since the caller has no more use for it.
- *   
- *   2.  Take ownership of the banner.  If the osifc implementation wishes
- *   to continue displaying the final screen configuration after a program
- *   has terminated, it can simply take over the banner and leave it on the
- *   screen.  The osifc subsystem must eventually delete the banner itself
- *   if it takes this routine; for example, if the osifc subsystem allows
- *   another client program to be loaded into the same window after a
- *   previous program has terminated, it would want to delete any orphaned
- *   banners from the previous program when loading a new program.  
- */
-void os_banner_orphan(void *banner_handle);
-
-/*
- *   Banner information structure.  This is filled in by the system-specific
- *   implementation in os_banner_getinfo().  
- */
-struct os_banner_info_t
-{
-    /* alignment */
-    int align;
-
-    /* style flags - these indicate the style flags actually in use */
-    unsigned long style;
-
-    /* 
-     *   Actual on-screen size of the banner, in rows and columns.  If the
-     *   banner is displayed in a proportional font or can display multiple
-     *   fonts of different sizes, this is approximated by the number of "0"
-     *   characters in the window's default font that will fit in the
-     *   window's display area.  
-     */
-    int rows;
-    int columns;
-
-    /*
-     *   Actual on-screen size of the banner in pixels.  This is meaningful
-     *   only for full HTML interpreter; for text-only interpreters, these
-     *   are always set to zero.
-     *   
-     *   Note that even if we're running on a GUI operating system, these
-     *   aren't meaningful unless this is a full HTML interpreter.  Text-only
-     *   interpreters should always set these to zero, even on GUI OS's.  
-     */
-    int pix_width;
-    int pix_height;
-
-    /* 
-     *   OS line wrapping flag.  If this is set, the window uses OS-level
-     *   line wrapping because the window uses a proportional font, so the
-     *   caller does not need to (and should not) perform line breaking in
-     *   text displayed in the window.
-     *   
-     *   Note that OS line wrapping is a PERMANENT feature of the window.
-     *   Callers can note this information once and expect it to remain
-     *   fixed through the window's lifetime.  
-     */
-    int os_line_wrap;
-};
-typedef struct os_banner_info_t os_banner_info_t;
-
-/* 
- *   Get information on the banner - fills in the information structure with
- *   the banner's current settings.  Note that this should indicate the
- *   ACTUAL properties of the banner, not the requested properties; this
- *   allows callers to determine how the banner is actually displayed, which
- *   depends upon the platform's capabilities and user preferences.
- *   
- *   Returns true if the information was successfully obtained, false if
- *   not.  This can return false if the underlying OS window has already
- *   been closed by a user action, for example.  
- */
-int os_banner_getinfo(void *banner_handle, os_banner_info_t *info);
-
-/* 
- *   Get the character width/height of the banner, for layout purposes.  This
- *   gives the size of the banner in character cells.
- *   
- *   These are not meaningful when the underlying window uses a proportional
- *   font or varying fonts of different sizes.  When the size of text varies
- *   in the window, the OS layer is responsible for word-wrapping and other
- *   layout, in which case these simply return zero.
- *   
- *   Note that these routines might appear to be redundant with the 'rows'
- *   and 'columns' information returned from os_banner_getinfo(), but these
- *   have two important distinctions.  First, these routines return only the
- *   width and height information, so they can be implemented with less
- *   overhead than os_banner_getinfo(); this is important because formatters
- *   might need to call these routines frequently while formatting text.
- *   Second, these routines are not required to return an approximation for
- *   windows using proportional fonts, as os_banner_getinfo() does; these can
- *   simply return zero when a proportional font is in use.  
- */
-int os_banner_get_charwidth(void *banner_handle);
-int os_banner_get_charheight(void *banner_handle);
-
-/* clear the contents of a banner */
-void os_banner_clear(void *banner_handle);
-
-/* 
- *   Display output on a banner.  Writes the output to the window on the
- *   display at the current output position.
- *   
- *   The following special characters should be recognized and handled:
- *   
- *   '\n' - newline; move output position to the start of the next line.
- *   
- *   '\r' - move output position to start of current line; subsequent text
- *   overwrites any text previously displayed on the current line.  It is
- *   permissible to delete the old text immediately on seeing the '\r',
- *   rather than waiting for additional text to actually overwrite it.
- *   
- *   All other characters should simply be displayed as ordinary printing
- *   text characters.  Note that tab characters should not be passed to this
- *   routine, but if they are, they can simply be treated as ordinary spaces
- *   if desired.  Other control characters (backspace, escape, etc) should
- *   never be passed to this routine; the implementation is free to ignore
- *   any control characters not listed above.
- *   
- *   If any text displayed here overflows the current boundaries of the
- *   window on the screen, the text MUST be "clipped" to the current window
- *   boundaries; in other words, anything this routine tries to display
- *   outside of the window's on-screen rectangle must not actually be shown
- *   on the screen.
- *   
- *   Text overflowing the display boundaries MUST also be retained in an
- *   internal buffer.  This internal buffer can be limited to the actual
- *   maximum display size of the terminal screen or application window, if
- *   desired.  It is necessary to retain clipped text, because this allows a
- *   window to be expanded to the size of its contents AFTER the contents
- *   have already been displayed.
- *   
- *   If the banner does its own line wrapping, it must indicate this via the
- *   os_line_wrap flag in the os_banner_getinfo() return data.  If the
- *   banner doesn't indicate this flag, then it must not do any line
- *   wrapping at all, even if the caller attempts to write text beyond the
- *   right edge of the window - any text overflowing the width of the window
- *   must simply be clipped.
- *   
- *   Text grid banners must ALWAYS clip - these banners should never perform
- *   any line wrapping.  
- */
-void os_banner_disp(void *banner_handle, const char *txt, size_t len);
-
-/*
- *   Set the text attributes in a banner, for subsequent text displays.
- *   'attr' is a (bitwise-OR'd) combination of OS_ATTR_xxx values. 
- */
-void os_banner_set_attr(void *banner_handle, int attr);
-
-/* 
- *   Set the text color in a banner, for subsequent text displays.  The 'fg'
- *   and 'bg' colors are given as RGB or parameterized colors; see the
- *   definition of os_color_t for details.
- *   
- *   If the underlying renderer is HTML-enabled, then this should not be
- *   used; the appropriate HTML code should simply be displayed to the
- *   banner instead.  
- */
-void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg);
-
-/* 
- *   Set the screen color in the banner - this is analogous to the screen
- *   color in the main text area.
- *   
- *   If the underlying renderer is HTML-enabled, then this should not be
- *   used; the HTML <BODY> tag should be used instead.  
- */
-void os_banner_set_screen_color(void *banner_handle, os_color_t color);
-
-/* flush output on a banner */
-void os_banner_flush(void *banner_handle);
-
-/*
- *   Set the banner's size.  The size has the same meaning as in
- *   os_banner_create().
- *   
- *   'is_advisory' indicates whether the sizing is required or advisory only.
- *   If this flag is false, then the size should be set as requested.  If
- *   this flag is true, it means that the caller intends to call
- *   os_banner_size_to_contents() at some point, and that the size being set
- *   now is for advisory purposes only.  Platforms that support
- *   size-to-contents may simply ignore advisory sizing requests, although
- *   they might want to ensure that they have sufficient off-screen buffer
- *   space to keep track of the requested size of display, so that the
- *   information the caller displays in preparation for calling
- *   size-to-contents will be retained.  Platforms that do not support
- *   size-to-contents should set the requested size even when 'is_advisory'
- *   is true.  
- */
-void os_banner_set_size(void *banner_handle, int siz, int siz_units,
-                        int is_advisory);
-
-/* 
- *   Set the banner to the size of its current contents.  This can be used
- *   to set the banner's size after some text (or other material) has been
- *   displayed to the banner, so that the size can be set according to the
- *   banner's actual space requirements.
- *   
- *   This changes the banner's "requested size" to match the current size.
- *   Subsequent calls to os_banner_getinfo() will thus indicate a requested
- *   size according to the size set here.  
- */
-void os_banner_size_to_contents(void *banner_handle);
-
-/* 
- *   Turn HTML mode on/off in the banner window.  If the underlying renderer
- *   doesn't support HTML, these have no effect.  
- */
-void os_banner_start_html(void *banner_handle);
-void os_banner_end_html(void *banner_handle);
-
-/*
- *   Set the output coordinates in a text grid window.  The grid window is
- *   arranged into character cells numbered from row zero, column zero for
- *   the upper left cell.  This function can only be used if the window was
- *   created with type OS_BANNER_TYPE_TEXTGRID; the request should simply be
- *   ignored by other window types.
- *   
- *   Moving the output position has no immediate effect on the display, and
- *   does not itself affect the "content size" for the purposes of
- *   os_banner_size_to_contents().  This simply sets the coordinates where
- *   any subsequent text is displayed.  
- */
-void os_banner_goto(void *banner_handle, int row, int col);
-
-
 /* ------------------------------------------------------------------------ */
 /*
  *   Get system information.  'code' is a SYSINFO_xxx code, which
diff --git a/engines/glk/tads/os_parse.cpp b/engines/glk/tads/os_parse.cpp
new file mode 100644
index 0000000..d722d14
--- /dev/null
+++ b/engines/glk/tads/os_parse.cpp
@@ -0,0 +1,989 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/os_parse.h"
+#include "glk/glk_types.h"
+
+namespace Glk {
+namespace TADS {
+
+#ifdef GLK_MODULE_UNICODE
+
+enum CHARMAPS { OS_UTF8, OS_CP1251, OS_CP1252, OS_MACROMAN, OS_UNKNOWN };
+
+static uint os_charmap = OS_UTF8;
+
+uint is_cyrillic(unsigned char ch)
+{
+    if (ch >= 0xBC)
+        return 1;
+
+    switch (ch)
+    {
+        case 0x80:
+        case 0x81:
+        case 0x83:
+        case 0x8A:
+        case 0x8C:
+        case 0x8D:
+        case 0x8E:
+        case 0x8F:
+        case 0x90:
+        case 0x9A:
+        case 0x9C:
+        case 0x9D:
+        case 0x9E:
+        case 0x9F:
+        case 0xA1:
+        case 0xA2:
+        case 0xA3:
+        case 0xA5:
+        case 0xA8:
+        case 0xAA:
+        case 0xAF:
+        case 0xB2:
+        case 0xB3:
+        case 0xB4:
+        case 0xB8:
+        case 0xBA:
+            return 1;
+
+        default:
+            return 0;
+    }
+}
+
+uint is_macroman (unsigned char ch)
+{
+    switch (ch)
+    {
+        /* trademarks */
+        case 0xA8:
+        case 0xAA:
+            return 1;
+
+        /* dashes and right quotes */
+        case 0xD0:
+        case 0xD1:
+        case 0xD3:
+        case 0xD5:
+            return 1;
+
+        /* accents */
+        case 0x8E:
+        case 0x8F:
+        case 0x9A:
+            return 1;
+
+        default:
+            return 0;
+    }
+}
+
+uint is_cp1252 (unsigned char ch)
+{
+    switch (ch)
+    {
+        /* trademarks */
+        case 0x99:
+        case 0xAE:
+            return 1;
+
+        /* dashes and right quotes */
+        case 0x92:
+        case 0x94:
+        case 0x96:
+        case 0x97:
+            return 1;
+
+        /* accents */
+        case 0xE8:
+        case 0xE9:
+        case 0xF6:
+            return 1;
+
+        default:
+            return 0;
+    }
+}
+
+uint identify_chars(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+    uint pos = 0;
+    uint val = 0;
+    uint count_macroman = 0;
+    uint count_cp1252 = 0;
+    uint wordlen = 0;
+    uint cyrilen = 0;
+    uint charmap = OS_UNKNOWN;
+
+    while (pos < buflen)
+    {
+        val = buf[pos++];
+
+        count_macroman += is_macroman(val);
+        count_cp1252 += is_cp1252(val);
+
+        if (val != 0x20)
+        {
+            wordlen++;
+            cyrilen += is_cyrillic(val);
+        }
+        else
+        {
+            if (wordlen == cyrilen)
+            {
+                charmap = OS_CP1251;
+                break;
+            }
+            wordlen = 0;
+            cyrilen = 0;
+        }
+    }
+
+    if (charmap == OS_CP1251)
+        os_charmap = OS_CP1251;
+    else if (count_cp1252 >= count_macroman)
+        os_charmap = OS_CP1252;
+    else
+        os_charmap = OS_MACROMAN;
+
+    return os_parse_chars(buf, buflen, out, outlen);
+}
+
+uint parse_utf8(const unsigned char *buf, uint buflen, uint32 *out, uint outlen)
+{
+    uint pos = 0;
+    uint outpos = 0;
+    uint res;
+    uint val0, val1, val2, val3;
+
+    while (outpos < outlen)
+    {
+        if (pos >= buflen)
+            break;
+
+        val0 = buf[pos++];
+
+        if (val0 < 0x80)
+        {
+            res = val0;
+            out[outpos++] = res;
+            continue;
+        }
+
+        if ((val0 & 0xe0) == 0xc0)
+        {
+            if (pos+1 > buflen)
+            {
+                //printf("incomplete two-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            val1 = buf[pos++];
+            if ((val1 & 0xc0) != 0x80)
+            {
+                //printf("malformed two-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            res = (val0 & 0x1f) << 6;
+            res |= (val1 & 0x3f);
+            out[outpos++] = res;
+            continue;
+        }
+
+        if ((val0 & 0xf0) == 0xe0)
+        {
+            if (pos+2 > buflen)
+            {
+                //printf("incomplete three-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            val1 = buf[pos++];
+            val2 = buf[pos++];
+            if ((val1 & 0xc0) != 0x80)
+            {
+                //printf("malformed three-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            if ((val2 & 0xc0) != 0x80)
+            {
+                //printf("malformed three-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            res = (((val0 & 0xf)<<12)  & 0x0000f000);
+            res |= (((val1 & 0x3f)<<6) & 0x00000fc0);
+            res |= (((val2 & 0x3f))    & 0x0000003f);
+            out[outpos++] = res;
+            continue;
+        }
+
+        if ((val0 & 0xf0) == 0xf0)
+        {
+            if ((val0 & 0xf8) != 0xf0)
+            {
+                //printf("malformed four-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            if (pos+3 > buflen)
+            {
+                //printf("incomplete four-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            val1 = buf[pos++];
+            val2 = buf[pos++];
+            val3 = buf[pos++];
+            if ((val1 & 0xc0) != 0x80)
+            {
+                //printf("malformed four-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            if ((val2 & 0xc0) != 0x80)
+            {
+                //printf("malformed four-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            if ((val3 & 0xc0) != 0x80)
+            {
+                //printf("malformed four-byte character\n");
+                return identify_chars(buf, buflen, out, outlen);
+            }
+            res = (((val0 & 0x7)<<18)   & 0x1c0000);
+            res |= (((val1 & 0x3f)<<12) & 0x03f000);
+            res |= (((val2 & 0x3f)<<6)  & 0x000fc0);
+            res |= (((val3 & 0x3f))     & 0x00003f);
+            out[outpos++] = res;
+            continue;
+        }
+
+        //printf("malformed character\n");
+        return identify_chars(buf, buflen, out, outlen);
+    }
+
+    return outpos;
+}
+
+uint prepare_utf8(const uint32 *buf, uint buflen, unsigned char *out, uint outlen)
+{
+    uint i=0, k=0;
+
+    /*convert UTF-32 to UTF-8 */
+    while (i < buflen && k < outlen)
+    {
+        if ((buf[i] < 0x80))
+        {
+            out[k] = buf[i];
+            k++;
+        }
+        else if ((buf[i] < 0x800) && (k < outlen - 1))
+        {
+            out[k  ] = (0xC0 | ((buf[i] & 0x7C0) >> 6));
+            out[k+1] = (0x80 |  (buf[i] & 0x03F)      );
+            k = k + 2;
+        }
+        else if ((buf[i] < 0x10000) && (k < outlen - 2))
+        {
+            out[k  ] = (0xE0 | ((buf[i] & 0xF000) >> 12));
+            out[k+1] = (0x80 | ((buf[i] & 0x0FC0) >>  6));
+            out[k+2] = (0x80 |  (buf[i] & 0x003F)       );
+            k = k + 3;
+        }
+        else if ((buf[i] < 0x200000) && (k < outlen - 3))
+        {
+            out[k  ] = (0xF0 | ((buf[i] & 0x1C0000) >> 18));
+            out[k+1] = (0x80 | ((buf[i] & 0x03F000) >> 12));
+            out[k+2] = (0x80 | ((buf[i] & 0x000FC0) >>  6));
+            out[k+3] = (0x80 |  (buf[i] & 0x00003F)       );
+            k = k + 4;
+        }
+        else
+        {
+            out[k] = '?';
+            k++;
+        }
+        i++;
+    }
+
+    return k;
+}
+
+/* http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT */
+
+static const uint CP1251ToUnicode[128] = {
+    0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030,
+    0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, 0x0452, 0x2018, 0x2019, 0x201C,
+    0x201D, 0x2022, 0x2013, 0x2014, 0x003F, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C,
+    0x045B, 0x045F, 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7,
+    0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, 0x00B0, 0x00B1,
+    0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB,
+    0x0458, 0x0405, 0x0455, 0x0457, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415,
+    0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+    0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429,
+    0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, 0x0430, 0x0431, 0x0432, 0x0433,
+    0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D,
+    0x043E, 0x043F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+    0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F
+
+};
+
+uint parse_cp1251(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+    uint pos = 0;
+    uint outpos = 0;
+    uint res;
+
+    while (outpos < outlen)
+    {
+        if (pos >= buflen)
+            break;
+
+        res = buf[pos++];
+
+        if (res < 0x80)
+            out[outpos++] = res;
+        else
+            out[outpos++] = CP1251ToUnicode[res - 0x80];
+    }
+
+    return outpos;
+}
+
+uint prepare_cp1251(const uint *buf, uint buflen,
+                      unsigned char *out, uint outlen)
+{
+    uint pos = 0;
+    uint outpos = 0;
+    uint res;
+
+    while (outpos < outlen)
+    {
+        if (pos >= buflen)
+            break;
+
+        res = buf[pos++];
+
+        if (res < 0x80)
+        {
+            out[outpos++] = res;
+            continue;
+        }
+
+        switch (res)
+        {
+            case 0x0402: out[outpos++] = 0x80; break;
+            case 0x0403: out[outpos++] = 0x81; break;
+            case 0x201A: out[outpos++] = 0x82; break;
+            case 0x0453: out[outpos++] = 0x83; break;
+            case 0x201E: out[outpos++] = 0x84; break;
+            case 0x2026: out[outpos++] = 0x85; break;
+            case 0x2020: out[outpos++] = 0x86; break;
+            case 0x2021: out[outpos++] = 0x87; break;
+            case 0x20AC: out[outpos++] = 0x88; break;
+            case 0x2030: out[outpos++] = 0x89; break;
+            case 0x0409: out[outpos++] = 0x8A; break;
+            case 0x2039: out[outpos++] = 0x8B; break;
+            case 0x040A: out[outpos++] = 0x8C; break;
+            case 0x040C: out[outpos++] = 0x8D; break;
+            case 0x040B: out[outpos++] = 0x8E; break;
+            case 0x040F: out[outpos++] = 0x8F; break;
+
+            case 0x0452: out[outpos++] = 0x90; break;
+            case 0x2018: out[outpos++] = 0x91; break;
+            case 0x2019: out[outpos++] = 0x92; break;
+            case 0x201C: out[outpos++] = 0x93; break;
+            case 0x201D: out[outpos++] = 0x94; break;
+            case 0x2022: out[outpos++] = 0x95; break;
+            case 0x2013: out[outpos++] = 0x96; break;
+            case 0x2014: out[outpos++] = 0x97; break;
+            case 0x2122: out[outpos++] = 0x99; break;
+            case 0x0459: out[outpos++] = 0x9A; break;
+            case 0x203A: out[outpos++] = 0x9B; break;
+            case 0x045A: out[outpos++] = 0x9C; break;
+            case 0x045C: out[outpos++] = 0x9D; break;
+            case 0x045B: out[outpos++] = 0x9E; break;
+            case 0x045F: out[outpos++] = 0x9F; break;
+
+            case 0x00A0: out[outpos++] = 0xA0; break;
+            case 0x040E: out[outpos++] = 0xA1; break;
+            case 0x045E: out[outpos++] = 0xA2; break;
+            case 0x0408: out[outpos++] = 0xA3; break;
+            case 0x00A4: out[outpos++] = 0xA4; break;
+            case 0x0490: out[outpos++] = 0xA5; break;
+            case 0x00A6: out[outpos++] = 0xA6; break;
+            case 0x00A7: out[outpos++] = 0xA7; break;
+            case 0x0401: out[outpos++] = 0xA8; break;
+            case 0x00A9: out[outpos++] = 0xA9; break;
+            case 0x0404: out[outpos++] = 0xAA; break;
+            case 0x00AB: out[outpos++] = 0xAB; break;
+            case 0x00AC: out[outpos++] = 0xAC; break;
+            case 0x00AD: out[outpos++] = 0xAD; break;
+            case 0x00AE: out[outpos++] = 0xAE; break;
+            case 0x0407: out[outpos++] = 0xAF; break;
+
+            case 0x00B0: out[outpos++] = 0xB0; break;
+            case 0x00B1: out[outpos++] = 0xB1; break;
+            case 0x0406: out[outpos++] = 0xB2; break;
+            case 0x0456: out[outpos++] = 0xB3; break;
+            case 0x0491: out[outpos++] = 0xB4; break;
+            case 0x00B5: out[outpos++] = 0xB5; break;
+            case 0x00B6: out[outpos++] = 0xB6; break;
+            case 0x00B7: out[outpos++] = 0xB7; break;
+            case 0x0451: out[outpos++] = 0xB8; break;
+            case 0x2116: out[outpos++] = 0xB9; break;
+            case 0x0454: out[outpos++] = 0xBA; break;
+            case 0x00BB: out[outpos++] = 0xBB; break;
+            case 0x0458: out[outpos++] = 0xBC; break;
+            case 0x0405: out[outpos++] = 0xBD; break;
+            case 0x0455: out[outpos++] = 0xBE; break;
+            case 0x0457: out[outpos++] = 0xBF; break;
+
+            case 0x0410: out[outpos++] = 0xC0; break;
+            case 0x0411: out[outpos++] = 0xC1; break;
+            case 0x0412: out[outpos++] = 0xC2; break;
+            case 0x0413: out[outpos++] = 0xC3; break;
+            case 0x0414: out[outpos++] = 0xC4; break;
+            case 0x0415: out[outpos++] = 0xC5; break;
+            case 0x0416: out[outpos++] = 0xC6; break;
+            case 0x0417: out[outpos++] = 0xC7; break;
+            case 0x0418: out[outpos++] = 0xC8; break;
+            case 0x0419: out[outpos++] = 0xC9; break;
+            case 0x041A: out[outpos++] = 0xCA; break;
+            case 0x041B: out[outpos++] = 0xCB; break;
+            case 0x041C: out[outpos++] = 0xCC; break;
+            case 0x041D: out[outpos++] = 0xCD; break;
+            case 0x041E: out[outpos++] = 0xCE; break;
+            case 0x041F: out[outpos++] = 0xCF; break;
+
+            case 0x0420: out[outpos++] = 0xD0; break;
+            case 0x0421: out[outpos++] = 0xD1; break;
+            case 0x0422: out[outpos++] = 0xD2; break;
+            case 0x0423: out[outpos++] = 0xD3; break;
+            case 0x0424: out[outpos++] = 0xD4; break;
+            case 0x0425: out[outpos++] = 0xD5; break;
+            case 0x0426: out[outpos++] = 0xD6; break;
+            case 0x0427: out[outpos++] = 0xD7; break;
+            case 0x0428: out[outpos++] = 0xD8; break;
+            case 0x0429: out[outpos++] = 0xD9; break;
+            case 0x042A: out[outpos++] = 0xDA; break;
+            case 0x042B: out[outpos++] = 0xDB; break;
+            case 0x042C: out[outpos++] = 0xDC; break;
+            case 0x042D: out[outpos++] = 0xDD; break;
+            case 0x042E: out[outpos++] = 0xDE; break;
+            case 0x042F: out[outpos++] = 0xDF; break;
+
+            case 0x0430: out[outpos++] = 0xE0; break;
+            case 0x0431: out[outpos++] = 0xE1; break;
+            case 0x0432: out[outpos++] = 0xE2; break;
+            case 0x0433: out[outpos++] = 0xE3; break;
+            case 0x0434: out[outpos++] = 0xE4; break;
+            case 0x0435: out[outpos++] = 0xE5; break;
+            case 0x0436: out[outpos++] = 0xE6; break;
+            case 0x0437: out[outpos++] = 0xE7; break;
+            case 0x0438: out[outpos++] = 0xE8; break;
+            case 0x0439: out[outpos++] = 0xE9; break;
+            case 0x043A: out[outpos++] = 0xEA; break;
+            case 0x043B: out[outpos++] = 0xEB; break;
+            case 0x043C: out[outpos++] = 0xEC; break;
+            case 0x043D: out[outpos++] = 0xED; break;
+            case 0x043E: out[outpos++] = 0xEE; break;
+            case 0x043F: out[outpos++] = 0xEF; break;
+
+            case 0x0440: out[outpos++] = 0xF0; break;
+            case 0x0441: out[outpos++] = 0xF1; break;
+            case 0x0442: out[outpos++] = 0xF2; break;
+            case 0x0443: out[outpos++] = 0xF3; break;
+            case 0x0444: out[outpos++] = 0xF4; break;
+            case 0x0445: out[outpos++] = 0xF5; break;
+            case 0x0446: out[outpos++] = 0xF6; break;
+            case 0x0447: out[outpos++] = 0xF7; break;
+            case 0x0448: out[outpos++] = 0xF8; break;
+            case 0x0449: out[outpos++] = 0xF9; break;
+            case 0x044A: out[outpos++] = 0xFA; break;
+            case 0x044B: out[outpos++] = 0xFB; break;
+            case 0x044C: out[outpos++] = 0xFC; break;
+            case 0x044D: out[outpos++] = 0xFD; break;
+            case 0x044E: out[outpos++] = 0xFE; break;
+            case 0x044F: out[outpos++] = 0xFF; break;
+
+            default:
+                /* undefined */
+                out[outpos++] = '?';
+                break;
+        }
+
+        continue;
+    }
+
+    return outpos;
+}
+
+/* http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT */
+
+static const uint CP1252ToUnicode[128] = {
+    0x20AC, 0x003F, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030,
+    0x0160, 0x2039, 0x0152, 0x003F, 0x017D, 0x003F, 0x003F, 0x2018, 0x2019, 0x201C,
+    0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x003F,
+    0x017E, 0x0178, 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+    0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 0x00B0, 0x00B1,
+    0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB,
+    0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5,
+    0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+    0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9,
+    0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 0x00E0, 0x00E1, 0x00E2, 0x00E3,
+    0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED,
+    0x00EE, 0x00EF, 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
+    0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
+};
+
+uint parse_cp1252(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+    uint pos = 0;
+    uint outpos = 0;
+    uint res;
+
+    while (outpos < outlen)
+    {
+        if (pos >= buflen)
+            break;
+
+        res = buf[pos++];
+
+        if (res < 0x80)
+            out[outpos++] = res;
+        else
+            out[outpos++] = CP1252ToUnicode[res - 0x80];
+    }
+
+    return outpos;
+}
+
+uint prepare_cp1252(const uint *buf, uint buflen,
+                      unsigned char *out, uint outlen)
+{
+    uint pos = 0;
+    uint outpos = 0;
+    uint res;
+
+    while (outpos < outlen)
+    {
+        if (pos >= buflen)
+            break;
+
+        res = buf[pos++];
+
+        if (res < 0x80)
+        {
+            out[outpos++] = res;
+            continue;
+        }
+
+        switch (res)
+        {
+            case 0x20AC: out[outpos++] = 0x80; break;
+            case 0x201A: out[outpos++] = 0x82; break;
+            case 0x0192: out[outpos++] = 0x83; break;
+            case 0x201E: out[outpos++] = 0x84; break;
+            case 0x2026: out[outpos++] = 0x85; break;
+            case 0x2020: out[outpos++] = 0x86; break;
+            case 0x2021: out[outpos++] = 0x87; break;
+            case 0x02C6: out[outpos++] = 0x88; break;
+            case 0x2030: out[outpos++] = 0x89; break;
+            case 0x0160: out[outpos++] = 0x8A; break;
+            case 0x2039: out[outpos++] = 0x8B; break;
+            case 0x0152: out[outpos++] = 0x8C; break;
+            case 0x017D: out[outpos++] = 0x8E; break;
+
+            case 0x2018: out[outpos++] = 0x91; break;
+            case 0x2019: out[outpos++] = 0x92; break;
+            case 0x201C: out[outpos++] = 0x93; break;
+            case 0x201D: out[outpos++] = 0x94; break;
+            case 0x2022: out[outpos++] = 0x95; break;
+            case 0x2013: out[outpos++] = 0x96; break;
+            case 0x2014: out[outpos++] = 0x97; break;
+            case 0x02DC: out[outpos++] = 0x98; break;
+            case 0x2122: out[outpos++] = 0x99; break;
+            case 0x0161: out[outpos++] = 0x9A; break;
+            case 0x203A: out[outpos++] = 0x9B; break;
+            case 0x0153: out[outpos++] = 0x9C; break;
+            case 0x017E: out[outpos++] = 0x9E; break;
+            case 0x0178: out[outpos++] = 0x9F; break;
+
+            case 0x00A0: out[outpos++] = 0xA0; break;
+            case 0x00A1: out[outpos++] = 0xA1; break;
+            case 0x00A2: out[outpos++] = 0xA2; break;
+            case 0x00A3: out[outpos++] = 0xA3; break;
+            case 0x00A4: out[outpos++] = 0xA4; break;
+            case 0x00A5: out[outpos++] = 0xA5; break;
+            case 0x00A6: out[outpos++] = 0xA6; break;
+            case 0x00A7: out[outpos++] = 0xA7; break;
+            case 0x00A8: out[outpos++] = 0xA8; break;
+            case 0x00A9: out[outpos++] = 0xA9; break;
+            case 0x00AA: out[outpos++] = 0xAA; break;
+            case 0x00AB: out[outpos++] = 0xAB; break;
+            case 0x00AC: out[outpos++] = 0xAC; break;
+            case 0x00AD: out[outpos++] = 0xAD; break;
+            case 0x00AE: out[outpos++] = 0xAE; break;
+            case 0x00AF: out[outpos++] = 0xAF; break;
+
+            case 0x00B0: out[outpos++] = 0xB0; break;
+            case 0x00B1: out[outpos++] = 0xB1; break;
+            case 0x00B2: out[outpos++] = 0xB2; break;
+            case 0x00B3: out[outpos++] = 0xB3; break;
+            case 0x00B4: out[outpos++] = 0xB4; break;
+            case 0x00B5: out[outpos++] = 0xB5; break;
+            case 0x00B6: out[outpos++] = 0xB6; break;
+            case 0x00B7: out[outpos++] = 0xB7; break;
+            case 0x00B8: out[outpos++] = 0xB8; break;
+            case 0x00B9: out[outpos++] = 0xB9; break;
+            case 0x00BA: out[outpos++] = 0xBA; break;
+            case 0x00BB: out[outpos++] = 0xBB; break;
+            case 0x00BC: out[outpos++] = 0xBC; break;
+            case 0x00BD: out[outpos++] = 0xBD; break;
+            case 0x00BE: out[outpos++] = 0xBE; break;
+            case 0x00BF: out[outpos++] = 0xBF; break;
+
+            case 0x00C0: out[outpos++] = 0xC0; break;
+            case 0x00C1: out[outpos++] = 0xC1; break;
+            case 0x00C2: out[outpos++] = 0xC2; break;
+            case 0x00C3: out[outpos++] = 0xC3; break;
+            case 0x00C4: out[outpos++] = 0xC4; break;
+            case 0x00C5: out[outpos++] = 0xC5; break;
+            case 0x00C6: out[outpos++] = 0xC6; break;
+            case 0x00C7: out[outpos++] = 0xC7; break;
+            case 0x00C8: out[outpos++] = 0xC8; break;
+            case 0x00C9: out[outpos++] = 0xC9; break;
+            case 0x00CA: out[outpos++] = 0xCA; break;
+            case 0x00CB: out[outpos++] = 0xCB; break;
+            case 0x00CC: out[outpos++] = 0xCC; break;
+            case 0x00CD: out[outpos++] = 0xCD; break;
+            case 0x00CE: out[outpos++] = 0xCE; break;
+            case 0x00CF: out[outpos++] = 0xCF; break;
+
+            case 0x00D0: out[outpos++] = 0xD0; break;
+            case 0x00D1: out[outpos++] = 0xD1; break;
+            case 0x00D2: out[outpos++] = 0xD2; break;
+            case 0x00D3: out[outpos++] = 0xD3; break;
+            case 0x00D4: out[outpos++] = 0xD4; break;
+            case 0x00D5: out[outpos++] = 0xD5; break;
+            case 0x00D6: out[outpos++] = 0xD6; break;
+            case 0x00D7: out[outpos++] = 0xD7; break;
+            case 0x00D8: out[outpos++] = 0xD8; break;
+            case 0x00D9: out[outpos++] = 0xD9; break;
+            case 0x00DA: out[outpos++] = 0xDA; break;
+            case 0x00DB: out[outpos++] = 0xDB; break;
+            case 0x00DC: out[outpos++] = 0xDC; break;
+            case 0x00DD: out[outpos++] = 0xDD; break;
+            case 0x00DE: out[outpos++] = 0xDE; break;
+            case 0x00DF: out[outpos++] = 0xDF; break;
+
+            case 0x00E0: out[outpos++] = 0xE0; break;
+            case 0x00E1: out[outpos++] = 0xE1; break;
+            case 0x00E2: out[outpos++] = 0xE2; break;
+            case 0x00E3: out[outpos++] = 0xE3; break;
+            case 0x00E4: out[outpos++] = 0xE4; break;
+            case 0x00E5: out[outpos++] = 0xE5; break;
+            case 0x00E6: out[outpos++] = 0xE6; break;
+            case 0x00E7: out[outpos++] = 0xE7; break;
+            case 0x00E8: out[outpos++] = 0xE8; break;
+            case 0x00E9: out[outpos++] = 0xE9; break;
+            case 0x00EA: out[outpos++] = 0xEA; break;
+            case 0x00EB: out[outpos++] = 0xEB; break;
+            case 0x00EC: out[outpos++] = 0xEC; break;
+            case 0x00ED: out[outpos++] = 0xED; break;
+            case 0x00EE: out[outpos++] = 0xEE; break;
+            case 0x00EF: out[outpos++] = 0xEF; break;
+
+            case 0x00F0: out[outpos++] = 0xF0; break;
+            case 0x00F1: out[outpos++] = 0xF1; break;
+            case 0x00F2: out[outpos++] = 0xF2; break;
+            case 0x00F3: out[outpos++] = 0xF3; break;
+            case 0x00F4: out[outpos++] = 0xF4; break;
+            case 0x00F5: out[outpos++] = 0xF5; break;
+            case 0x00F6: out[outpos++] = 0xF6; break;
+            case 0x00F7: out[outpos++] = 0xF7; break;
+            case 0x00F8: out[outpos++] = 0xF8; break;
+            case 0x00F9: out[outpos++] = 0xF9; break;
+            case 0x00FA: out[outpos++] = 0xFA; break;
+            case 0x00FB: out[outpos++] = 0xFB; break;
+            case 0x00FC: out[outpos++] = 0xFC; break;
+            case 0x00FD: out[outpos++] = 0xFD; break;
+            case 0x00FE: out[outpos++] = 0xFE; break;
+            case 0x00FF: out[outpos++] = 0xFF; break;
+
+            default:
+                /* undefined */
+                out[outpos++] = '?';
+                break;
+        }
+
+        continue;
+    }
+
+    return outpos;
+}
+
+/* http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT */
+
+static const uint MacRomanToUnicode[128] = {
+    0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, 0x00E0, 0x00E2,
+    0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, 0x00EA, 0x00EB, 0x00ED, 0x00EC,
+    0x00EE, 0x00EF, 0x00F1, 0x00F3, 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9,
+    0x00FB, 0x00FC, 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF,
+    0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, 0x221E, 0x00B1,
+    0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, 0x220F, 0x03C0, 0x222B, 0x00AA,
+    0x00BA, 0x03A9, 0x00E6, 0x00F8, 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248,
+    0x2206, 0x00AB, 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,
+    0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, 0x00FF, 0x0178,
+    0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, 0x2021, 0x00B7, 0x201A, 0x201E,
+    0x2030, 0x00C2, 0x00CA, 0x00C1, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC,
+    0x00D3, 0x00D4, 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC,
+    0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7
+};
+
+uint parse_mac(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+    uint pos = 0;
+    uint outpos = 0;
+    uint res;
+
+    while (outpos < outlen)
+    {
+        if (pos >= buflen)
+            break;
+
+        res = buf[pos++];
+
+        if (res < 0x80)
+            out[outpos++] = res;
+        else
+            out[outpos++] = MacRomanToUnicode[res - 0x80];
+    }
+
+    return outpos;
+}
+
+uint prepare_mac(const uint *buf, uint buflen, unsigned char *out, uint outlen) {
+    uint pos = 0;
+    uint outpos = 0;
+    uint res;
+
+    while (outpos < outlen)
+    {
+        if (pos >= buflen)
+            break;
+
+        res = buf[pos++];
+
+        if (res < 0x80)
+        {
+            out[outpos++] = res;
+            continue;
+        }
+
+        switch (res)
+        {
+            case 0x00C4: out[outpos++] = 0x80; break;
+            case 0x00C5: out[outpos++] = 0x81; break;
+            case 0x00C7: out[outpos++] = 0x82; break;
+            case 0x00C9: out[outpos++] = 0x83; break;
+            case 0x00D1: out[outpos++] = 0x84; break;
+            case 0x00D6: out[outpos++] = 0x85; break;
+            case 0x00DC: out[outpos++] = 0x86; break;
+            case 0x00E1: out[outpos++] = 0x87; break;
+            case 0x00E0: out[outpos++] = 0x88; break;
+            case 0x00E2: out[outpos++] = 0x89; break;
+            case 0x00E4: out[outpos++] = 0x8A; break;
+            case 0x00E3: out[outpos++] = 0x8B; break;
+            case 0x00E5: out[outpos++] = 0x8C; break;
+            case 0x00E7: out[outpos++] = 0x8D; break;
+            case 0x00E9: out[outpos++] = 0x8E; break;
+            case 0x00E8: out[outpos++] = 0x8F; break;
+
+            case 0x00EA: out[outpos++] = 0x90; break;
+            case 0x00EB: out[outpos++] = 0x91; break;
+            case 0x00ED: out[outpos++] = 0x92; break;
+            case 0x00EC: out[outpos++] = 0x93; break;
+            case 0x00EE: out[outpos++] = 0x94; break;
+            case 0x00EF: out[outpos++] = 0x95; break;
+            case 0x00F1: out[outpos++] = 0x96; break;
+            case 0x00F3: out[outpos++] = 0x97; break;
+            case 0x00F2: out[outpos++] = 0x98; break;
+            case 0x00F4: out[outpos++] = 0x99; break;
+            case 0x00F6: out[outpos++] = 0x9A; break;
+            case 0x00F5: out[outpos++] = 0x9B; break;
+            case 0x00FA: out[outpos++] = 0x9C; break;
+            case 0x00F9: out[outpos++] = 0x9D; break;
+            case 0x00FB: out[outpos++] = 0x9E; break;
+            case 0x00FC: out[outpos++] = 0x9F; break;
+
+            case 0x2020: out[outpos++] = 0xA0; break;
+            case 0x00B0: out[outpos++] = 0xA1; break;
+            case 0x00A2: out[outpos++] = 0xA2; break;
+            case 0x00A3: out[outpos++] = 0xA3; break;
+            case 0x00A7: out[outpos++] = 0xA4; break;
+            case 0x2022: out[outpos++] = 0xA5; break;
+            case 0x00B6: out[outpos++] = 0xA6; break;
+            case 0x00DF: out[outpos++] = 0xA7; break;
+            case 0x00AE: out[outpos++] = 0xA8; break;
+            case 0x00A9: out[outpos++] = 0xA9; break;
+            case 0x2122: out[outpos++] = 0xAA; break;
+            case 0x00B4: out[outpos++] = 0xAB; break;
+            case 0x00A8: out[outpos++] = 0xAC; break;
+            case 0x2260: out[outpos++] = 0xAD; break;
+            case 0x00C6: out[outpos++] = 0xAE; break;
+            case 0x00D8: out[outpos++] = 0xAF; break;
+
+            case 0x221E: out[outpos++] = 0xB0; break;
+            case 0x00B1: out[outpos++] = 0xB1; break;
+            case 0x2264: out[outpos++] = 0xB2; break;
+            case 0x2265: out[outpos++] = 0xB3; break;
+            case 0x00A5: out[outpos++] = 0xB4; break;
+            case 0x00B5: out[outpos++] = 0xB5; break;
+            case 0x2202: out[outpos++] = 0xB6; break;
+            case 0x2211: out[outpos++] = 0xB7; break;
+            case 0x220F: out[outpos++] = 0xB8; break;
+            case 0x03C0: out[outpos++] = 0xB9; break;
+            case 0x222B: out[outpos++] = 0xBA; break;
+            case 0x00AA: out[outpos++] = 0xBB; break;
+            case 0x00BA: out[outpos++] = 0xBC; break;
+            case 0x03A9: out[outpos++] = 0xBD; break;
+            case 0x00E6: out[outpos++] = 0xBE; break;
+            case 0x00F8: out[outpos++] = 0xBF; break;
+
+            case 0x00BF: out[outpos++] = 0xC0; break;
+            case 0x00A1: out[outpos++] = 0xC1; break;
+            case 0x00AC: out[outpos++] = 0xC2; break;
+            case 0x221A: out[outpos++] = 0xC3; break;
+            case 0x0192: out[outpos++] = 0xC4; break;
+            case 0x2248: out[outpos++] = 0xC5; break;
+            case 0x2206: out[outpos++] = 0xC6; break;
+            case 0x00AB: out[outpos++] = 0xC7; break;
+            case 0x00BB: out[outpos++] = 0xC8; break;
+            case 0x2026: out[outpos++] = 0xC9; break;
+            case 0x00A0: out[outpos++] = 0xCA; break;
+            case 0x00C0: out[outpos++] = 0xCB; break;
+            case 0x00C3: out[outpos++] = 0xCC; break;
+            case 0x00D5: out[outpos++] = 0xCD; break;
+            case 0x0152: out[outpos++] = 0xCE; break;
+            case 0x0153: out[outpos++] = 0xCF; break;
+
+            case 0x2013: out[outpos++] = 0xD0; break;
+            case 0x2014: out[outpos++] = 0xD1; break;
+            case 0x201C: out[outpos++] = 0xD2; break;
+            case 0x201D: out[outpos++] = 0xD3; break;
+            case 0x2018: out[outpos++] = 0xD4; break;
+            case 0x2019: out[outpos++] = 0xD5; break;
+            case 0x00F7: out[outpos++] = 0xD6; break;
+            case 0x25CA: out[outpos++] = 0xD7; break;
+            case 0x00FF: out[outpos++] = 0xD8; break;
+            case 0x0178: out[outpos++] = 0xD9; break;
+            case 0x2044: out[outpos++] = 0xDA; break;
+            case 0x20AC: out[outpos++] = 0xDB; break;
+            case 0x2039: out[outpos++] = 0xDC; break;
+            case 0x203A: out[outpos++] = 0xDD; break;
+            case 0xFB01: out[outpos++] = 0xDE; break;
+            case 0xFB02: out[outpos++] = 0xDF; break;
+
+            case 0x2021: out[outpos++] = 0xE0; break;
+            case 0x00B7: out[outpos++] = 0xE1; break;
+            case 0x201A: out[outpos++] = 0xE2; break;
+            case 0x201E: out[outpos++] = 0xE3; break;
+            case 0x2030: out[outpos++] = 0xE4; break;
+            case 0x00C2: out[outpos++] = 0xE5; break;
+            case 0x00CA: out[outpos++] = 0xE6; break;
+            case 0x00C1: out[outpos++] = 0xE7; break;
+            case 0x00CB: out[outpos++] = 0xE8; break;
+            case 0x00C8: out[outpos++] = 0xE9; break;
+            case 0x00CD: out[outpos++] = 0xEA; break;
+            case 0x00CE: out[outpos++] = 0xEB; break;
+            case 0x00CF: out[outpos++] = 0xEC; break;
+            case 0x00CC: out[outpos++] = 0xED; break;
+            case 0x00D3: out[outpos++] = 0xEE; break;
+            case 0x00D4: out[outpos++] = 0xEF; break;
+
+            case 0xF8FF: out[outpos++] = 0xF0; break;
+            case 0x00D2: out[outpos++] = 0xF1; break;
+            case 0x00DA: out[outpos++] = 0xF2; break;
+            case 0x00DB: out[outpos++] = 0xF3; break;
+            case 0x00D9: out[outpos++] = 0xF4; break;
+            case 0x0131: out[outpos++] = 0xF5; break;
+            case 0x02C6: out[outpos++] = 0xF6; break;
+            case 0x02DC: out[outpos++] = 0xF7; break;
+            case 0x00AF: out[outpos++] = 0xF8; break;
+            case 0x02D8: out[outpos++] = 0xF9; break;
+            case 0x02D9: out[outpos++] = 0xFA; break;
+            case 0x02DA: out[outpos++] = 0xFB; break;
+            case 0x00B8: out[outpos++] = 0xFC; break;
+            case 0x02DD: out[outpos++] = 0xFD; break;
+            case 0x02DB: out[outpos++] = 0xFE; break;
+            case 0x02C7: out[outpos++] = 0xFF; break;
+
+            default:
+                /* undefined */
+                out[outpos++] = '?';
+                break;
+        }
+
+        continue;
+    }
+
+    return outpos;
+}
+
+uint os_parse_chars(const unsigned char *buf, uint buflen, uint32 *out, uint outlen) {
+    switch (os_charmap)
+    {
+        case OS_UTF8:
+            return parse_utf8(buf, buflen, out, outlen);
+
+        case OS_CP1251:
+            return parse_cp1251(buf, buflen, out, outlen);
+
+        case OS_CP1252:
+            return parse_cp1252(buf, buflen, out, outlen);
+
+        case OS_MACROMAN:
+            return parse_mac(buf, buflen, out, outlen);
+
+        default:
+            return 0;
+    }
+}
+
+uint os_prepare_chars(const uint32 *buf, uint buflen, unsigned char *out, uint outlen) {
+    switch (os_charmap) {
+        case OS_UTF8:
+            return prepare_utf8(buf, buflen, out, outlen);
+
+        case OS_CP1251:
+            return prepare_cp1251(buf, buflen, out, outlen);
+
+        case OS_CP1252:
+            return prepare_cp1252(buf, buflen, out, outlen);
+
+        case OS_MACROMAN:
+            return prepare_mac(buf, buflen, out, outlen);
+
+        default:
+            return 0;
+    }
+}
+
+#endif /* GLK_MODULE_UNICODE */
+
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/os_parse.h b/engines/glk/tads/os_parse.h
new file mode 100644
index 0000000..05f88c2
--- /dev/null
+++ b/engines/glk/tads/os_parse.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* TADS OS interface file type definitions
+ *
+ * Defines certain datatypes used in the TADS operating system interface
+ */
+
+#ifndef GLK_TADS_OS_PARSE
+#define GLK_TADS_OS_PARSE
+
+#include "common/scummsys.h"
+
+namespace Glk {
+namespace TADS {
+
+extern uint os_parse_chars(const unsigned char *buf, uint buflen, uint32 *out, uint outlen);
+
+extern uint os_prepare_chars(const uint32 *buf, uint buflen, unsigned char *out, uint outlen);
+
+} // End of namespace TADS
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/tads/tads2/debug.h b/engines/glk/tads/tads2/debug.h
index 52695cf..0e7369e 100644
--- a/engines/glk/tads/tads2/debug.h
+++ b/engines/glk/tads/tads2/debug.h
@@ -25,6 +25,7 @@
 
 #include "glk/tads/tads2/lib.h"
 #include "glk/tads/tads2/object.h"
+#include "glk/tads/os_glk.h"
 
 namespace Glk {
 namespace TADS {
@@ -181,7 +182,7 @@ void dbgclin(struct tokcxdef *tokctx, objnum objn, uint ofs);
  *   the program define the appropriate implementation through the linker.
  */
 #ifdef DBG_OFF
-#define dbgpresent() (FALSE)
+#define dbgpresent() (false)
 #else
 int dbgpresent();
 #endif
@@ -545,22 +546,22 @@ void trcsho(void);
  */
 
 #ifdef DBG_OFF
-# define dbgenter(ctx, bp, self, target, prop, binum, argc)
-# define dbgleave(ctx, exittype)
-# define dbgdump(ctx)
-# define dbgrst(ctx) ((void)0)
-# define dbgframe(ctx, frofs, linofs)
-# define dbgssi(ctx, ofs, instr, err, p)
+#define dbgenter(ctx, bp, self, target, prop, binum, argc)
+#define dbgleave(ctx, exittype)
+#define dbgdump(ctx) ((void)0)
+#define dbgrst(ctx) ((void)0)
+#define dbgframe(ctx, frofs, linofs)
+#define dbgssi(ctx, ofs, instr, err, p) ((void)0)
 #else /* DBG_OFF */
-# define dbgenter(ctx, bp, self, target, prop, binum, argc) \
+#define dbgenter(ctx, bp, self, target, prop, binum, argc) \
    dbgent(ctx, bp, self, target, prop, binum, argc)
-# define dbgleave(ctx, exittype) dbglv(ctx, exittype)
-# define dbgdump(ctx) dbgds(ctx)
-# define dbgrst(ctx) ((ctx)->dbgcxfcn = (ctx)->dbgcxdep = 0)
-# define dbgframe(ctx, frofs, linofs) \
+#define dbgleave(ctx, exittype) dbglv(ctx, exittype)
+#define dbgdump(ctx) dbgds(ctx)
+#define dbgrst(ctx) ((ctx)->dbgcxfcn = (ctx)->dbgcxdep = 0)
+#define dbgframe(ctx, frofs, linofs) \
    (((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgffr = (frofs)), \
     ((ctx)->dbgcxfrm[(ctx)->dbgcxfcn - 1].dbgflin = (linofs)))
-# define dbgssi(ctx, ofs, instr, err, p) dbgss(ctx, ofs, instr, err, p)
+#define dbgssi(ctx, ofs, instr, err, p) dbgss(ctx, ofs, instr, err, p)
 #endif /* DBG_OFF */
 
 /* ======================================================================== */
diff --git a/engines/glk/tads/tads2/error_message.cpp b/engines/glk/tads/tads2/error_message.cpp
index 3cfe8de..ace3613 100644
--- a/engines/glk/tads/tads2/error_message.cpp
+++ b/engines/glk/tads/tads2/error_message.cpp
@@ -27,58 +27,42 @@ namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-	
-/*--------------------------------- lerini ---------------------------------*/
-/*
- * lerini - allocate and initialize an error context.  Returns a
- * pointer to an initialized error context if successful, 0 otherwise.
- */
 errcxdef *lerini() {
-  errcxdef *errcx;                                         /* error context */
+	errcxdef *errcx;                                         /* error context */
   
-  /* allocate an error context */
-  if (!(errcx = (errcxdef *)ltk_suballoc(sizeof(errcxdef))))
-  {
-    /* failure */
-    return((errcxdef *)0);
-  }
+	// allocate an error context
+	if (!(errcx = (errcxdef *)ltk_suballoc(sizeof(errcxdef)))) {
+		// failure
+		return((errcxdef *)0);
+	}
 
-  /* initialize the error context */
-  errcx->errcxfp  = (osfildef *)0;                  /* no error file handle */
-  errcx->errcxofs = 0;                      /* no offset in argument buffer */
-  errcx->errcxlog = ltk_errlog;                    /* error logging routine */
-  errcx->errcxlgc = errcx;                         /* error logging context */
+	// initialize the error context
+	errcx->errcxfp  = (osfildef *)0;                  /* no error file handle */
+	errcx->errcxofs = 0;                      /* no offset in argument buffer */
+	errcx->errcxlog = ltk_errlog;                    /* error logging routine */
+	errcx->errcxlgc = errcx;                         /* error logging context */
 
-  /* return the new context */
-  return(errcx);
+	// return the new context
+	return errcx;
 }
 
-
-/*--------------------------------- lerfre ---------------------------------*/
-/*
- * lerfre - FREe error context allocated by errini.
- */
-void lerfre(errcxdef *errcx) {
-  /* free the context */
-  ltk_subfree(errcx);
+void errini(errcxdef *ctx, char *arg0) {
+	VARUSED(ctx);
+	VARUSED(arg0);
 }
 
+void errini(errcxdef *ctx, osfildef *fp) {
+	VARUSED(ctx);
+	VARUSED(fp);
+}
 
-/*--------------------------------- errmsg ---------------------------------*/
-/*
- * errmsg - format error message number 'err' into the given buffer.
- */
-void errmsg(errcxdef *ctx, char *outbuf, int outbufl, uint err) {
-  sprintf(outbuf, "Error #%d occured.", err);
+void lerfre(errcxdef *errcx) {
+	// free the context
+	ltk_subfree(errcx);
 }
 
-/*--------------------------------- errini ---------------------------------*/
-/*
- * errini - initialize error system.
- */
-void errini(errcxdef *ctx, char *arg0) {
-    VARUSED(ctx);
-    VARUSED(arg0);
+void errmsg(errcxdef *ctx, char *outbuf, uint outbufl, uint err) {
+	sprintf(outbuf, "Error #%d occured.", err);
 }
 
 } // End of namespace TADS2
diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp
index bef91f1..10b8bca 100644
--- a/engines/glk/tads/tads2/file_io.cpp
+++ b/engines/glk/tads/tads2/file_io.cpp
@@ -1769,6 +1769,12 @@ ret_error:
     return TRUE;
 }
 
+void fioxor(uchar *p, uint siz, uint seed, uint inc)
+{
+	for (; siz; seed += inc, --siz)
+		*p++ ^= (uchar)seed;
+}
+
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/tads2/get_string.cpp b/engines/glk/tads/tads2/get_string.cpp
new file mode 100644
index 0000000..69982fc
--- /dev/null
+++ b/engines/glk/tads/tads2/get_string.cpp
@@ -0,0 +1,173 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/tads/tads2/character_map.h"
+#include "glk/tads/tads2/text_io.h"
+#include "glk/tads/tads2/os.h"
+
+namespace Glk {
+namespace TADS {
+namespace TADS2 {
+
+
+/*
+ *   Global variable with the current command logging file.  If this is
+ *   not null, we'll log each command that we read to this file.  
+ */
+osfildef *cmdfile;
+
+/*
+ *   External global with the current script input file.  If this is
+ *   non-null, we'll read commands from this file rather than from the
+ *   keyboard.  
+ */
+extern osfildef *scrfp;
+
+/*
+ *   External global indicating script echo status.  If we're reading from
+ *   a script input file (i.e., scrfp is non-null), and this variable is
+ *   true, it indicates that we're in "quiet" mode reading the script, so
+ *   we will not echo commands that we read from the script file to the
+ *   display. 
+ */
+extern int scrquiet;
+
+/*
+ *   getstring reads a string from the keyboard, doing all necessary
+ *   output flushing.  Prompting is to be done by the caller.  This
+ *   routine should be called instead of os_gets.
+ */
+int getstring(char *prompt, char *buf, int bufl)
+{
+    char  *result;
+    int    savemoremode;
+	int    retval = 0;
+
+    /* show prompt if one was given and flush output */
+    savemoremode = setmore(0);
+    if (prompt != 0)
+    {
+        /* display the prompt text */
+        outformat(prompt);
+
+        /* make sure it shows up in the log file as well */
+        out_logfile_print(prompt, FALSE);
+    }
+    outflushn(0);
+    outreset();
+
+    /* read from the command input file if we have one */
+    if (scrfp != 0)
+    {
+        int quiet = scrquiet;
+        
+        /* try reading from command input file */
+        if ((result = qasgets(buf, bufl)) == 0)
+        {
+            /*
+             *   End of command input file; return to reading the
+             *   keyboard.  If we didn't already show the prompt, show it
+             *   now.
+             *   
+             *   Note that qasgets() will have closed the script file
+             *   before returning eof, so we won't directly read the
+             *   command here but instead handle it later when we check to
+             *   see if we need to read from the keyboard.  
+             */
+            if (quiet && prompt != 0)
+                outformat(prompt);
+            outflushn(0);
+            outreset();
+
+            /*
+             *   Guarantee that moremode is turned back on.  (moremode can
+             *   be turned off for one of two reasons: we're printing the
+             *   prompt, or we're reading from a script with no pauses.
+             *   In either case, moremode should be turned back on at this
+             *   point. -CDN) 
+             */
+            savemoremode = 1;
+
+            /* turn off NONSTOP mode now that we're done with the script */
+            os_nonstop_mode(FALSE);
+        }
+
+        /* success */
+        retval = 0;
+    }
+
+    /* if we don't have a script file, read from the keyboard */
+    if (scrfp == 0)
+    {
+        /* update the status line */
+        runstat();
+        
+        /* read a line from the keyboard */
+        result = (char *)os_gets((uchar *)buf, bufl);
+        
+        /* 
+         *   if the result is null, we're at eof, so return a non-zero
+         *   value; otherwise, we successfully read a command, so return
+         *   zero 
+         */
+        retval = (result == 0);
+    }
+
+    /* restore the original "more" mode */
+    setmore(savemoremode);
+
+    /* check the result */
+    if (retval != 0)
+    {
+        /* we got an error reading the command - return the error */
+        return retval;
+    }
+    else
+    {
+        char *p;
+        
+        /* 
+         *   we got a command, or at least a partial command (if we timed
+         *   out, we may still have a partial line in the buffer) - write
+         *   the input line to the log and/or command files, as
+         *   appropriate 
+         */
+        out_logfile_print(buf, TRUE);
+        if (cmdfile != 0)
+        {
+            os_fprintz(cmdfile, ">");
+            os_fprintz(cmdfile, buf);
+            os_fprintz(cmdfile, "\n");
+        }
+
+        /* translate the input to the internal character set */
+        for (p = buf ; *p != '\0' ; ++p)
+            *p = cmap_n2i(*p);
+
+        /* success */
+        return retval;
+    }
+}
+
+} // End of namespace TADS2
+} // End of namespace TADS
+} // End of namespace Glk
diff --git a/engines/glk/tads/tads2/ltk.cpp b/engines/glk/tads/tads2/ltk.cpp
index b112839..c385ee7 100644
--- a/engines/glk/tads/tads2/ltk.cpp
+++ b/engines/glk/tads/tads2/ltk.cpp
@@ -130,15 +130,15 @@ void ltk_free(void *mem) {
 * ltk_errlog - ERRor LOGging function.  Logs an error from the LER
 * system.
 */
-void ltk_errlog(void *ctx, char *fac, int errno, int argc, erradef *argv) {
+void ltk_errlog(void *ctx, char *fac, int errCode, int argc, erradef *argv) {
 	char buf[128];                                  /* formatted error buffer */
 	char msg[128];                                          /* message buffer */
 
 															/* $$$ filter out error #504 $$$ */
-	if (errno == 504) return;
+	if (errCode == 504) return;
 
 	/* get the error message into msg */
-	errmsg((errcxdef *)ctx, msg, sizeof(msg), errno);
+	errmsg((errcxdef *)ctx, msg, sizeof(msg), errCode);
 
 	/* format the error message */
 	errfmt(buf, (int)sizeof(buf), msg, argc, argv);
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 6aa39b5..8b9fd74 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -3554,6 +3554,33 @@ void out_set_doublespace(int dbl)
     doublespace = dbl;
 }
 
+int tio_askfile(const char *prompt, char *reply, int replen, int prompt_type, os_filetype_t file_type) {
+	// let the OS layer handle it
+	return os_askfile(prompt, reply, replen, prompt_type, file_type);
+}
+
+int tio_input_dialog(int icon_id, const char *prompt, int standard_button_set,
+	const char **buttons, int button_count,
+	int default_index, int cancel_index) {
+	// call the OS implementation
+	return os_input_dialog(icon_id, prompt, standard_button_set,
+		buttons, button_count,
+		default_index, cancel_index);
+}
+
+void outfmt(tiocxdef *ctx, uchar *txt) {
+    uint len;
+
+    VARUSED(ctx);
+
+    /* read the length prefix */
+    len = osrp2(txt) - 2;
+    txt += 2;
+
+    /* write out the string */
+    tioputslen(ctx, (char *)txt, len);
+}
+
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/tads2/post_compilation.cpp b/engines/glk/tads/tads2/post_compilation.cpp
index cd501db..c1755ca 100644
--- a/engines/glk/tads/tads2/post_compilation.cpp
+++ b/engines/glk/tads/tads2/post_compilation.cpp
@@ -28,6 +28,38 @@ namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
+void supcont(void *ctx, objnum obj, prpnum prp) {
+		// No implementation
+}
+
+void supivoc(supcxdef *ctx) {
+	// No implementation
+}
+
+void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc,
+		objnum *preinit, int warnlevel, int casefold) {
+	// No implementation
+}
+
+void suprsrv(supcxdef *sup, void(*bif[])(struct bifcxdef *, int),
+		toktdef *tab, int fncntmax, int v1compat, char *new_do, int casefold) {
+	// No implementation
+}
+
+void supbif(supcxdef *sup, void(*bif[])(struct bifcxdef *, int), int bifsiz) {
+	// No implementation
+}
+
+void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err,
+		char *sym_name, int sym_name_len, objnum objn) {
+	// No implementation
+}
+
+void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target,
+		int inh_from_obj, int flags) {
+	// No implementation
+}
+
 } // End of namespace TADS2
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/tads2/post_compilation.h b/engines/glk/tads/tads2/post_compilation.h
index fdd1261..1d7ac7a 100644
--- a/engines/glk/tads/tads2/post_compilation.h
+++ b/engines/glk/tads/tads2/post_compilation.h
@@ -46,34 +46,34 @@ struct supcxdef {
 };
 
 /* set up contents list for one object for demand-on-load */
-inline void supcont(void *ctx, objnum obj, prpnum prp);
+extern void supcont(void *ctx, objnum obj, prpnum prp);
 
 /* set up inherited vocabulary (called before executing game) */
-inline void supivoc(supcxdef *ctx);
+extern void supivoc(supcxdef *ctx);
 
 /* find required objects/functions */
-inline void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc,
+extern void supfind(errcxdef *ctx, tokthdef *tab, voccxdef *voc,
              objnum *preinit, int warnlevel, int casefold);
 
 /* set up reserved words */
-inline void suprsrv(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
+extern void suprsrv(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
              toktdef *tab, int fncntmax, int v1compat, char *new_do,
              int casefold);
 
 /* set up built-in functions without symbol table (for run-time) */
-inline void supbif(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
+extern void supbif(supcxdef *sup, void (*bif[])(struct bifcxdef *, int),
             int bifsiz);
 
 /* log an undefined-object error */
-inline void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err,
+extern void sup_log_undefobj(mcmcxdef *mctx, errcxdef *ec, int err,
                       char *sym_name, int sym_name_len, objnum objn);
 
 /* set up inherited vocabulary for a particular object */
-inline void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target,
+extern void supivoc1(supcxdef *sup, voccxdef *ctx, vocidef *v, objnum target,
               int inh_from_obj, int flags);
 
 /* get name of an object out of symbol table */
-inline void supgnam(char *buf, tokthdef *tab, objnum objn);
+extern void supgnam(char *buf, tokthdef *tab, objnum objn);
 
 /* table of built-in functions */
 struct supbidef {
diff --git a/engines/glk/tads/tads2/runtime_driver.cpp b/engines/glk/tads/tads2/runtime_driver.cpp
index b2169dd..20ba6c1 100644
--- a/engines/glk/tads/tads2/runtime_driver.cpp
+++ b/engines/glk/tads/tads2/runtime_driver.cpp
@@ -53,6 +53,84 @@ void tok_read_defines(tokcxdef *tctx, osfildef *fp, errcxdef *ec)
 }
 
 /* dummy debugger functions */
+
+int dbgbpset(dbgcxdef *ctx, char *addr, int *bpnum) { return 0; }
+
+int dbgbpat(dbgcxdef *ctx, objnum objn, objnum self,
+	uint ofs, int *bpnum, char *bpname, int toggle,
+	char *condition, int *did_set) { return 0; }
+
+int dbgbpatid(dbgcxdef *ctx, int bpnum, objnum target, objnum self,
+	uint ofs, char *bpname, int toggle, char *cond,
+	int *did_set) { return 0; }
+
+int dbgisbp(dbgcxdef *ctx, objnum target, objnum self, uint ofs, int *bpnum) { return 0; }
+
+int dbgisbpena(dbgcxdef *ctx, int bpnum) { return 0; }
+
+int dbgbpdel(dbgcxdef *ctx, int bpnum) { return 0; }
+
+int dbgbpdis(dbgcxdef *ctx, int bpnum, int disable) { return 0; }
+
+int dbgbpsetcond(dbgcxdef *ctx, int bpnum, char *cond) { return 0; }
+
+void dbgbplist(dbgcxdef *ctx, void(*dispfn)(void *ctx, const char *str, int len), void *dispctx) {}
+
+void dbgbpenum(dbgcxdef *ctx, void(*cbfunc)(void *cbctx, int bpnum, const char *desc,
+		const char *cond, int disabled), void *cbctx) {}
+
+void dbgbpeach(dbgcxdef *ctx, void(*fn)(void *, int, uchar *, uint), void *fnctx) {}
+
+int dbgbpgetinfo(dbgcxdef *ctx, int bpnum, char *descbuf, size_t descbuflen,
+	char *condbuf, size_t condbuflen) { return 0; }
+
+int dbgeval(dbgcxdef *ctx, char *expr,
+	void(*dispfn)(void *dispctx, const char *str, int strl),
+	void *dispctx, int level, int showtype) { return 0; }
+
+int dbgevalext(dbgcxdef *ctx, char *expr,
+	void(*dispfn)(void *dispctx, const char *str, int strl),
+	void *dispctx, int level, int showtype, dattyp *dat,
+	void(*aggcb)(void *aggctx, const char *subname,
+		int subnamelen, const char *relationship),
+	void *aggctx, int speculative) { return 0 ;}
+
+void dbgenumlcl(dbgcxdef *ctx, int level,
+	void(*func)(void *ctx, const char *lclnam, size_t lclnamlen),
+	void *cbctx) {}
+
+int dbgcompile(dbgcxdef *ctx, char *expr, dbgfdef *fr, objnum *objn,
+	int speculative) { return 0; }
+
+void dbgwhere(dbgcxdef *ctx, char *buf) {}
+
+int dbgwxset(dbgcxdef *ctx, char *expr, int *wxnum, int level) { return 0; }
+
+int dbgwxdel(dbgcxdef *ctx, int wxnum) { return 0; }
+
+void dbgwxupd(dbgcxdef *ctx,
+	void(*dispfn)(void *dispctx, const char *txt, int len),
+	void *dispctx) {}
+
+void dbgswitch(struct lindef **linp, struct lindef *newlin) {}
+
+void dbguini(dbgcxdef *ctx, const char *game_filename) {}
+
+void dbguini2(dbgcxdef *ctx) {}
+
+int dbgu_err_resume(dbgcxdef *ctx) { return 0; }
+
+int dbgu_find_src(const char *origname, int origlen,
+	char *fullname, size_t full_len, int must_find_file) { return 0; }
+
+void dbgucmd(dbgcxdef *ctx, int bphit, int err, unsigned int *exec_ofs) {}
+
+void dbguquitting(dbgcxdef *ctx) {}
+
+void dbguterm(dbgcxdef *ctx) {}
+
+void dbguerr(dbgcxdef *ctx, int errnum, char *msg) {}
+
 void trchid(void) {}
 void trcsho(void) {}
 
diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h
index 33a914e..6519465 100644
--- a/engines/glk/tads/tads2/text_io.h
+++ b/engines/glk/tads/tads2/text_io.h
@@ -212,8 +212,7 @@ void out_set_doublespace(int dbl);
  *   call directly for graphical implementations.  We'll use formatted
  *   text for text-only implementations.  
  */
-int tio_askfile(const char *prompt, char *fname_buf, int fname_buf_len,
-                int prompt_type, os_filetype_t file_type);
+int tio_askfile(const char *prompt, char *reply, int replen, int prompt_type, os_filetype_t file_type);
 
 /*
  *   Display a dialog, using a system-defined dialog (via os_input_dialog)


Commit: 5aacb456458af18581fcf72fc96824027c566e79
    https://github.com/scummvm/scummvm/commit/5aacb456458af18581fcf72fc96824027c566e79
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS2: Added plygo

Changed paths:
    engines/glk/tads/tads2/play.cpp


diff --git a/engines/glk/tads/tads2/play.cpp b/engines/glk/tads/tads2/play.cpp
index 115d2b6..0c78c6d 100644
--- a/engines/glk/tads/tads2/play.cpp
+++ b/engines/glk/tads/tads2/play.cpp
@@ -21,13 +21,335 @@
  */
 
 #include "glk/tads/tads2/play.h"
+#include "glk/tads/tads2/error.h"
+#include "glk/tads/tads2/file_io.h"
 
 namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
 void plygo(runcxdef *run, voccxdef *voc, tiocxdef *tio, objnum preinit, char *restore_fname) {
-		// TODO
+   int       err;
+    errcxdef *ec = run->runcxerr;
+    char      filbuf[128];
+    int       first_time;
+    int noreg inited = FALSE;
+
+    NOREG((&inited));
+
+    first_time = TRUE;
+
+    /* 
+     *   Write out the special <?T2> HTML sequence, in case we're on an HTML
+     *   system.  This tells the HTML parser to use the parsing rules for
+     *   TADS 2 callers. 
+     */
+    outformat("\\H+<?T2>\\H-");
+    
+startover:
+    if (!inited)
+    {
+        /* use Me as the format-string actor for preinit and init */
+        tiosetactor(voc->voccxtio, voc->voccxme);
+
+        /*
+         *   Run preinit, if it hasn't been run yet.  Note that we only
+         *   do this the first time through.  If we come back here via the
+         *   restart function, preinit will already have been run in the
+         *   restart function itself, so we don't need to run it again.
+         */
+        if (first_time)
+        {
+            /* make a note that we've been through here once already */
+            first_time = FALSE;
+
+            /* remember the preinit function for later use in restarting */
+            voc->voccxpreinit = preinit;
+
+            /* run the preinit() function */
+            ERRBEGIN(ec)
+            {
+                /* reset the interpreter */
+                runrst(run);
+
+                /* reset the parser */
+                voc_stk_ini(voc, (uint)VOC_STACK_SIZE);
+
+                /* run preinit */
+                if (preinit != MCMONINV)
+                    runfn(run, preinit, 0);
+            }
+            ERRCATCH(ec, err)
+            {
+                /* if they restarted, go back and start over */
+                if (err == ERR_RUNRESTART)
+                    goto startover;
+
+                /* resignal the error */
+                errrse(ec);
+            }
+            ERREND(ec);
+        }
+        
+        /* 
+         *   Run the "init" function.  Do NOT run init if we're restoring
+         *   a game directly from the command line AND there's an
+         *   initRestore function defined. 
+         */
+        if (restore_fname == 0 || voc->voccxinitrestore == MCMONINV)
+        {
+            ERRBEGIN(ec)
+            {
+                /* reset the interpreter */
+                runrst(run);
+
+                /* reset the parser */
+                voc_stk_ini(voc, (uint)VOC_STACK_SIZE);
+
+                /* run init */
+                runfn(run, (objnum)voc->voccxini, 0);
+            }
+            ERRCATCH(ec, err)
+            {
+                /* if they restarted, go back and start over */
+                if (err == ERR_RUNRESTART)
+                    goto startover;
+                
+                /* resignal the error */
+                errrse(ec);
+            }
+            ERREND(ec);
+        }
+    }
+    
+    /* next time through, we'll need to run init again */
+    inited = FALSE;
+
+    /* 
+     *   check for startup parameter file to restore - if there's a
+     *   system-specific parameter file specified, pretend that it was
+     *   specified as the restore file 
+     */
+    if (os_paramfile(filbuf))
+        restore_fname = filbuf;
+
+    /* check for a file to restore */
+    if (restore_fname != 0)
+    {
+        /*
+         *   Check to see if the game file supports the initRestore
+         *   function.  If so, call it to restore the game.  If not,
+         *   restore the game directly. 
+         */
+        if (voc->voccxinitrestore != MCMONINV)
+        {
+            char restore_buf[OSFNMAX*2];
+            char *src;
+            char *dst;
+            
+            /* convert any backslashes to double backslashes */
+            for (src = restore_fname, dst = restore_buf ;
+                 *src != '\0' && dst + 2 < restore_buf + sizeof(restore_buf) ;
+                 ++src)
+            {
+                switch(*src)
+                {
+                case '\\':
+                    /* it's a backslash - double it */
+                    *dst++ = '\\';
+                    *dst++ = '\\';
+                    break;
+
+                default:
+                    /* copy the character as-is */
+                    *dst++ = *src;
+                }
+            }
+            
+            /* 
+             *   all the game's initRestore function with the name of
+             *   saved game file to restore as the argument 
+             */
+
+            /* reset the interpreter */
+            runrst(run);
+
+            /* reset the parser */
+            voc_stk_ini(voc, (uint)VOC_STACK_SIZE);
+
+            /* push the game file name and run initRestore */
+            runpstr(run, restore_buf, dst - restore_buf, 0);
+            runfn(run, (objnum)voc->voccxinitrestore, 1);
+        }
+        else
+        {
+            /* restore the game */
+            os_printz("\n\n[Restoring saved game]\n\n");
+            err = fiorso(voc, restore_fname);
+            if (err)
+            {
+                char buf[60 + OSFNMAX];
+
+                sprintf(buf, "\n\nError: unable to restore file \"%s\"\n\n",
+                        restore_fname);
+                os_printz(buf);
+            }
+        }
+
+        /* forget the saved game name, in case we restore */
+        restore_fname = 0;
+    }
+
+    /* clear out the redo command buffer */
+    voc->voccxredobuf[0] = '\0';
+    
+    /* read and execute commands */
+    for (;;)
+    {
+        char buf[128];
+        
+        err = 0;
+        ERRBEGIN(ec)
+            
+        /* read a new command if there's nothing to redo */
+        if (!voc->voccxredo)
+        {
+            /* reset hidden output so we're showing output */
+            tioshow(tio);
+            tioflush(tio);
+
+            /* clear the interpreter stack */
+            runrst(run);
+
+            /* read a command */
+            vocread(voc, MCMONINV, MCMONINV, buf, (int)sizeof(buf), 0);
+
+            /* special qa checking */
+            if (buf[0] == '@')
+            {
+                int   quiet = FALSE;
+                char *p;
+                
+                p = buf + 1;
+                if (*p == '@')
+                {
+                    /* turn off MORE mode */
+                    setmore(0);
+
+                    /* set NONSTOP mode in the OS layer */
+                    os_nonstop_mode(TRUE);
+
+                    /* skip the extra '@' */
+                    ++p;
+                }
+                else if (*p == '!')
+                {
+                    quiet = TRUE;
+                    ++p;
+                }
+                while (*p != '\0' && t_isspace(*p)) ++p;
+                if (*p != '\0')
+                {
+                    /* open the named file */
+                    qasopn(p, quiet);
+                }
+                else
+                {
+                    char fname[256];
+
+                    /* no file was named - ask the user to select a file */
+                    if (tio_askfile("Read script file:", fname, sizeof(fname),
+                                    OS_AFP_OPEN, OSFTCMD) == 0)
+                        qasopn(fname, quiet);
+                }
+                goto end_loop;
+            }
+        }
+
+        /* 
+         *   If there's redo in the redo buffer, use it now.  If the
+         *   buffer is empty and the redo flag is set, we'll just
+         *   re-execute whatever's in our internal buffer.
+         */
+        if (voc->voccxredo && voc->voccxredobuf[0] != '\0')
+        {
+            /* copy the redo buffer into our internal buffer */
+            strcpy(buf, voc->voccxredobuf);
+
+            /* we've consumed it now, so clear it out */
+            voc->voccxredobuf[0] = '\0';
+        }
+
+        /* we've now consumed the redo */
+        voc->voccxredo = FALSE;
+
+        /* clear any pending break that's queued up */
+        (void)os_break();
+
+        /* execute the command */
+        (void)voccmd(voc, buf, (uint)sizeof(buf));
+        
+    end_loop:
+        ERRCATCH(ec, err)
+        {
+            if (err != ERR_RUNQUIT
+                && err != ERR_RUNRESTART
+                && !(err == ERR_RUNABRT && voc->voccxredo))
+                errclog(ec);
+        }
+        ERREND(ec);
+
+        /* on interrupt, undo last command (which was partially executed) */
+        if (err == ERR_USRINT && voc->voccxundo)
+        {
+            ERRBEGIN(ec)
+                objundo(voc->voccxmem, voc->voccxundo);
+            ERRCATCH(ec, err)
+                if (err != ERR_NOUNDO && err != ERR_ICUNDO)
+                    errrse(ec);
+            ERREND(ec)
+        }
+            
+        /* if they want to quit, we're done */
+        if (err == ERR_RUNQUIT)
+            break;
+        else if (err == ERR_RUNRESTART)
+            goto startover;
+    }
+
+    /*
+     *   If we're quitting, give the debugger one last chance at taking
+     *   control.  If it just returns, we can go ahead and terminate, but
+     *   if it wants it can restart the game by calling bifrst() as
+     *   normal.  
+     */
+    ERRBEGIN(ec)
+    {
+        /* clear anything in the debugger stack trace */
+        run->runcxdbg->dbgcxfcn = 0;
+        run->runcxdbg->dbgcxdep = 0;
+
+        /* tell the debugger the game has exited */
+        dbguquitting(run->runcxdbg);
+    }
+    ERRCATCH(ec, err)
+    {
+        switch(err)
+        {
+        case ERR_RUNRESTART:
+            /* they restarted the game - re-enter the play loop */
+            goto startover;
+
+        case ERR_RUNQUIT:
+            /* quitting - proceed to return as normal */
+            break;
+
+        default:
+            /* resignal any other error */
+            errrse(ec);
+        }
+    }
+    ERREND(ec);
 }
 
 } // End of namespace TADS2


Commit: e3454b5d96c258f59a1186eae1bdfd01d5f7af3e
    https://github.com/scummvm/scummvm/commit/e3454b5d96c258f59a1186eae1bdfd01d5f7af3e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: TADS: Don't support RTL

Changed paths:
    engines/glk/glk.h
    engines/glk/tads/tads.cpp
    engines/glk/tads/tads.h


diff --git a/engines/glk/glk.h b/engines/glk/glk.h
index 6b95eb2..f96f384 100644
--- a/engines/glk/glk.h
+++ b/engines/glk/glk.h
@@ -86,7 +86,7 @@ protected:
 	/**
 	  * Returns true whether a given feature is supported by the engine
 	  */
-	virtual bool hasFeature(EngineFeature f) const;
+	virtual bool hasFeature(EngineFeature f) const override;
 
 	/**
 	 * Setup the video mode
@@ -139,16 +139,6 @@ public:
 	}
 
 	/**
-	 * Returns the bitset of game features
-	 */
-	uint32 getFeatures() const;
-
-	/**
-	 * Returns whether the game is a demo
-	 */
-	bool isDemo() const;
-
-	/**
 	 * Returns the language
 	 */
 	Common::Language getLanguage() const { return _gameDescription._language; };
diff --git a/engines/glk/tads/tads.cpp b/engines/glk/tads/tads.cpp
index 61e13cd..b021b02 100644
--- a/engines/glk/tads/tads.cpp
+++ b/engines/glk/tads/tads.cpp
@@ -35,6 +35,14 @@ TADS::TADS(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gam
 	os_init(nullptr, nullptr, 0, 0, 0);
 }
 
+bool TADS::hasFeature(EngineFeature f) const {
+	// The TADS codebases rely on a lot of static globals, so RTL can't be supported
+	if (f == kSupportsRTL)
+		return false;
+
+	return GlkAPI::hasFeature(f);
+}
+
 Common::Error TADS::loadGameData(strid_t file) {
 	// TODO
 	return Common::kNoError;
diff --git a/engines/glk/tads/tads.h b/engines/glk/tads/tads.h
index e4640f0..6ad8780 100644
--- a/engines/glk/tads/tads.h
+++ b/engines/glk/tads/tads.h
@@ -49,6 +49,11 @@ public:
 	virtual InterpreterType getInterpreterType() const override { return INTERPRETER_SCOTT; }
 
 	/**
+	  * Returns true whether a given feature is supported by the engine
+	  */
+	virtual bool hasFeature(EngineFeature f) const override;
+
+	/**
 	 * Load a savegame from the passed stream
 	 */
 	virtual Common::Error loadGameData(strid_t file) override;


Commit: 0d172c8ca695ae67b881b92add19e488c6053fbb
    https://github.com/scummvm/scummvm/commit/0d172c8ca695ae67b881b92add19e488c6053fbb
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:06-07:00

Commit Message:
GLK: Fix freeing closed windows

Changed paths:
    engines/glk/windows.cpp
    engines/glk/windows.h


diff --git a/engines/glk/windows.cpp b/engines/glk/windows.cpp
index ffb1fbf..7b6833f 100644
--- a/engines/glk/windows.cpp
+++ b/engines/glk/windows.cpp
@@ -187,10 +187,21 @@ void Windows::windowClose(Window *win, StreamResult *result) {
 				return;
 			}
 
+			// Detach window being closed from parent pair window
+			pairWin->_children.remove_at(index);
+			win->_parent = nullptr;
+
 			if (!(pairWin->_dir & winmethod_Arbitrary)) {
-				sibWin = (index = ((int)pairWin->_children.size() - 1)) ?
-					pairWin->_children.front() : pairWin->_children[index + 1];
+				// Get the remaining child window
+				assert(pairWin->_children.size() == 1);
+				sibWin = pairWin->_children.front();
+
+				// Detach it from the pair window
+				index = pairWin->_children.indexOf(sibWin);
+				assert(index >= 0);
+				pairWin->_children.remove_at(index);
 
+				// Set up window as either the singular root, or grandparent pair window if one exists
 				grandparWin = dynamic_cast<PairWindow *>(pairWin->_parent);
 				if (!grandparWin) {
 					_rootWin = sibWin;
@@ -453,7 +464,12 @@ uint Windows::rgbShift(uint color) {
 /*--------------------------------------------------------------------------*/
 
 Windows::iterator &Windows::iterator::operator++() {
-	_current = _windows->iterateTreeOrder(_current);
+	_current = _current->_next;
+	return *this;
+}
+
+Windows::iterator &Windows::iterator::operator--() {
+	_current = _current->_prev;
 	return *this;
 }
 
diff --git a/engines/glk/windows.h b/engines/glk/windows.h
index 9cab07a..1974b3e 100644
--- a/engines/glk/windows.h
+++ b/engines/glk/windows.h
@@ -74,6 +74,11 @@ public:
 		iterator &operator++();
 
 		/**
+		 * Move to previous
+		 */
+		iterator &operator--();
+
+		/**
 		 * Equality test
 		 */
 		bool operator==(const iterator &i) {


Commit: 0c02346b48e068dddff81bf4fde57040135deb51
    https://github.com/scummvm/scummvm/commit/0c02346b48e068dddff81bf4fde57040135deb51
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00

Commit Message:
GLK: TADS2: Fix os initialization

Changed paths:
    engines/glk/tads/os_glk.cpp
    engines/glk/tads/tads.cpp
    engines/glk/tads/tads2/tads2.cpp


diff --git a/engines/glk/tads/os_glk.cpp b/engines/glk/tads/os_glk.cpp
index 884703c..90a5f00 100644
--- a/engines/glk/tads/os_glk.cpp
+++ b/engines/glk/tads/os_glk.cpp
@@ -92,7 +92,7 @@ int os_init(int *argc, char *argv[], const char *prompt,
     /* close statuswin; reopened on request */
     g_vm->glk_window_close(statuswin, 0);
 
-    statuswin = NULL;
+    statuswin = nullptr;
 
     g_vm->glk_set_window(mainwin);
 
diff --git a/engines/glk/tads/tads.cpp b/engines/glk/tads/tads.cpp
index b021b02..6ce2915 100644
--- a/engines/glk/tads/tads.cpp
+++ b/engines/glk/tads/tads.cpp
@@ -32,7 +32,6 @@ TADS *g_vm;
 
 TADS::TADS(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
 	g_vm = this;
-	os_init(nullptr, nullptr, 0, 0, 0);
 }
 
 bool TADS::hasFeature(EngineFeature f) const {
diff --git a/engines/glk/tads/tads2/tads2.cpp b/engines/glk/tads/tads2/tads2.cpp
index 6203900..547cb0f 100644
--- a/engines/glk/tads/tads2/tads2.cpp
+++ b/engines/glk/tads/tads2/tads2.cpp
@@ -23,6 +23,7 @@
 #include "glk/tads/tads2/tads2.h"
 #include "glk/tads/tads2/appctx.h"
 #include "glk/tads/tads2/runtime_app.h"
+#include "glk/tads/tads2/os.h"
 
 namespace Glk {
 namespace TADS {
@@ -32,11 +33,18 @@ TADS2::TADS2(OSystem *syst, const GlkGameDescription &gameDesc) : TADS(syst, gam
 }
 
 void TADS2::runGame() {
+	// Initialize the OS layer
+	os_init(nullptr, nullptr, 0, 0, 0);
+	os_instbrk(true);
+
 	char name[255];
 	strcpy(name, getFilename().c_str());
 	char *argv[2] = { nullptr, name };
 
 	trdmain(2, argv, nullptr, ".sav");
+
+	os_instbrk(false);
+	os_uninit();
 }
 
 } // End of namespace TADS2


Commit: aa5fd603b6fd623c138fd3529176847b5b538e49
    https://github.com/scummvm/scummvm/commit/aa5fd603b6fd623c138fd3529176847b5b538e49
Author: dreammaster (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00

Commit Message:
GLK: TADS2: Compilation fixes

Changed paths:
    engines/glk/tads/os_buffer.h
    engines/glk/tads/os_filetype.h
    engines/glk/tads/os_glk.cpp
    engines/glk/tads/os_glk.h
    engines/glk/tads/tads2/built_in.cpp
    engines/glk/tads/tads2/error.cpp
    engines/glk/tads/tads2/error_handling.cpp
    engines/glk/tads/tads2/error_handling.h
    engines/glk/tads/tads2/line_source_file.cpp
    engines/glk/tads/tads2/ltk.cpp
    engines/glk/tads/tads2/ltk.h
    engines/glk/tads/tads2/memory_cache_heap.cpp
    engines/glk/tads/tads2/memory_cache_heap.h
    engines/glk/tads/tads2/output.cpp
    engines/glk/tads/tads2/text_io.h
    engines/glk/tads/tads2/vocabulary.h
    engines/glk/tads/tads2/vocabulary_parser.cpp


diff --git a/engines/glk/tads/os_buffer.h b/engines/glk/tads/os_buffer.h
index 3fc2209..4fa9662 100644
--- a/engines/glk/tads/os_buffer.h
+++ b/engines/glk/tads/os_buffer.h
@@ -33,6 +33,8 @@
 #ifndef GLK_TADS_OS_BUFFER
 #define GLK_TADS_OS_BUFFER
 
+#include "common/scummsys.h"
+
 namespace Glk {
 namespace TADS {
 
diff --git a/engines/glk/tads/os_filetype.h b/engines/glk/tads/os_filetype.h
index 93b1016..2a48c4b 100644
--- a/engines/glk/tads/os_filetype.h
+++ b/engines/glk/tads/os_filetype.h
@@ -55,7 +55,7 @@ enum os_filetype_t {
 	OSFTT3IMG = 12,                  /* T3 image file (.t3 - formerly .t3x) */
 	OSFTT3OBJ = 13,                                /* T3 object file (.t3o) */
 	OSFTT3SYM = 14,                         /* T3 symbol export file (.t3s) */
-	OSFTT3SAV = 15,                           /* T3 saved state file (.t3v) */
+	OSFTT3SAV = 15                            /* T3 saved state file (.t3v) */
 };
 
 } // End of namespace TADS
diff --git a/engines/glk/tads/os_glk.cpp b/engines/glk/tads/os_glk.cpp
index 90a5f00..03d8a3a 100644
--- a/engines/glk/tads/os_glk.cpp
+++ b/engines/glk/tads/os_glk.cpp
@@ -641,8 +641,10 @@ unsigned char *os_gets(unsigned char *buf, size_t buflen)
  *   routine with use_timeout==false.  The regular os_gets() would not
  *   satisfy this need, because it cannot resume an interrupted input.)  
  */
+#if defined GLK_TIMERS && defined GLK_MODULE_LINE_ECHO
 static char * timebuf = NULL;
 static size_t timelen = 0;
+#endif
 
 int os_gets_timeout(unsigned char *buf, size_t bufl,
                     unsigned long timeout_in_milliseconds, int use_timeout)
@@ -1099,5 +1101,12 @@ void os_expause() {
 
 void os_plain(void) {}
 
+int memicmp(const char *s1, const char *s2, int len) {
+	Common::String cs1(s1, len);
+	Common::String cs2(s2, len);
+
+	return cs1.compareToIgnoreCase(cs2);
+}
+
 } // End of namespace TADS
 } // End of namespace Glk
diff --git a/engines/glk/tads/os_glk.h b/engines/glk/tads/os_glk.h
index 7b76876..ac5090f 100644
--- a/engines/glk/tads/os_glk.h
+++ b/engines/glk/tads/os_glk.h
@@ -3217,6 +3217,8 @@ int os_get_sysinfo(int code, void *param, long *result);
 #define os_remainder_long(a, b)  ((a) % (b))
 #endif
 
+int memicmp(const char *s1, const char *s2, int len);
+
 } // End of namespace TADS
 } // End of namespace Glk
 
diff --git a/engines/glk/tads/tads2/built_in.cpp b/engines/glk/tads/tads2/built_in.cpp
index 30c2772..c67399d 100644
--- a/engines/glk/tads/tads2/built_in.cpp
+++ b/engines/glk/tads/tads2/built_in.cpp
@@ -902,7 +902,7 @@ void bifrnd(bifcxdef *ctx, int argc)
 /* 
  *   case-insensitive substring matching 
  */
-static char *bif_stristr(const char *s1, const char *s2)
+static const char *bif_stristr(const char *s1, const char *s2)
 {
     size_t s1len;
     size_t s2len;
@@ -913,7 +913,7 @@ static char *bif_stristr(const char *s1, const char *s2)
     {
         /* if this is a match, return this substring */
         if (memicmp(s1, s2, s2len) == 0)
-            return (char *)s1;
+            return (const char *)s1;
     }
 
     return 0;
@@ -4146,7 +4146,7 @@ void bifinpdlg(bifcxdef *ctx, int argc)
     uchar *p;
     char prompt[256];
     char lblbuf[256];
-    char *labels[10];
+    const char *labels[10];
     char *dst;
     char *xp;
     uint len;
diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp
index eeb59cd..62c5e9a 100644
--- a/engines/glk/tads/tads2/error.cpp
+++ b/engines/glk/tads/tads2/error.cpp
@@ -93,7 +93,7 @@ int errfmt(char *outbuf, int outbufl, const char *fmt, int argc, const erradef *
                     static struct
                     {
                         int   tokid;
-                        char *toknam;
+                        const char *toknam;
                     } toklist[] =
                     {
                         { TOKTSEM, "semicolon" },
@@ -106,7 +106,7 @@ int errfmt(char *outbuf, int outbufl, const char *fmt, int argc, const erradef *
                         { TOKTWHILE, "\"while\"" },
                         { TOKTLPAR, "left paren ('(')" },
                         { TOKTEQ, "'='" },
-                        { 0, (char *)0 }
+                        { 0, (const char *)nullptr }
                     };
                     
                     for (i = 0 ; toklist[i].toknam ; ++i)
@@ -185,7 +185,7 @@ void errsign(errcxdef *ctx, int e) {
 }
 
 /* enter a string argument */
-char *errstr(errcxdef *ctx, char *str, int len) {
+char *errstr(errcxdef *ctx, const char *str, int len) {
     char *ret = &ctx->errcxbuf[ctx->errcxofs];
     
     memcpy(ret, str, (size_t)len);
diff --git a/engines/glk/tads/tads2/error_handling.cpp b/engines/glk/tads/tads2/error_handling.cpp
index b71fc8c..ac2b01d 100644
--- a/engines/glk/tads/tads2/error_handling.cpp
+++ b/engines/glk/tads/tads2/error_handling.cpp
@@ -34,7 +34,7 @@ int errfmt(char *outbuf, int outbufl, char *fmt, int argc, erradef *argv)
     int    argi   = 0;
     int    len;
     char   buf[20];
-	char  *p = nullptr;
+    const char  *p = nullptr;
     char   fmtchar;
 
     while (*fmt != '\0' && outbufl > 1)
@@ -148,7 +148,7 @@ void errjmp(jmp_buf buf, int e)
 #ifdef ERR_NO_MACRO
 
 /* base error signal function */
-void errsign(errcxdef *ctx, int e, char *facility)
+void errsign(errcxdef *ctx, int e, const char *facility)
 {
     strncpy(ctx->errcxptr->errfac, facility, ERRFACMAX);
     ctx->errcxptr->errfac[ERRFACMAX] = '\0';
@@ -161,7 +161,7 @@ void errsign(errcxdef *ctx, int e, char *facility)
 }
 
 /* signal an error with no arguments */
-void errsigf(errcxdef *ctx, char *facility, int e)
+void errsigf(errcxdef *ctx, const char *facility, int e)
 {
     errargc(ctx, 0);
     errsign(ctx, e, facility);
@@ -188,7 +188,7 @@ void errrse1(errcxdef *ctx, errdef *fr)
 }
 
 /* log an error: base function */
-void errlogn(errcxdef *ctx, int err, char *facility)
+void errlogn(errcxdef *ctx, int err, const char *facility)
 {
     ctx->errcxofs = 0;
     (*ctx->errcxlog)(ctx->errcxlgc, facility, err, ctx->errcxptr->erraac,
@@ -196,7 +196,7 @@ void errlogn(errcxdef *ctx, int err, char *facility)
 }
 
 /* log an error with no arguments */
-void errlogf(errcxdef *ctx, char *facility, int err)
+void errlogf(errcxdef *ctx, const char *facility, int err)
 {
     errargc(ctx, 0);
     errlogn(ctx, err, facility);
diff --git a/engines/glk/tads/tads2/error_handling.h b/engines/glk/tads/tads2/error_handling.h
index 7c56b2f..8d37929 100644
--- a/engines/glk/tads/tads2/error_handling.h
+++ b/engines/glk/tads/tads2/error_handling.h
@@ -74,7 +74,7 @@ namespace TADS2 {
 
 union erradef {
     int   erraint;                                      /* integer argument */
-    char *errastr;                                  /* text string argument */
+    const char *errastr;                            /* text string argument */
 };
 
 struct errdef {
@@ -99,7 +99,7 @@ typedef struct errmfdef errmfdef;
 
 struct errcxdef {
     errdef   *errcxptr;                              /* current error frame */
-    void    (*errcxlog)(void *, char *fac, int err, int argc, erradef *);
+    void    (*errcxlog)(void *, const char *fac, int err, int argc, erradef *);
                                          /* error logging callback function */
     void     *errcxlgc;               /* context for error logging callback */
     int       errcxofs;                        /* offset in argument buffer */
@@ -187,7 +187,7 @@ char *errstr(errcxdef *ctx, const char *str, int len);
 
 /* signal an error with argument count already set */
 #ifdef ERR_NO_MACRO
-void errsign(errcxdef *ctx, int e, char *facility);
+void errsign(errcxdef *ctx, int e, const char *facility);
 #else /* ERR_NO_MACRO */
 # ifdef DEBUG
 void errjmp(jmp_buf buf, int e);
@@ -206,7 +206,7 @@ void errjmp(jmp_buf buf, int e);
 
 /* signal an error with no arguments */
 #ifdef ERR_NO_MACRO
-void errsigf(errcxdef *ctx, char *facility, int err);
+void errsigf(errcxdef *ctx, const char *facility, int err);
 #else /* ERR_NO_MACRO */
 #define errsigf(ctx, fac, e) (errargc(ctx,0),errsign(ctx,e,fac))
 #endif /* ERR_NO_MACRO */
@@ -270,7 +270,7 @@ void errrse1(errcxdef *ctx, errdef *fr);
 
 /* log an error (no signalling, just reporting) */
 #ifdef ERR_NO_MACRO
-void errlogn(errcxdef *ctx, int err, char *facility);
+void errlogn(errcxdef *ctx, int err, const char *facility);
 #else /* ERR_NO_MACRO */
 
 #define errlogn(ctx,err,fac) \
@@ -282,7 +282,7 @@ void errlogn(errcxdef *ctx, int err, char *facility);
 
 /* log an error with no arguments */
 #ifdef ERR_NO_MACRO
-void errlogf(errcxdef *ctx, char *facility, int err);
+void errlogf(errcxdef *ctx, const char *facility, int err);
 #else /* ERR_NO_MACRO */
 
 /* void errlogf(errcxdef *ctx, char *facility, int err); */
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index b5da3d1..430b1d9 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -403,7 +403,8 @@ void linfglop2(lindef *lin, uchar *buf)
 /* save line source information to binary (.gam) file; TRUE ==> error */
 int linfwrt(lindef *lin, osfildef *fp)
 {
-#   define  linf ((linfdef *)lin)
+#define linf ((linfdef *)lin)
+#define UCHAR_MAX 255
     uchar   buf[UCHAR_MAX + 6];
     size_t  len;
     uint    pgcnt;
diff --git a/engines/glk/tads/tads2/ltk.cpp b/engines/glk/tads/tads2/ltk.cpp
index c385ee7..6961f21 100644
--- a/engines/glk/tads/tads2/ltk.cpp
+++ b/engines/glk/tads/tads2/ltk.cpp
@@ -130,7 +130,7 @@ void ltk_free(void *mem) {
 * ltk_errlog - ERRor LOGging function.  Logs an error from the LER
 * system.
 */
-void ltk_errlog(void *ctx, char *fac, int errCode, int argc, erradef *argv) {
+void ltk_errlog(void *ctx, const char *fac, int errCode, int argc, erradef *argv) {
 	char buf[128];                                  /* formatted error buffer */
 	char msg[128];                                          /* message buffer */
 
@@ -152,7 +152,7 @@ void ltk_errlog(void *ctx, char *fac, int errCode, int argc, erradef *argv) {
 /*
 * ltk_dlg - DiaLog.  Puts the given message in a dialog box.
 */
-void ltk_dlg(char *title, char *msg, ...) {
+void ltk_dlg(const char *title, const char *msg, ...) {
 	va_list  argp;                                             /* printf args */
 	char     inbuf[80];                                       /* input buffer */
 	char     outbuf[160];                    /* allow inbuf to double in size */
diff --git a/engines/glk/tads/tads2/ltk.h b/engines/glk/tads/tads2/ltk.h
index 2fbe7a8..fa66b8a 100644
--- a/engines/glk/tads/tads2/ltk.h
+++ b/engines/glk/tads/tads2/ltk.h
@@ -56,13 +56,13 @@ extern void ltkfre();
  * printf-style arguments must be passed in also, if the message
  * requires them.  
  */
-extern void ltk_dlg(char *title, char *msg, ...);
+extern void ltk_dlg(const char *title, const char *msg, ...);
 
 
 /*
  * ltk_errlog - Error logging function for LER routines.
  */
-extern void ltk_errlog(void *ctx, char *fac, int errCode, int agrc, erradef *argv);
+extern void ltk_errlog(void *ctx, const char *fac, int errCode, int agrc, erradef *argv);
 
 
 /*
diff --git a/engines/glk/tads/tads2/memory_cache_heap.cpp b/engines/glk/tads/tads2/memory_cache_heap.cpp
index fae3c5e..5704f46 100644
--- a/engines/glk/tads/tads2/memory_cache_heap.cpp
+++ b/engines/glk/tads/tads2/memory_cache_heap.cpp
@@ -30,7 +30,7 @@ namespace TADS2 {
 /* global to keep track of all allocations */
 IF_DEBUG(ulong mchtotmem;)
 
-uchar *mchalo(errcxdef *ctx, size_t siz, char *comment) {
+uchar *mchalo(errcxdef *ctx, size_t siz, const char *comment) {
     uchar *ret;
 
     VARUSED(comment);
diff --git a/engines/glk/tads/tads2/memory_cache_heap.h b/engines/glk/tads/tads2/memory_cache_heap.h
index 269cdec..6405f49 100644
--- a/engines/glk/tads/tads2/memory_cache_heap.h
+++ b/engines/glk/tads/tads2/memory_cache_heap.h
@@ -43,7 +43,7 @@ namespace TADS2 {
  *   An out-of-memory error is signalled if insufficient memory
  *   is available.  The comment is for debugging purposes only.
  */
-uchar *mchalo(errcxdef *ctx, size_t siz, char *comment);
+uchar *mchalo(errcxdef *ctx, size_t siz, const char *comment);
 
 /* allocate a structure */
 #define MCHNEW(errctx, typ, comment) \
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 8b9fd74..6ff2a99 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -192,7 +192,7 @@ static uchar    *fmstop;                          /* format string area top */
 static objnum    cmdActor;                                 /* current actor */
 
 /* forward declarations of static functions */
-static void outstring_stream(out_stream_info *stream, char *s);
+static void outstring_stream(out_stream_info *stream, const char *s);
 static void outchar_noxlat_stream(out_stream_info *stream, char c);
 static char out_parse_entity(char *outbuf, size_t outbuf_size,
                              char **sp, size_t *slenp);
@@ -1077,8 +1077,8 @@ static void outflushn_stream(out_stream_info *stream, int nl)
     }
     else
     {
-        char *suffix = nullptr; /* extra text to add after the flushed text */
-        int   countnl = 0;         /* true if line counts for [more] paging */
+        const char *suffix = nullptr; /* extra text to add after the flushed text */
+        int   countnl = 0;               /* true if line counts for [more] paging */
 
         /* null-terminate the buffer at the current position */
         stream->linebuf[++i] = '\0';
@@ -1621,7 +1621,7 @@ static void outchar_stream(out_stream_info *stream, char c)
 /* 
  *   write out a string, translating to the local system character set 
  */
-static void outstring_stream(out_stream_info *stream, char *s)
+static void outstring_stream(out_stream_info *stream, const char *s)
 {
     /* write out each character in the string */
     for ( ; *s ; ++s)
@@ -1813,7 +1813,7 @@ static char read_tag(char *dst, size_t dstlen, int *is_end_tag,
  *   display a string of a given length to a given stream 
  */
 static int outformatlen_stream(out_stream_info *stream,
-                               char *s, size_t slen)
+                               const char *s, size_t slen)
 {
     char     c;
     int      done = 0;
diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h
index 6519465..9a0e198 100644
--- a/engines/glk/tads/tads2/text_io.h
+++ b/engines/glk/tads/tads2/text_io.h
@@ -92,7 +92,7 @@ uint tiocapturesize(tiocxdef *ctx);
 void outfmt(tiocxdef *ctx, uchar *txt);
 
 /* format a null-terminated (C-style) string to the display */
-int outformat(char *s);
+int outformat(const char *s);
 
 /* format a counted-length string, which may not be null-terminated */
 int outformatlen(char *s, uint len);
diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h
index f4c058e..d28dcfe 100644
--- a/engines/glk/tads/tads2/vocabulary.h
+++ b/engines/glk/tads/tads2/vocabulary.h
@@ -609,7 +609,7 @@ void vocrevert(voccxdef *ctx);
 void vocdmnclr(voccxdef *ctx);
 
 /* display a parser error message */
-void vocerr(voccxdef *ctx, int err, char *f, ...);
+void vocerr(voccxdef *ctx, int err, const char *f, ...);
 
 /* 
  *   display a parser informational error message - this will display the
@@ -617,7 +617,7 @@ void vocerr(voccxdef *ctx, int err, char *f, ...);
  *   words, and should be used when providing information, such as objects
  *   we're assuming by default 
  */
-void vocerr_info(voccxdef *ctx, int err, char *f, ...);
+void vocerr_info(voccxdef *ctx, int err, const char *f, ...);
 
 /* client undo callback - undoes a daemon/fuse/notifier */
 void vocdundo(void *ctx, uchar *data);
diff --git a/engines/glk/tads/tads2/vocabulary_parser.cpp b/engines/glk/tads/tads2/vocabulary_parser.cpp
index c0ff75d..7190058 100644
--- a/engines/glk/tads/tads2/vocabulary_parser.cpp
+++ b/engines/glk/tads/tads2/vocabulary_parser.cpp
@@ -686,7 +686,7 @@ struct vocerr_va_info
  *   vocerr_va().  
  */
 static void vocerr_va_prep(voccxdef *ctx, struct vocerr_va_info *info,
-                           int err, char *f, va_list argptr)
+                           int err, const char *f, va_list argptr)
 {
     /* 
      *   presume that we'll use the given format string, instead of one
@@ -874,7 +874,7 @@ static void vocerr_va_prep(voccxdef *ctx, struct vocerr_va_info *info,
  *   property re-initialized for the call to this routine.  
  */
 static void vocerr_va(voccxdef *ctx, struct vocerr_va_info *info,
-                      int err, char *f, va_list argptr)
+                      int err, const char *f, va_list argptr)
 {
     char *buf;
     
@@ -896,7 +896,7 @@ static void vocerr_va(voccxdef *ctx, struct vocerr_va_info *info,
 /* 
  *   display a parser informational message 
  */
-void vocerr_info(voccxdef *ctx, int err, char *f, ...)
+void vocerr_info(voccxdef *ctx, int err, const char *f, ...)
 {
     va_list argptr;
     struct vocerr_va_info info;
@@ -915,7 +915,7 @@ void vocerr_info(voccxdef *ctx, int err, char *f, ...)
 /* 
  *   display a parser error 
  */
-void vocerr(voccxdef *ctx, int err, char *f, ...)
+void vocerr(voccxdef *ctx, int err, const char *f, ...)
 {
     va_list argptr;
     struct vocerr_va_info info;


Commit: 1a974b9749f2c12edbdf98cbe9f4b6ec9fcba40d
    https://github.com/scummvm/scummvm/commit/1a974b9749f2c12edbdf98cbe9f4b6ec9fcba40d
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00

Commit Message:
GLK: TADS2: Further compilation fixes

Changed paths:
    engines/glk/tads/tads2/error.cpp
    engines/glk/tads/tads2/output.cpp
    engines/glk/tads/tads2/run.cpp
    engines/glk/tads/tads2/run.h
    engines/glk/tads/tads2/runtime_driver.cpp
    engines/glk/tads/tads2/text_io.h
    engines/glk/tads/tads2/vocabulary_parser.cpp


diff --git a/engines/glk/tads/tads2/error.cpp b/engines/glk/tads/tads2/error.cpp
index 62c5e9a..ad83d06 100644
--- a/engines/glk/tads/tads2/error.cpp
+++ b/engines/glk/tads/tads2/error.cpp
@@ -184,16 +184,6 @@ void errsign(errcxdef *ctx, int e) {
 #endif
 }
 
-/* enter a string argument */
-char *errstr(errcxdef *ctx, const char *str, int len) {
-    char *ret = &ctx->errcxbuf[ctx->errcxofs];
-    
-    memcpy(ret, str, (size_t)len);
-    ret[len] = '\0';
-    ctx->errcxofs += len + 1;
-    return(ret);
-}
-
 /* log an error: base function */
 void errlogn(errcxdef *ctx, int err) {
 	error("errlogn");
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 6ff2a99..6f5cd84 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -194,8 +194,7 @@ static objnum    cmdActor;                                 /* current actor */
 /* forward declarations of static functions */
 static void outstring_stream(out_stream_info *stream, const char *s);
 static void outchar_noxlat_stream(out_stream_info *stream, char c);
-static char out_parse_entity(char *outbuf, size_t outbuf_size,
-                             char **sp, size_t *slenp);
+static char out_parse_entity(char *outbuf, size_t outbuf_size, const char **sp, size_t *slenp);
 
 
 /* ------------------------------------------------------------------------ */
@@ -1741,7 +1740,7 @@ static void out_pop_stream()
  *   Get the next character, writing the previous character to the given
  *   output stream if it's not null. 
  */
-static char nextout_copy(char **s, size_t *slen,
+static char nextout_copy(const char **s, size_t *slen,
                          char prv, out_stream_info *stream)
 {
     /* if there's a stream, write the previous character to the stream */
@@ -1759,7 +1758,7 @@ static char nextout_copy(char **s, size_t *slen,
  *   the next character after the tag name.
  */
 static char read_tag(char *dst, size_t dstlen, int *is_end_tag,
-                     char **s, size_t *slen, out_stream_info *stream)
+                     const char **s, size_t *slen, out_stream_info *stream)
 {
     char c;
     
@@ -2722,12 +2721,10 @@ static int outformatlen_stream(out_stream_info *stream,
 /*
  *   Parse an HTML entity markup 
  */
-static char out_parse_entity(char *outbuf, size_t outbuf_size,
-                             char **sp, size_t *slenp)
-{
+static char out_parse_entity(char *outbuf, size_t outbuf_size, const char **sp, size_t *slenp) {
     char  ampbuf[10];
     char *dst;
-    char *orig_s;
+    const char *orig_s;
     size_t orig_slen;
     const amp_tbl_t *ampptr;
     size_t lo, hi, cur;
@@ -2988,8 +2985,7 @@ void tio_set_html_expansion(unsigned int html_char_val,
 /*
  *   Write out a c-style (null-terminated) string.
  */
-int outformat(char *s)
-{
+int outformat(const char *s) {
     return outformatlen(s, strlen(s));
 }
 
@@ -2999,11 +2995,10 @@ int outformat(char *s)
  *   This routine sends out a string, one character at a time (via outchar).
  *   Escape codes ('\n', and so forth) are handled here.
  */
-int outformatlen(char *s, uint slen)
-{
+int outformatlen(const char *s, uint slen) {
     char     c;
     uint     orig_slen;
-    char    *orig_s;
+    const char *orig_s;
     int      ret;
     int      called_filter;
 
diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp
index 5a9ede0..4179ff2 100644
--- a/engines/glk/tads/tads2/run.cpp
+++ b/engines/glk/tads/tads2/run.cpp
@@ -281,7 +281,7 @@ void runrepush(runcxdef *ctx, runsdef *val)
 }
 
 /* push a counted-length string onto the stack */
-void runpstr(runcxdef *ctx, char *str, int len, int sav)
+void runpstr(runcxdef *ctx, const char *str, int len, int sav)
 {
 	runsdef val;
 
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index 6c303b8..297cb9d 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -146,7 +146,7 @@ void runpnil(runcxdef *ctx);
 void runpbuf(runcxdef *ctx, int typ, void *val);
 
 /* push a counted-length string onto the stack */
-void runpstr(runcxdef *ctx, char *str, int len, int sav);
+void runpstr(runcxdef *ctx, const char *str, int len, int sav);
 
 /*
  *   Push a C-style string onto the stack, converting escape codes.  If
diff --git a/engines/glk/tads/tads2/runtime_driver.cpp b/engines/glk/tads/tads2/runtime_driver.cpp
index 20ba6c1..aec8670 100644
--- a/engines/glk/tads/tads2/runtime_driver.cpp
+++ b/engines/glk/tads/tads2/runtime_driver.cpp
@@ -789,9 +789,7 @@ static void trdmain1(errcxdef *ec, int argc, char *argv[],
 #endif
 
 /* log an error */
-static void trdlogerr(void *ctx0, char *fac, int err,
-                      int argc, erradef *argv)
-{
+static void trdlogerr(void *ctx0, const char *fac, int err, int argc, erradef *argv) {
     errcxdef *ctx = (errcxdef *)ctx0;
     char      buf[256];
     char      msg[256];
diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h
index 9a0e198..d4dd16f 100644
--- a/engines/glk/tads/tads2/text_io.h
+++ b/engines/glk/tads/tads2/text_io.h
@@ -95,7 +95,7 @@ void outfmt(tiocxdef *ctx, uchar *txt);
 int outformat(const char *s);
 
 /* format a counted-length string, which may not be null-terminated */
-int outformatlen(char *s, uint len);
+int outformatlen(const char *s, uint len);
 
 /* flush output, with specified newline mode */
 void outflushn(int nl);
diff --git a/engines/glk/tads/tads2/vocabulary_parser.cpp b/engines/glk/tads/tads2/vocabulary_parser.cpp
index 7190058..0b7bed6 100644
--- a/engines/glk/tads/tads2/vocabulary_parser.cpp
+++ b/engines/glk/tads/tads2/vocabulary_parser.cpp
@@ -667,7 +667,7 @@ struct vocerr_va_info
     char user_msg[400];
 
     /* the sprintf-style format string to display */
-    char *fmt;
+    const char *fmt;
 
     /* 
      *   Pointer to the output buffer to use to format the string 'fmt' with
@@ -734,7 +734,7 @@ static void vocerr_va_prep(voccxdef *ctx, struct vocerr_va_info *info,
             };
             struct argbuf_t  args[5];
             struct argbuf_t *argp;
-            char  *p;
+            const char  *p;
 
             /* 
              *   Retrieve the arguments by examining the format string.  We


Commit: 5381930e8b09f61b8c7969db4a657067586be897
    https://github.com/scummvm/scummvm/commit/5381930e8b09f61b8c7969db4a657067586be897
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00

Commit Message:
GLK: TADS2: Fix uninitialized variable warnings

Changed paths:
    engines/glk/tads/tads2/built_in.cpp
    engines/glk/tads/tads2/execute_command.cpp
    engines/glk/tads/tads2/file_io.cpp
    engines/glk/tads/tads2/line_source_file.cpp
    engines/glk/tads/tads2/object.cpp


diff --git a/engines/glk/tads/tads2/built_in.cpp b/engines/glk/tads/tads2/built_in.cpp
index c67399d..5831903 100644
--- a/engines/glk/tads/tads2/built_in.cpp
+++ b/engines/glk/tads/tads2/built_in.cpp
@@ -1189,8 +1189,8 @@ void bifsub(bifcxdef *ctx, int argc)
 /* cvtstr - convert value to a string */
 void bifcvs(bifcxdef *ctx, int argc)
 {
-    char *p;
-    int   len;
+    char *p = nullptr;
+    int   len = 0;
     char  buf[30];
     
     bifcntargs(ctx, 1, argc);
diff --git a/engines/glk/tads/tads2/execute_command.cpp b/engines/glk/tads/tads2/execute_command.cpp
index d38d92b..d8ad6b2 100644
--- a/engines/glk/tads/tads2/execute_command.cpp
+++ b/engines/glk/tads/tads2/execute_command.cpp
@@ -2645,7 +2645,7 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
                 {
                     uchar   *l = runpoplst(rcx);
                     uint     lstsiz;
-                    objnum   defobj;
+                    objnum   defobj = 0;
                     int      objcnt;
                     objnum   newprep;
                     runsdef  val;
@@ -2841,7 +2841,7 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
                 {
                     uchar   *l = runpoplst(rcx);
                     uint     lstsiz;
-                    objnum   defobj;
+                    objnum   defobj = 0;
                     int      objcnt;
                     runsdef  val;
                     objnum   o;
@@ -3218,7 +3218,7 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
                 uchar   *l = runpoplst(rcx);
                 uint     lstsiz;
                 int      objcnt;
-                objnum   defobj;
+                objnum   defobj = 0;
                 objnum   o;
                 runsdef  val;
                 
@@ -3469,9 +3469,9 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
                     }
                     else
                     {
-                        int is_him;
-                        int is_her;
-                        int is_them;
+                        int is_him = 0;
+                        int is_her = 0;
+                        int is_them = 0;
 
                         /* run through the objects and check him/her */
                         for (i = 0 ; i < vcnt ; ++i)
diff --git a/engines/glk/tads/tads2/file_io.cpp b/engines/glk/tads/tads2/file_io.cpp
index 10b8bca..5495c1b 100644
--- a/engines/glk/tads/tads2/file_io.cpp
+++ b/engines/glk/tads/tads2/file_io.cpp
@@ -50,7 +50,7 @@ void OS_LOADDS fioldobj(void *ctx0, mclhd handle, uchar *ptr, ushort siz)
     osfildef   *fp = ctx->fiolcxfp;
     char        buf[7];
     errcxdef   *ec = ctx->fiolcxerr;
-    uint        rdsiz;
+    uint        rdsiz = 0;
     
     /* figure out what type of object is to be loaded */
     osfseek(fp, seekpos + ctx->fiolcxst, OSFSK_SET);
@@ -714,8 +714,8 @@ static void fiord1(mcmcxdef *mctx, voccxdef *vctx, tokcxdef *tctx,
             uchar *p;
             uchar *bufp;
             ulong  bsiz;
-            int    len1;
-            int    len2;
+            int    len1 = 0;
+            int    len2 = 0;
             
             /* do it in a single file read, if we can, for speed */
             curpos = osfpos(fp) - startofs;
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index 430b1d9..affc3cd 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -87,7 +87,7 @@ linfdef *linfini(mcmcxdef *mctx, errcxdef *ec, char *filename,
     int       i;
     objnum   *objp;
     linfdef  *linf;
-    osfildef *fp;
+    osfildef *fp = nullptr;
     char      fbuf[OSFNMAX + 1];
     tokpdef   fakepath;
     int       len;
@@ -404,8 +404,8 @@ void linfglop2(lindef *lin, uchar *buf)
 int linfwrt(lindef *lin, osfildef *fp)
 {
 #define linf ((linfdef *)lin)
-#define UCHAR_MAX 255
-    uchar   buf[UCHAR_MAX + 6];
+#define BYTE_MAX 255
+    uchar   buf[BYTE_MAX + 6];
     size_t  len;
     uint    pgcnt;
     uchar  *objp;
@@ -413,7 +413,7 @@ int linfwrt(lindef *lin, osfildef *fp)
 
     buf[0] = lin->linid;
     len = strlen(linf->linfnam);
-    if (len > UCHAR_MAX)
+    if (len > BYTE_MAX)
         return FALSE;
     buf[1] = (uchar)len;
     oswp4(buf + 2, linf->linfcrec);
diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp
index 4360f5f..4e6f309 100644
--- a/engines/glk/tads/tads2/object.cpp
+++ b/engines/glk/tads/tads2/object.cpp
@@ -42,11 +42,11 @@ uint objgetp(mcmcxdef *mctx, objnum objn, prpnum prop, dattyp *typptr)
     uint    retval;                       /* property offset, if we find it */
     uint    ignprop; /* ignored property - use if real property isn't found */
     uchar   pbuf[2];                  /* property number in portable format */
-    uchar  *indp;
+    uchar  *indp = nullptr;
     uchar  *indbase;
     int     last;
     int     first;
-    int     cur;
+    int     cur = 0;
     
     oswp2(pbuf, prop);            /* get property number in portable foramt */
     objptr = (objdef *)mcmlck(mctx, objn);      /* get a lock on the object */
@@ -192,7 +192,7 @@ static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
 {
     uchar  *sc;
     ushort  sccnt;
-    ushort  psav;
+    ushort  psav = 0;
     dattyp  typsav = DAT_NIL;
     objnum  osavn = MCMONINV;
     uchar  *o1;
@@ -240,7 +240,7 @@ static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
         /* if we found the property, remember it */
         if (poff != 0)
         {
-            int isdesc;
+            int isdesc = 0;
             
             /* if we have a previous object, determine lineage */
             if (found)


Commit: 8f9fcec16c41ba4bfc51e7b92bdc701f5a2a2e6b
    https://github.com/scummvm/scummvm/commit/8f9fcec16c41ba4bfc51e7b92bdc701f5a2a2e6b
Author: dreammaster (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00

Commit Message:
GLK: TADS2: Further compilation fixes

Changed paths:
    engines/glk/tads/tads2/built_in.cpp
    engines/glk/tads/tads2/data.cpp
    engines/glk/tads/tads2/data.h
    engines/glk/tads/tads2/debug.cpp
    engines/glk/tads/tads2/debug.h
    engines/glk/tads/tads2/execute_command.cpp
    engines/glk/tads/tads2/get_string.cpp
    engines/glk/tads/tads2/line_source_file.cpp
    engines/glk/tads/tads2/object.cpp
    engines/glk/tads/tads2/object.h
    engines/glk/tads/tads2/output.cpp
    engines/glk/tads/tads2/run.cpp
    engines/glk/tads/tads2/run.h
    engines/glk/tads/tads2/runtime_driver.cpp
    engines/glk/tads/tads2/text_io.h
    engines/glk/tads/tads2/vocabulary.cpp
    engines/glk/tads/tads2/vocabulary.h
    engines/glk/tads/tads2/vocabulary_parser.cpp


diff --git a/engines/glk/tads/tads2/built_in.cpp b/engines/glk/tads/tads2/built_in.cpp
index 5831903..4cc6e15 100644
--- a/engines/glk/tads/tads2/built_in.cpp
+++ b/engines/glk/tads/tads2/built_in.cpp
@@ -1189,7 +1189,7 @@ void bifsub(bifcxdef *ctx, int argc)
 /* cvtstr - convert value to a string */
 void bifcvs(bifcxdef *ctx, int argc)
 {
-    char *p = nullptr;
+    const char *p = nullptr;
     int   len = 0;
     char  buf[30];
     
@@ -1973,7 +1973,7 @@ void bifsct(bifcxdef *bifctx, int argc)
  *   Portable keystroke mappings.  We map the extended key codes to these
  *   strings, so that the TADS code can access arrow keys and the like.  
  */
-static char *ext_key_names[] =
+static const char *ext_key_names[] =
 {
     "[up]",                                                   /* CMD_UP - 1 */
     "[down]",                                               /* CMD_DOWN - 2 */
@@ -2142,8 +2142,8 @@ void bifwrd(bifcxdef *ctx, int argc)
     int       ob;
     vocoldef *v;
     uchar     buf[128];
-    uchar    *dst;
-    uchar    *src;
+    uchar *dst;
+    const uchar *src;
     uint      len;
     runsdef   val;
     
@@ -2169,13 +2169,13 @@ void bifwrd(bifcxdef *ctx, int argc)
     /* now build a list of strings from the words, if there are any */
     if (v != 0 && voclistlen(v) != 0 && v->vocolfst != 0 && v->vocollst != 0)
     {
-        for (dst = buf + 2, src = (uchar *)v->vocolfst ;
-             src <= (uchar *)v->vocollst ; src += len + 1)
+        for (dst = buf + 2, src = (const uchar *)v->vocolfst ;
+             src <= (const uchar *)v->vocollst ; src += len + 1)
         {
             *dst++ = DAT_SSTRING;
-            len = strlen((char *)src);
+            len = strlen((const char *)src);
             oswp2(dst, len + 2);
-            strcpy((char *)dst + 2, (char *)src);
+            strcpy((char *)dst + 2, (const char *)src);
             dst += len + 2;
         }
     }
diff --git a/engines/glk/tads/tads2/data.cpp b/engines/glk/tads/tads2/data.cpp
index fe002a3..dfd641c 100644
--- a/engines/glk/tads/tads2/data.cpp
+++ b/engines/glk/tads/tads2/data.cpp
@@ -32,7 +32,7 @@ namespace TADS {
 namespace TADS2 {
 
 /* return size of a data value */
-uint datsiz(dattyp typ, void *val)
+uint datsiz(dattyp typ, const void *val)
 {
     switch(typ)
     {
@@ -45,7 +45,7 @@ uint datsiz(dattyp typ, void *val)
     case DAT_SSTRING:
     case DAT_DSTRING:
     case DAT_LIST:
-        return(osrp2((char *)val));
+        return(osrp2((const char *)val));
 
     case DAT_NIL:
     case DAT_TRUE:
@@ -59,10 +59,10 @@ uint datsiz(dattyp typ, void *val)
         
     case DAT_TPL:
         /* template is counted array of 10-byte entries, plus length byte */
-        return(1 + ((*(uchar *)val) * VOCTPLSIZ));
+        return(1 + ((*(const uchar *)val) * VOCTPLSIZ));
 
     case DAT_TPL2:
-        return(1 + ((*(uchar *)val) * VOCTPL2SIZ));
+        return(1 + ((*(const uchar *)val) * VOCTPL2SIZ));
 
     default:
         return(0);
diff --git a/engines/glk/tads/tads2/data.h b/engines/glk/tads/tads2/data.h
index e573730..6a914cd 100644
--- a/engines/glk/tads/tads2/data.h
+++ b/engines/glk/tads/tads2/data.h
@@ -50,7 +50,7 @@ enum DataType {
 typedef int dattyp;
 
 /* determine the size of a piece of data */
-uint datsiz(dattyp typ, void *valptr);
+uint datsiz(dattyp typ, const void *valptr);
 
 } // End of namespace TADS2
 } // End of namespace TADS
diff --git a/engines/glk/tads/tads2/debug.cpp b/engines/glk/tads/tads2/debug.cpp
index 94e0b37..726cbe2 100644
--- a/engines/glk/tads/tads2/debug.cpp
+++ b/engines/glk/tads/tads2/debug.cpp
@@ -188,12 +188,12 @@ int dbgnam(dbgcxdef *ctx, char *outbuf, int typ, int val)
 }
 
 /* send a buffer value (as from a list) to ui callback for display */
-static void dbgpbval(dbgcxdef *ctx, dattyp typ, uchar *val,
+static void dbgpbval(dbgcxdef *ctx, dattyp typ, const uchar *val,
                      void (*dispfn)(void *, const char *, int),
                      void *dispctx)
 {
     char  buf[TOKNAMMAX + 1];
-    char *p = buf;
+    const char *p = buf;
     uint  len;
 
     switch(typ)
@@ -209,7 +209,7 @@ static void dbgpbval(dbgcxdef *ctx, dattyp typ, uchar *val,
         
     case DAT_SSTRING:
         len = osrp2(val) - 2;
-        p = (char *)val + 2;
+        p = (const char *)val + 2;
         break;
         
     case DAT_NIL:
@@ -220,10 +220,10 @@ static void dbgpbval(dbgcxdef *ctx, dattyp typ, uchar *val,
     case DAT_LIST:
         (*dispfn)(dispctx, "[", 1);
         len = osrp2(val) - 2;
-        p = (char *)val + 2;
+        p = (const char *)val + 2;
         while (len)
         {
-            dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
+            dbgpbval(ctx, (dattyp)*p, (const uchar *)(p + 1), dispfn, dispctx);
             lstadv((uchar **)&p, &len);
             if (len) (*dispfn)(dispctx, " ", 1);
         }
@@ -265,8 +265,8 @@ void dbgpval(dbgcxdef *ctx, runsdef *val,
 {
     uchar   buf[TOKNAMMAX + 1];
     uint    len;
-    uchar  *p = buf;
-    char   *typ = 0;
+    const uchar  *p = buf;
+    const char *typ = 0;
     
     switch(val->runstyp)
     {
@@ -288,27 +288,28 @@ void dbgpval(dbgcxdef *ctx, runsdef *val,
         break;
         
     case DAT_NIL:
-        p = (uchar *)"nil";
+        p = (const uchar *)"nil";
         len = 3;
         break;
 
-    case DAT_LIST:
+    case DAT_LIST: {
         if (showtype) (*dispfn)(dispctx, "list: ", 6);
         (*dispfn)(dispctx, "[", 1);
         len = osrp2(val->runsv.runsvstr) - 2;
-        p = val->runsv.runsvstr + 2;
+        uchar *up = val->runsv.runsvstr + 2;
         while (len)
         {
-            dbgpbval(ctx, (dattyp)*p, (uchar *)(p + 1), dispfn, dispctx);
-            lstadv(&p, &len);
+            dbgpbval(ctx, (dattyp)*up, (const uchar *)(up + 1), dispfn, dispctx);
+            lstadv(&up, &len);
             if (len) (*dispfn)(dispctx, " ", 1);
         }
         (*dispfn)(dispctx, "]", 1);
         len = 0;
+	p = up;
         break;
-        
+    } 
     case DAT_TRUE:
-        p = (uchar *)"true";
+        p = (const uchar *)"true";
         len = 4;
         break;
         
@@ -323,7 +324,7 @@ void dbgpval(dbgcxdef *ctx, runsdef *val,
         break;
         
     default:
-        p = (uchar *)"[unknown type]";
+        p = (const uchar *)"[unknown type]";
         len = 14;
         break;
     }
@@ -356,7 +357,7 @@ void dbgpval(dbgcxdef *ctx, runsdef *val,
 
     /* display the text */
     if (len != 0)
-        (*dispfn)(dispctx, (char *)p, len);
+        (*dispfn)(dispctx, (const char *)p, len);
 
     /* add a closing quote if it's a string and we showed an open quote */
     if (val->runstyp == DAT_SSTRING && !(typ && showtype))
@@ -446,7 +447,7 @@ static void dbgdsdisp(void *ctx, const char *buf, int bufl)
     if (buf[0] == '\n')
         tioflush((tiocxdef *)ctx);
     else
-        tioputslen((tiocxdef *)ctx, (char *)buf, bufl);
+        tioputslen((tiocxdef *)ctx, (const char *)buf, bufl);
 }
 
 /* dump the stack */
diff --git a/engines/glk/tads/tads2/debug.h b/engines/glk/tads/tads2/debug.h
index 0e7369e..3c53a89 100644
--- a/engines/glk/tads/tads2/debug.h
+++ b/engines/glk/tads/tads2/debug.h
@@ -547,7 +547,7 @@ void trcsho(void);
 
 #ifdef DBG_OFF
 #define dbgenter(ctx, bp, self, target, prop, binum, argc)
-#define dbgleave(ctx, exittype)
+#define dbgleave(ctx, exittype) ((void)0)
 #define dbgdump(ctx) ((void)0)
 #define dbgrst(ctx) ((void)0)
 #define dbgframe(ctx, frofs, linofs)
diff --git a/engines/glk/tads/tads2/execute_command.cpp b/engines/glk/tads/tads2/execute_command.cpp
index d8ad6b2..63c5f02 100644
--- a/engines/glk/tads/tads2/execute_command.cpp
+++ b/engines/glk/tads/tads2/execute_command.cpp
@@ -2264,7 +2264,7 @@ static void voc_askobj_indirect(voccxdef *ctx, vocoldef *dolist,
     cnt = voclistlen(dolist);
     for (len = 0, i = 0 ; i < cnt ; )
     {
-        char *p;
+        const char *p;
         size_t curlen;
         int j;
             
@@ -2331,7 +2331,7 @@ static void voc_askobj_indirect(voccxdef *ctx, vocoldef *dolist,
      */
     for (i = 0 ; i < cnt ; )
     {
-        char *p;
+        const char *p;
         uchar *subp;
         uchar *subsubp;
         size_t curlen;
@@ -3427,7 +3427,7 @@ int execmd(voccxdef *ctx, objnum actor, objnum prep,
                     int   i;
                     int   vcnt;
                     int   distinct;
-                    char *lastfst;
+                    const char *lastfst;
 
                     /*
                      *   If possible, tailor the pronoun to the situation
diff --git a/engines/glk/tads/tads2/get_string.cpp b/engines/glk/tads/tads2/get_string.cpp
index 69982fc..624b8d9 100644
--- a/engines/glk/tads/tads2/get_string.cpp
+++ b/engines/glk/tads/tads2/get_string.cpp
@@ -56,7 +56,7 @@ extern int scrquiet;
  *   output flushing.  Prompting is to be done by the caller.  This
  *   routine should be called instead of os_gets.
  */
-int getstring(char *prompt, char *buf, int bufl)
+int getstring(const char *prompt, char *buf, int bufl)
 {
     char  *result;
     int    savemoremode;
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index affc3cd..c2942a8 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -31,6 +31,7 @@ namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
+#define BYTE_MAX 0xff
 
 /* initialize a pre-allocated linfdef, skipping debugger page setup */
 void linfini2(mcmcxdef *mctx, linfdef *linf,
@@ -404,7 +405,6 @@ void linfglop2(lindef *lin, uchar *buf)
 int linfwrt(lindef *lin, osfildef *fp)
 {
 #define linf ((linfdef *)lin)
-#define BYTE_MAX 255
     uchar   buf[BYTE_MAX + 6];
     size_t  len;
     uint    pgcnt;
@@ -440,7 +440,7 @@ int linfwrt(lindef *lin, osfildef *fp)
 int linfload(osfildef *fp, dbgcxdef *dbgctx, errcxdef *ec, tokpdef *path)
 {
     linfdef *linf;
-    uchar    buf[UCHAR_MAX + 6];
+    uchar    buf[BYTE_MAX + 6];
     uint     pgcnt;
     uchar   *objp;
     mcmon   *objn;
diff --git a/engines/glk/tads/tads2/object.cpp b/engines/glk/tads/tads2/object.cpp
index 4e6f309..7bd2f3a 100644
--- a/engines/glk/tads/tads2/object.cpp
+++ b/engines/glk/tads/tads2/object.cpp
@@ -419,7 +419,7 @@ void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only)
  *   pointer is null, no undo information is kept.  
  */
 void objsetp(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ,
-             void *val, objucxdef *undoctx)
+             const void *val, objucxdef *undoctx)
 {
     objdef *objptr;
     prpdef *p;
diff --git a/engines/glk/tads/tads2/object.h b/engines/glk/tads/tads2/object.h
index 5e2499c..0536f05 100644
--- a/engines/glk/tads/tads2/object.h
+++ b/engines/glk/tads/tads2/object.h
@@ -275,7 +275,7 @@ objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz);
  *   undo information is retained. 
  */
 void objsetp(mcmcxdef *ctx, objnum obj, prpnum prop,
-             dattyp typ, void *val, objucxdef *undoctx);
+             dattyp typ, const void *val, objucxdef *undoctx);
 
 /* 
  *   Delete a property.  If mark_only is true, we'll only mark the
diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 6f5cd84..79284b7 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -3203,7 +3203,7 @@ int tiologcls(tiocxdef *ctx)
  *   caller should not include any newlines in the text being displayed
  *   here.  
  */
-void out_logfile_print(char *txt, int nl)
+void out_logfile_print(const char *txt, int nl)
 {
     /* if there's no log file, there's nothing to do */
     if (logfp == 0)
diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp
index 4179ff2..64b52a4 100644
--- a/engines/glk/tads/tads2/run.cpp
+++ b/engines/glk/tads/tads2/run.cpp
@@ -298,9 +298,9 @@ void runpstr(runcxdef *ctx, const char *str, int len, int sav)
 }
 
 /* push a C-style string, converting escape codes */
-void runpushcstr(runcxdef *ctx, char *str, size_t len, int sav)
+void runpushcstr(runcxdef *ctx, const char *str, size_t len, int sav)
 {
-	char    *p;
+	const char *p;
 	char    *dst;
 	size_t   need;
 	runsdef  val;
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index 297cb9d..0a39fca 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -153,7 +153,7 @@ void runpstr(runcxdef *ctx, const char *str, int len, int sav);
  *   the character contains backslashes, newline, or tab characters, we'll
  *   convert these characters to their escaped equivalent.
  */
-void runpushcstr(runcxdef *ctx, char *str, size_t len, int sav);
+void runpushcstr(runcxdef *ctx, const char *str, size_t len, int sav);
 
 /*
  *   Push a property onto the stack.  codepp is a pointer to the caller's
diff --git a/engines/glk/tads/tads2/runtime_driver.cpp b/engines/glk/tads/tads2/runtime_driver.cpp
index aec8670..6b6a6d7 100644
--- a/engines/glk/tads/tads2/runtime_driver.cpp
+++ b/engines/glk/tads/tads2/runtime_driver.cpp
@@ -224,7 +224,7 @@ static void trdusage(errcxdef *ec)
         errmsg(ec, buf, (uint)sizeof(buf), ERR_TRUSPARM);
 
         /* format in the application name */
-        argv[0].errastr = (char *)ec->errcxappctx->usage_app_name;
+        argv[0].errastr = ec->errcxappctx->usage_app_name;
         errfmt(buf2, (int)sizeof(buf2), buf, 1, argv);
         
         /* display it */
diff --git a/engines/glk/tads/tads2/text_io.h b/engines/glk/tads/tads2/text_io.h
index d4dd16f..c6ba34c 100644
--- a/engines/glk/tads/tads2/text_io.h
+++ b/engines/glk/tads/tads2/text_io.h
@@ -111,7 +111,7 @@ void outreset(void);
  *   (in particular, if no more input is available from the keyboard),
  *   zero on success.  
  */
-int getstring(char *prompt, char *buf, int bufl);
+int getstring(const char *prompt, char *buf, int bufl);
 
 /* set capitalize-next-character mode on/off */
 void outcaps(void);
@@ -133,7 +133,7 @@ int tiologcls(tiocxdef *ctx);
  *   caller should not include any newlines in the text being displayed
  *   here.  
  */
-void out_logfile_print(char *txt, int nl);
+void out_logfile_print(const char *txt, int nl);
 
 
 /*
diff --git a/engines/glk/tads/tads2/vocabulary.cpp b/engines/glk/tads/tads2/vocabulary.cpp
index 8c4fead..e04dcb5 100644
--- a/engines/glk/tads/tads2/vocabulary.cpp
+++ b/engines/glk/tads/tads2/vocabulary.cpp
@@ -68,7 +68,7 @@ vocwdef *vocwget(voccxdef *ctx, uint idx)
 #endif /*VOCW_IN_CACHE */
 
 /* hash value is based on first 6 characters only to allow match-in-6 */
-uint vochsh(uchar *t, int len)
+uint vochsh(const uchar *t, int len)
 {
     uint ret = 0;
     
@@ -80,7 +80,7 @@ uint vochsh(uchar *t, int len)
 }
 
 /* copy vocabulary word, and convert to lower case */
-static void voccpy(uchar *dst, uchar *src, int len)
+static void voccpy(uchar *dst, const uchar *src, int len)
 {
     for ( ; len ; --len, ++dst, ++src)
         *dst = vocisupper(*src) ? tolower(*src) : *src;
diff --git a/engines/glk/tads/tads2/vocabulary.h b/engines/glk/tads/tads2/vocabulary.h
index d28dcfe..01fcbf3 100644
--- a/engines/glk/tads/tads2/vocabulary.h
+++ b/engines/glk/tads/tads2/vocabulary.h
@@ -163,8 +163,8 @@ struct vocddef {
 /* vocabulary object list entry */
 struct vocoldef {
     objnum  vocolobj;                           /* object matching the word */
-    char   *vocolfst;         /* first word in cmd[] that identified object */
-    char   *vocollst;          /* last word in cmd[] that identified object */
+    const char *vocolfst;     /* first word in cmd[] that identified object */
+    const char *vocollst;      /* last word in cmd[] that identified object */
     char   *vocolhlst;      /* hypothetical last word, if we trimmed a prep */
     int     vocolflg;                           /* special flags (ALL, etc) */
 };
@@ -346,14 +346,14 @@ void vociren(voccxdef *ctx, objnum oldnum, objnum newnum);
 struct vocseadef {
     vocdef  *v;
     vocwdef *vw;
-    uchar   *wrd1;
+    const uchar *wrd1;
     int      len1;
-    uchar   *wrd2;
+    const uchar *wrd2;
     int      len2;
 };
 
 /* find first word matching a given word */
-vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
+vocwdef *vocffw(voccxdef *ctx, const char *wrd, int len, const char *wrd2, int len2,
                 int p, vocseadef *search_ctx);
 
 /* find next word */
@@ -435,7 +435,7 @@ int try_preparse_cmd(voccxdef *ctx, char **cmd, int wrdcnt,
  */
 int try_unknown_verb(voccxdef *ctx, objnum actor,
                      char **cmd, int *typelist, int wrdcnt, int *next_start,
-                     int do_fuses, int err, char *msg, ...);
+                     int do_fuses, int err, const char *msg, ...);
 
 /* find a template */
 int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep,
@@ -445,7 +445,7 @@ int voctplfnd(voccxdef *ctx, objnum verb_in, objnum prep,
 void voc_make_obj_name(voccxdef *ctx, char *namebuf, char *cmd[],
                        int firstwrd, int lastwrd);
 void voc_make_obj_name_from_list(voccxdef *ctx, char *namebuf,
-                                 char *cmd[], char *firstwrd, char *lastwrd);
+                                 char *cmd[], const char *firstwrd, const char *lastwrd);
 
 /*
  *   check noun - determines whether the next set of words is a valid noun
@@ -506,7 +506,7 @@ void vocnoreach(voccxdef *ctx, objnum *list1, int cnt,
                 int multi_base_index, int multi_total_count);
 
 /* set {numObj | strObj}.value, as appropriate */
-void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
+void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, const void *val,
                vocoldef *inobj, vocoldef *outobj);
 
 /* macros to read values out of templates */
@@ -565,7 +565,7 @@ void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
 
 /* structure for special internal word table */
 struct vocspdef {
-    char *vocspin;
+    const char *vocspin;
     char  vocspout;
 };
 
@@ -643,7 +643,7 @@ void vocdusave_delobj(voccxdef *ctx, objnum objn);
 void vocdusave_me(voccxdef *ctx, objnum old_me);
 
 /* compute vocabulary word hash value */
-uint vochsh(uchar *t, int len);
+uint vochsh(const uchar *t, int len);
 
 /* TADS versions of isalpha, isspace, isdigit, etc */
 #define vocisupper(c) ((uchar)(c) <= 127 && Common::isUpper((uchar)(c)))
diff --git a/engines/glk/tads/tads2/vocabulary_parser.cpp b/engines/glk/tads/tads2/vocabulary_parser.cpp
index 0b7bed6..a70c6f8 100644
--- a/engines/glk/tads/tads2/vocabulary_parser.cpp
+++ b/engines/glk/tads/tads2/vocabulary_parser.cpp
@@ -32,7 +32,7 @@ namespace TADS {
 namespace TADS2 {
 
 
-static char *type_names[] =
+static const char *type_names[] =
 {
     "article", "adj", "noun", "prep", "verb", "special", "plural",
     "unknown"
@@ -203,10 +203,10 @@ void voc_push_objlist(voccxdef *ctx, objnum objlist[], int cnt)
  *   byte.  The list is bounded by firstwrd and lastwrd, inclusive of
  *   both.  
  */
-static void voc_push_strlist(voccxdef *ctx, char *firstwrd, char *lastwrd)
+static void voc_push_strlist(voccxdef *ctx, const char *firstwrd, const char *lastwrd)
 {
     size_t curlen;
-    char *p;
+    const char *p;
     uint lstsiz;
     uchar *lstp;
 
@@ -390,7 +390,7 @@ static void voc_push_toklist(voccxdef *ctx, char *wordlist[], int cnt)
 int vocread(voccxdef *ctx, objnum actor, objnum verb,
             char *buf, int bufl, int type)
 {
-    char *prompt;
+    const char *prompt;
     int ret;
 
     /* presume we'll return success */
@@ -528,7 +528,7 @@ int vocread(voccxdef *ctx, objnum actor, objnum verb,
  *   the user's entry, the second is the reference word in the dictionary.)
  *   Returns TRUE if the words match, FALSE otherwise.
  */
-static int voceq(uchar *s1, uint l1, uchar *s2, uint l2)
+static int voceq(const uchar *s1, uint l1, uchar *s2, uint l2)
 {
     uint i;
 
@@ -599,7 +599,7 @@ vocwdef *vocfnw(voccxdef *voccx, vocseadef *search_ctx)
 }
 
 /* find the first vocdef matching a set of words */
-vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
+vocwdef *vocffw(voccxdef *ctx, const char *wrd, int len, const char *wrd2, int len2,
                 int p, vocseadef *search_ctx)
 {
     uint     hshval;
@@ -607,15 +607,15 @@ vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
     vocwdef *vw, *vwf = nullptr;
 
     /* get the word's hash value */
-    hshval = vochsh((uchar *)wrd, len);
+    hshval = vochsh((const uchar *)wrd, len);
 
     /* scan the hash list until we run out of entries, or find a match */
     for (v = ctx->voccxhsh[hshval], vf = 0 ; v != 0 && vf == 0 ;
          v = v->vocnxt)
     {
         /* if this word matches, look at the objects in its list */
-        if (voceq((uchar *)wrd, len, v->voctxt, v->voclen)
-            && voceq((uchar *)wrd2, len2, v->voctxt + v->voclen, v->vocln2))
+        if (voceq((const uchar *)wrd, len, v->voctxt, v->voclen)
+            && voceq((const uchar *)wrd2, len2, v->voctxt + v->voclen, v->vocln2))
         {
             /* look for a suitable object in the vocwdef list */
             for (vw = vocwget(ctx, v->vocwlst) ; vw ;
@@ -646,9 +646,9 @@ vocwdef *vocffw(voccxdef *ctx, char *wrd, int len, char *wrd2, int len2,
         search_ctx->vw = vw;
 
         /* save the search criteria */
-        search_ctx->wrd1 = (uchar *)wrd;
+        search_ctx->wrd1 = (const uchar *)wrd;
         search_ctx->len1 = len;
-        search_ctx->wrd2 = (uchar *)wrd2;
+        search_ctx->wrd2 = (const uchar *)wrd2;
         search_ctx->len2 = len2;
     }
 
@@ -970,7 +970,7 @@ void vocerr(voccxdef *ctx, int err, const char *f, ...)
  */
 int try_unknown_verb(voccxdef *ctx, objnum actor,
                      char **cmd, int *typelist, int wrdcnt, int *next_start,
-                     int do_fuses, int vocerr_err, char *vocerr_msg, ...)
+                     int do_fuses, int vocerr_err, const char *vocerr_msg, ...)
 {
     int  show_msg;
     va_list  argptr;
@@ -1169,7 +1169,7 @@ int try_unknown_verb(voccxdef *ctx, objnum actor,
 #define vocisspec(wrd) \
    (vocisupper(*wrd) || (!vocisalpha(*wrd) && *wrd != '\'' && *wrd != '-'))
 
-static vocspdef vocsptab[] =
+static const vocspdef vocsptab[] =
 {
     { "of",     VOCW_OF   },
     { "and",    VOCW_AND  },
@@ -1191,7 +1191,7 @@ static vocspdef vocsptab[] =
 };
 
 /* test a word to see if it's a particular special word */
-static int voc_check_special(voccxdef *ctx, char *wrd, int checktyp)
+static int voc_check_special(voccxdef *ctx, const char *wrd, int checktyp)
 {
     /* search the user or built-in special table, as appropriate */
     if (ctx->voccxspp)
@@ -1219,13 +1219,13 @@ static int voc_check_special(voccxdef *ctx, char *wrd, int checktyp)
     }
     else
     {
-        vocspdef *x;
+        const vocspdef *x;
         
         for (x = vocsptab ; x->vocspin ; ++x)
         {
             /* if it matches in type and text, we have a match */
             if (x->vocspout == checktyp
-                && !strncmp((char *)wrd, x->vocspin, (size_t)6))
+                && !strncmp((const char *)wrd, x->vocspin, (size_t)6))
                 return TRUE;
         }
     }
@@ -1240,7 +1240,7 @@ int voctok(voccxdef *ctx, char *cmd, char *outbuf, char **wrd,
            int lower, int cvt_ones, int show_errors)
 {
     int       i;
-    vocspdef *x;
+    const vocspdef *x;
     int       l;
     char     *p;
     char     *w;
@@ -1355,8 +1355,7 @@ int voctok(voccxdef *ctx, char *cmd, char *outbuf, char **wrd,
             {
                 for (x = vocsptab ; x->vocspin ; ++x)
                 {
-                    if (!strncmp((char *)wrd[i-1], (char *)x->vocspin,
-                                 (size_t)6)
+                    if (!strncmp((char *)wrd[i-1], x->vocspin, (size_t)6)
                         && (cvt_ones ||
                             (x->vocspout != VOCW_ONE
                              && x->vocspout != VOCW_ONES))
@@ -1933,7 +1932,7 @@ static int vocisect_flags(objnum *list1, uint *flags1,
 static int vocgol(voccxdef *ctx, objnum *list, uint *flags, char **wrdlst,
                   int *typlst, int first, int cur, int last, int ofword)
 {
-    char      *wrd;
+    const char *wrd;
     int        typ;
     vocwdef   *v;
     int        cnt;
@@ -2437,7 +2436,7 @@ void voc_make_obj_name(voccxdef *ctx, char *namebuf, char *cmd[],
  *   Make an object name from a list entry 
  */
 void voc_make_obj_name_from_list(voccxdef *ctx, char *namebuf,
-                                 char *cmd[], char *firstwrd, char *lastwrd)
+                                 char *cmd[], const char *firstwrd, const char *lastwrd)
 {
     int i, i1, i2;
     
@@ -2583,7 +2582,7 @@ static int vocg1o(voccxdef *ctx, char *cmd[], int typelist[],
             n++;
             if (!cmd[n])
             {
-                char *p;
+                const char *p;
                 int   ver;
                 
                 if (vocspec(cmd[cur], VOCW_ALL))
@@ -4628,7 +4627,7 @@ int vocchkvis(voccxdef *ctx, objnum obj, objnum cmdActor)
 }
 
 /* set {numObj | strObj}.value, as appropriate */
-void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
+void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, const void *val,
                vocoldef *inobj, vocoldef *outobj)
 {
     *outobj = *inobj;
@@ -4638,7 +4637,7 @@ void vocsetobj(voccxdef *ctx, objnum obj, dattyp typ, void *val,
 
 /* set up a vocoldef */
 static void vocout(vocoldef *outobj, objnum obj, int flg,
-                   char *fst, char *lst)
+                   const char *fst, const char *lst)
 {
     outobj->vocolobj = obj;
     outobj->vocolflg = flg;
@@ -4765,7 +4764,7 @@ static void voc_get_spec_str(voccxdef *ctx, char vocw_id,
 /* set it/him/her */
 static int vocsetit(voccxdef *ctx, objnum obj, int accprop,
                     objnum actor, objnum verb, objnum prep,
-                    vocoldef *outobj, char *default_name, char vocw_id,
+                    vocoldef *outobj, const char *default_name, char vocw_id,
                     prpnum defprop, int silent)
 {
     if (obj == MCMONINV || !vocchkaccess(ctx, obj, (prpnum)accprop,
@@ -4858,7 +4857,7 @@ static int voc_disambig_hook(voccxdef *ctx, objnum verb, objnum actor,
                              objnum prep, objnum otherobj,
                              prpnum accprop, prpnum verprop,
                              objnum *objlist, uint *flags, int *objcount,
-                             char *firstwrd, char *lastwrd,
+                             const char *firstwrd, const char *lastwrd,
                              int num_wanted, int is_ambig, char *resp,
                              int silent)
 {
@@ -5477,7 +5476,7 @@ int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
             int       lpos = inpos;
             int       i = 0;
             int       cnt;
-            char     *p;
+            const char *p;
             int       cnt2, cnt3;
             int       trying_again;
             int       user_count = 0;
@@ -6985,7 +6984,7 @@ int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
                              *   use "such" 
                              */
                             if (i != cnt2) {
-                                char *last;
+                                const char *last;
 
                                 /* clear the word buffer */
                                 newobj[0] = '\0';


Commit: 0d0dbf601e70b569936469c88b9f2a8f0197e6e6
    https://github.com/scummvm/scummvm/commit/0d0dbf601e70b569936469c88b9f2a8f0197e6e6
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00

Commit Message:
GLK: TADS2: Fix uninitialized variable warnings

Changed paths:
    engines/glk/tads/tads2/output.cpp
    engines/glk/tads/tads2/run.cpp
    engines/glk/tads/tads2/run.h


diff --git a/engines/glk/tads/tads2/output.cpp b/engines/glk/tads/tads2/output.cpp
index 79284b7..5f7e46b 100644
--- a/engines/glk/tads/tads2/output.cpp
+++ b/engines/glk/tads/tads2/output.cpp
@@ -2448,7 +2448,7 @@ static int outformatlen_stream(out_stream_info *stream,
                          || !scumm_stricmp(tagbuf, "em")
                          || !scumm_stricmp(tagbuf, "strong"))
                 {
-                    int attr;
+                    int attr = 0;
                     
                     /* choose the attribute flag */
                     switch (tagbuf[0])
diff --git a/engines/glk/tads/tads2/run.cpp b/engines/glk/tads/tads2/run.cpp
index 64b52a4..0def6a7 100644
--- a/engines/glk/tads/tads2/run.cpp
+++ b/engines/glk/tads/tads2/run.cpp
@@ -1096,15 +1096,19 @@ void runexe(runcxdef *ctx, uchar *p0, objnum self, objnum target,
 	runsdef   val;                           /* stack element (for pushing) */
 	runsdef   val2;     /* another one (for popping in two-op instructions) */
 	uint      ofs;                   /* offset in code of current execution */
-	prpnum    prop;                         /* property number, when needed */
-	objnum    obj;                            /* object number, when needed */
+	prpnum    prop = 0;                     /* property number, when needed */
+	objnum    obj = 0;                        /* object number, when needed */
 	runsdef  *noreg rstsp;        /* sp to reset to on DISCARD instructions */
-	uchar    *lstp;                                         /* list pointer */
+	uchar    *lstp = nullptr;                               /* list pointer */
 	int       nargc;                   /* argument count of called function */
 	runsdef  *valp;
 	runsdef  *stkval;
-	int       i;
+	int       i = 0;
 	int       brkchk;
+	runsdef  val3;
+	int      asityp;
+	int      asiext = 0;
+	int      lclnum = 0;
 
 #ifndef DBG_OFF
 	int       err;
@@ -1954,11 +1958,6 @@ resume_from_error:
 			default:
 				if ((opc & OPCASI_MASK) == OPCASI_MASK)
 				{
-					runsdef  val3;
-					int      asityp;
-					int      asiext;
-					int      lclnum;
-
 					valp = &val;
 					stkval = &val;
 
diff --git a/engines/glk/tads/tads2/run.h b/engines/glk/tads/tads2/run.h
index 0a39fca..8b7c590 100644
--- a/engines/glk/tads/tads2/run.h
+++ b/engines/glk/tads/tads2/run.h
@@ -57,7 +57,11 @@ struct runsdef {
         objnum  runsvobj;                                   /* object value */
         prpnum  runsvprp;                          /* property number value */
         uchar  *runsvstr;                              /* string/list value */
-    } runsv;
+	} runsv;
+
+	runsdef() : runstyp(0) {
+		runsv.runsvnum = 0;
+	}
 };
 
 /* external function control structure */


Commit: 139673984edd38895ed7646066f2af06f3dc00a3
    https://github.com/scummvm/scummvm/commit/139673984edd38895ed7646066f2af06f3dc00a3
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00

Commit Message:
GLK: TADS2: Revert char * statics back to string literals

Changed paths:
    engines/glk/tads/tads2/execute_command.cpp
    engines/glk/tads/tads2/vocabulary_parser.cpp


diff --git a/engines/glk/tads/tads2/execute_command.cpp b/engines/glk/tads/tads2/execute_command.cpp
index 63c5f02..ffec9d6 100644
--- a/engines/glk/tads/tads2/execute_command.cpp
+++ b/engines/glk/tads/tads2/execute_command.cpp
@@ -1687,11 +1687,8 @@ static void exesaveit(voccxdef *ctx, vocoldef *dolist)
             ctx->voccxthm[i] = dolist[i].vocolobj;
             if (dbg)
             {
-				static char *STR1 = ", ";
-				static char *STR2 = "]\\n";
-
                 runppr(rcx, dolist[i].vocolobj, PRP_SDESC, 0);
-                tioputs(ctx->voccxtio, i+1 < cnt ? STR1 : STR2);
+                tioputs(ctx->voccxtio, i+1 < cnt ? ", " : "]\\n");
             }
         }
     }
diff --git a/engines/glk/tads/tads2/vocabulary_parser.cpp b/engines/glk/tads/tads2/vocabulary_parser.cpp
index a70c6f8..fba515e 100644
--- a/engines/glk/tads/tads2/vocabulary_parser.cpp
+++ b/engines/glk/tads/tads2/vocabulary_parser.cpp
@@ -1200,7 +1200,7 @@ static int voc_check_special(voccxdef *ctx, const char *wrd, int checktyp)
         char  *endp;
         char   typ;
         int    len;
-        int    wrdlen = strlen((char *)wrd);
+        int    wrdlen = strlen(wrd);
         
         for (p = ctx->voccxspp, endp = p + ctx->voccxspl ;
              p < endp ; )
@@ -2516,9 +2516,7 @@ static int vocg1o(voccxdef *ctx, char *cmd[], int typelist[],
 
     /* show trace message if in debug mode */
 	if (ctx->voccxflg & VOCCXFDBG) {
-		static char *CHECK = ". Checking for actor\\n";
-		static char *READING = ". Reading noun phrase\\n";
-		tioputs(ctx->vocxtio, chkact ? CHECK : READING);
+		tioputs(ctx->vocxtio, chkact ? ". Checking for actor\\n" : ". Reading noun phrase\\n");
 	}
 
     /* try the user parseNounPhrase hook */
@@ -5300,11 +5298,9 @@ int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
         else if (inlist[inpos].vocolflg == VOCS_IT ||
                  (inlist[inpos].vocolflg == VOCS_THEM && ctx->voccxthc == 0))
         {
-			static char *IT = "it";
-			static char *THEM = "them";
             err = vocsetit(ctx, ctx->voccxit, accprop, cmdActor,
                            cmdVerb, cmdPrep, &outlist[outpos],
-                           inlist[inpos].vocolflg == VOCS_IT ? IT : THEM,
+                           inlist[inpos].vocolflg == VOCS_IT ? "it" : "them",
                            (char)(inlist[inpos].vocolflg == VOCS_IT
                                   ? VOCW_IT : VOCW_THEM), defprop, silent);
             if (err != 0)
@@ -6803,11 +6799,8 @@ int vocdisambig(voccxdef *ctx, vocoldef *outlist, vocoldef *inlist,
                 }
                 else if (vocspec(diswordlist[0], VOCW_ANY))
                 {
-                    static char *anynm = "any";
-
                     /* choose the first object arbitrarily */
-                    vocout(&outlist[outpos++], list1[i], VOCS_ALL,
-                           anynm, anynm);
+					vocout(&outlist[outpos++], list1[i], VOCS_ALL, "any", "any");
                     break;
                 }
                 else


Commit: 608e9af69d203d4dd4fc65d16ef808d945341ed3
    https://github.com/scummvm/scummvm/commit/608e9af69d203d4dd4fc65d16ef808d945341ed3
Author: dreammaster (dreammaster at scummvm.org)
Date: 2019-05-24T18:21:07-07:00

Commit Message:
GLK: TADS2: Fix more const loss warnings

Changed paths:
    engines/glk/tads/tads2/line_source_file.cpp
    engines/glk/tads/tads2/line_source_file.h
    engines/glk/tads/tads2/runtime_app.h
    engines/glk/tads/tads2/runtime_driver.cpp
    engines/glk/tads/tads2/tokenizer.cpp
    engines/glk/tads/tads2/tokenizer.h


diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index c2942a8..1cf90b2 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -35,7 +35,7 @@ namespace TADS2 {
 
 /* initialize a pre-allocated linfdef, skipping debugger page setup */
 void linfini2(mcmcxdef *mctx, linfdef *linf,
-              char *filename, int flen, osfildef *fp, int new_line_records)
+              const char *filename, int flen, osfildef *fp, int new_line_records)
 {
     /* set up method pointers */
     linf->linflin.lingetp = linfget;
@@ -81,7 +81,7 @@ void linfini2(mcmcxdef *mctx, linfdef *linf,
  *   linfdef even if we can't find the file, reserving the maximum space
  *   for its path name to be filled in later. 
  */
-linfdef *linfini(mcmcxdef *mctx, errcxdef *ec, char *filename,
+linfdef *linfini(mcmcxdef *mctx, errcxdef *ec, const char *filename,
                  int flen, tokpdef *path, int must_find_file,
                  int new_line_records)
 {
diff --git a/engines/glk/tads/tads2/line_source_file.h b/engines/glk/tads/tads2/line_source_file.h
index a8b90d8..91b3127 100644
--- a/engines/glk/tads/tads2/line_source_file.h
+++ b/engines/glk/tads/tads2/line_source_file.h
@@ -75,13 +75,13 @@ struct linfdef {
 };
 
 /* initialize a file line source, opening the file for the line source */
-linfdef *linfini(mcmcxdef *mctx, errcxdef *errctx, char *filename,
+linfdef *linfini(mcmcxdef *mctx, errcxdef *errctx, const char *filename,
                  int flen, tokpdef *path, int must_find_file,
                  int new_line_records);
 
 /* initialize a pre-allocated linfdef, skipping debugger page setup */
 void linfini2(mcmcxdef *mctx, linfdef *linf,
-              char *filename, int flen, osfildef *fp, int new_line_records);
+              const char *filename, int flen, osfildef *fp, int new_line_records);
 
 /* get next line from line source */
 int linfget(lindef *lin);
diff --git a/engines/glk/tads/tads2/runtime_app.h b/engines/glk/tads/tads2/runtime_app.h
index 0e4e163..e6f36d2 100644
--- a/engines/glk/tads/tads2/runtime_app.h
+++ b/engines/glk/tads/tads2/runtime_app.h
@@ -51,13 +51,13 @@ extern int G_tads_oem_copyright_prefix;
  *   application container context is optional; pass null if no context is
  *   required.  
  */
-int trdmain(int argc, char **argv, appctxdef *appctx, char *save_ext);
+int trdmain(int argc, char **argv, appctxdef *appctx, const char *save_ext);
 
 /*
  *   Main debugger subsystem entrypoint.  Works like trdmain(), but starts
  *   the game under the debugger.  
  */
-int tddmain(int argc, char **argv, appctxdef *appctx, char *save_ext);
+int tddmain(int argc, char **argv, appctxdef *appctx, const char *save_ext);
 
 /*
  *   close and delete the swap file 
diff --git a/engines/glk/tads/tads2/runtime_driver.cpp b/engines/glk/tads/tads2/runtime_driver.cpp
index 6b6a6d7..6f1674d 100644
--- a/engines/glk/tads/tads2/runtime_driver.cpp
+++ b/engines/glk/tads/tads2/runtime_driver.cpp
@@ -254,7 +254,7 @@ static void trdusage_s(errcxdef *ec)
 
 
 static void trdmain1(errcxdef *ec, int argc, char *argv[],
-                     appctxdef *appctx, char *save_ext)
+                     appctxdef *appctx, const char *save_ext)
 {
     osfildef  *swapfp = (osfildef *)0;
     runcxdef   runctx;
@@ -857,7 +857,7 @@ void trd_close_swapfile(runcxdef *runctx)
 }
 
 /* main - called by os main after setting up arguments */
-int trdmain(int argc, char *argv[], appctxdef *appctx, char *save_ext)
+int trdmain(int argc, char *argv[], appctxdef *appctx, const char *save_ext)
 {
     errcxdef  errctx;
     int       err;
diff --git a/engines/glk/tads/tads2/tokenizer.cpp b/engines/glk/tads/tads2/tokenizer.cpp
index fc0ff01..85bba24 100644
--- a/engines/glk/tads/tads2/tokenizer.cpp
+++ b/engines/glk/tads/tads2/tokenizer.cpp
@@ -39,11 +39,11 @@ static char tokmac3[]  = "),nil))";
 static char tokmac4[]  = ")";
 
 /* forward definition of static functions */
-static int tokdfhsh(char *sym, int len);
+static int tokdfhsh(const char *sym, int len);
 
 
 /* find a #define symbol */
-static tokdfdef *tok_find_define(tokcxdef *ctx, char *sym, int len)
+static tokdfdef *tok_find_define(tokcxdef *ctx, const char *sym, int len)
 {
     int       hsh;
     tokdfdef *df;
@@ -150,7 +150,7 @@ void tok_write_defines(tokcxdef *ctx, osfildef *fp, errcxdef *ec)
 }
 
 /* compute a #define symbol's hash value */
-static int tokdfhsh(char *sym, int len)
+static int tokdfhsh(const char *sym, int len)
 {
     uint hsh;
 
@@ -160,8 +160,8 @@ static int tokdfhsh(char *sym, int len)
 }
 
 /* convert a #define symbol to lower case if folding case */
-static char *tok_casefold_defsym(tokcxdef *ctx, char *outbuf,
-                                 char *src, int len)
+static const char *tok_casefold_defsym(tokcxdef *ctx, char *outbuf,
+                                 const char *src, int len)
 {
     if (ctx->tokcxflg & TOKCXCASEFOLD)
     {
@@ -205,8 +205,8 @@ void tok_case_fold(tokcxdef *ctx, tokdef *tok)
 }
 
 /* add a symbol to the #define symbol table, folding case if necessary */
-void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len,
-                            char *expan, int explen)
+void tok_add_define_cvtcase(tokcxdef *ctx, const char *sym, int len,
+                            const char *expan, int explen)
 {
     char mysym[TOKNAMMAX];
     
@@ -218,7 +218,7 @@ void tok_add_define_cvtcase(tokcxdef *ctx, char *sym, int len,
 }
 
 /* add a symbol to the #define symbol table */
-void tok_add_define(tokcxdef *ctx, char *sym, int len,
+void tok_add_define(tokcxdef *ctx, const char *sym, int len,
                     char *expan, int explen)
 {
     int       hsh;
@@ -261,7 +261,7 @@ void tok_add_define_num_cvtcase(tokcxdef *ctx, char *sym, int len, int num)
 }
 
 /* undefine a #define symbol */
-void tok_del_define(tokcxdef *ctx, char *sym, int len)
+void tok_del_define(tokcxdef *ctx, const char *sym, int len)
 {
     int       hsh;
     tokdfdef *df;
@@ -290,7 +290,7 @@ void tok_del_define(tokcxdef *ctx, char *sym, int len)
 }
 
 /* scan a #define symbol to see how long it is */
-static int tok_scan_defsym(tokcxdef *ctx, char *p, int len)
+static int tok_scan_defsym(tokcxdef *ctx, const char *p, int len)
 {
     int symlen;
 
@@ -307,11 +307,11 @@ static int tok_scan_defsym(tokcxdef *ctx, char *p, int len)
 }
 
 /* process a #define */
-static void tokdefine(tokcxdef *ctx, char *p, int len)
+static void tokdefine(tokcxdef *ctx, const char *p, int len)
 {
-    char *sym;
+    const char *sym;
     int   symlen;
-    char *expan;
+    const char *expan;
     char  mysym[TOKNAMMAX];
     
     /* get the symbol */
@@ -374,10 +374,10 @@ static void tok_update_if_stat(tokcxdef *ctx)
 }
 
 /* process an #ifdef or a #ifndef */
-static void tok_ifdef_ifndef(tokcxdef *ctx, char *p, int len, int is_ifdef)
+static void tok_ifdef_ifndef(tokcxdef *ctx, const char *p, int len, int is_ifdef)
 {
     int   symlen;
-    char *sym;
+    const char *sym;
     int   stat;
     int   found;
     char  mysym[TOKNAMMAX];
@@ -416,38 +416,38 @@ static void tok_ifdef_ifndef(tokcxdef *ctx, char *p, int len, int is_ifdef)
 }
 
 /* process a #error */
-static void tok_p_error(tokcxdef *ctx, char *p, int len)
+static void tok_p_error(tokcxdef *ctx, const char *p, int len)
 {
     errlog1(ctx->tokcxerr, ERR_P_ERROR,
             ERRTSTR, errstr(ctx->tokcxerr, p, len));
 }
 
 /* process a #ifdef */
-static void tokifdef(tokcxdef *ctx, char *p, int len)
+static void tokifdef(tokcxdef *ctx, const char *p, int len)
 {
     tok_ifdef_ifndef(ctx, p, len, TRUE);
 }
 
 /* process a #ifndef */
-static void tokifndef(tokcxdef *ctx, char *p, int len)
+static void tokifndef(tokcxdef *ctx, const char *p, int len)
 {
     tok_ifdef_ifndef(ctx, p, len, FALSE);
 }
 
 /* process a #if */
-static void tokif(tokcxdef *ctx, char *p, int len)
+static void tokif(tokcxdef *ctx, const char *p, int len)
 {
     errsig(ctx->tokcxerr, ERR_PIF_NA);
 }
 
 /* process a #elif */
-static void tokelif(tokcxdef *ctx, char *p, int len)
+static void tokelif(tokcxdef *ctx, const char *p, int len)
 {
     errsig(ctx->tokcxerr, ERR_PELIF_NA);
 }
 
 /* process a #else */
-static void tokelse(tokcxdef *ctx, char *p, int len)
+static void tokelse(tokcxdef *ctx, const char *p, int len)
 {
     int cnt;
     
@@ -471,7 +471,7 @@ static void tokelse(tokcxdef *ctx, char *p, int len)
 }
 
 /* process a #endif */
-static void tokendif(tokcxdef *ctx, char *p, int len)
+static void tokendif(tokcxdef *ctx, const char *p, int len)
 {
     /* if we're not expecting #endif, it's an error */
     if (ctx->tokcxifcnt == 0)
@@ -488,9 +488,9 @@ static void tokendif(tokcxdef *ctx, char *p, int len)
 }
 
 /* process a #undef */
-static void tokundef(tokcxdef *ctx, char *p, int len)
+static void tokundef(tokcxdef *ctx, const char *p, int len)
 {
-    char *sym;
+    const char *sym;
     int   symlen;
     char  mysym[TOKNAMMAX];
     
@@ -514,7 +514,7 @@ static void tokundef(tokcxdef *ctx, char *p, int len)
 }
 
 /* process a #pragma directive */
-static void tokpragma(tokcxdef *ctx, char *p, int len)
+static void tokpragma(tokcxdef *ctx, const char *p, int len)
 {
     /* ignore empty pragmas */
     if (len == 0)
@@ -549,15 +549,15 @@ static void tokpragma(tokcxdef *ctx, char *p, int len)
 }
 
 /* process a #include directive */
-static void tokinclude(tokcxdef *ctx, char *p, int len)
+static void tokinclude(tokcxdef *ctx, const char *p, int len)
 {
     linfdef *child;
     tokpdef *path;
-    char    *fname;
+    const char *fname;
     int      match;
     int      flen;
     linfdef *lin;
-    char    *q;
+    const char *q;
     size_t   flen2;
 
     /* find the filename portion */
@@ -749,14 +749,14 @@ static int tokgetlin(tokcxdef *ctx, int dopound)
         if (dopound && ctx->tokcxlen != 0 && ctx->tokcxptr[0] == '#'
             && !(ctx->tokcxlin->linflg & LINFNOINC))
         {
-            char   *p;
+            const char *p;
             int     len;
-            static  struct
+            static const struct
             {
-                char  *nm;
+                const char *nm;
                 int    len;
                 int    ok_in_if;
-                void (*fn)(tokcxdef *, char *, int);
+                void (*fn)(tokcxdef *, const char *, int);
             }
             *dirp, dir[] =
             {
@@ -850,7 +850,7 @@ static int tokgetlin(tokcxdef *ctx, int dopound)
 /* get the next token, removing it from the input stream */
 int toknext(tokcxdef *ctx)
 {
-    char   *p;
+    const char *p;
     tokdef *tok = &ctx->tokcxcur;
     int     len;
 
@@ -949,20 +949,21 @@ nexttoken:
     {
         int       l;
         int       hash;
-        char     *q;
+        const char *q;
+	char     *tq;
         toktdef  *tab;
         int       found = FALSE;
         uchar     thischar;
         tokdfdef *df;
         
-        for (hash = 0, l = 0, q = tok->toknam ;
+        for (hash = 0, l = 0, tq = tok->toknam ;
              len != 0 && TOKISSYM(*p) && l < TOKNAMMAX ;
              (thischar = ((Common::isUpper((uchar)*p)
                            && (ctx->tokcxflg & TOKCXCASEFOLD))
                           ? Common::isLower((uchar)*p) : *p)),
              (hash = ((hash + thischar) & (TOKHASHSIZE - 1))),
-             (*q++ = thischar), ++p, --len, ++l) ;
-        *q = '\0';
+             (*tq++ = thischar), ++p, --len, ++l) ;
+        *tq = '\0';
         if (len != 0 && TOKISSYM(*p))
         {
             while (len != 0 && TOKISSYM(*p)) ++p, --len;
@@ -1081,7 +1082,7 @@ nexttoken:
     else if (*p == '"' || *p == '\'')
     {
         char  delim;                 /* closing delimiter we're looking for */
-        char *strstart;                       /* pointer to start of string */
+        const char *strstart;                 /* pointer to start of string */
         int   warned;
         
         delim = *p;
diff --git a/engines/glk/tads/tads2/tokenizer.h b/engines/glk/tads/tads2/tokenizer.h
index 11d76c6..882ab92 100644
--- a/engines/glk/tads/tads2/tokenizer.h
+++ b/engines/glk/tads/tads2/tokenizer.h
@@ -334,10 +334,10 @@ struct tokcxdef {
     void     *tokcxscx;    /* context for string storage callback functions */
     ushort  (*tokcxsst)(void *ctx);
                /* start storing a string; return offset of string's storage */
-    void    (*tokcxsad)(void *ctx, char *str, ushort len);
+    void    (*tokcxsad)(void *ctx, const char *str, ushort len);
                                               /* add characters to a string */
     void    (*tokcxsend)(void *ctx);               /* finish storing string */
-    char     *tokcxmsav[TOKMACNEST]; /* saved positions for macro expansion */
+    const char *tokcxmsav[TOKMACNEST]; /* saved positions for macro expansion */
     ushort    tokcxmsvl[TOKMACNEST];   /* saved lengths for macro expansion */
     int       tokcxmlvl;                             /* macro nesting level */
     int       tokcxflg;                                            /* flags */
@@ -350,7 +350,7 @@ struct tokcxdef {
     tokdef    tokcxcur;                                    /* current token */
     char     *tokcxbuf;                            /* buffer for long lines */
     ushort    tokcxbsz;                         /* size of long line buffer */
-    char     *tokcxptr;                         /* pointer into line source */
+    const char *tokcxptr;                       /* pointer into line source */
     ushort    tokcxlen;                         /* length of text in buffer */
     uchar     tokcxinx[256];                   /* special character indices */
     tokdfdef *tokcxdf[TOKDFHSHSIZ];       /* hash table for #define symbols */
@@ -429,8 +429,8 @@ int toknext(tokcxdef *ctx);
 int tokget1(tokcxdef *ctx, tokdef *tok, int consume);
 
 /* add a symbol to the #define symbol table */
-void tok_add_define(tokcxdef *ctx, char *sym, int len,
-                    char *expan, int explen);
+void tok_add_define(tokcxdef *ctx, const char *sym, int len,
+                    const char *expan, int explen);
 
 /* 
  *   add a symbol to the #define symbol table, folding case if we're





More information about the Scummvm-git-logs mailing list