[Scummvm-git-logs] scummvm master -> 643ee33a3deab293c8377ce278866c0e6b241fab

dreammaster paulfgilbert at gmail.com
Mon Sep 30 00:09:29 CEST 2019


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

Summary:
255f2b4c82 GLK: QUEST: Initial subengine commit
30bf05479a GLK: QUEST: Added detection entries
32c9deefb7 GLK: QUEST: Fix infinite recursion
055f6f0b48 GLK: QUEST: Startup/logging fix
97e61ddb0e GLK: QUEST: Fix crash printing formatted strings
b35088b788 GLK: QUEST: Fix exitting by closing game window
defc19ada4 GLK: QUEST: Savegames aren't supported for Quest games
643ee33a3d GLK: QUEST: gcc compilation fixes


Commit: 255f2b4c82867b3c113743d02cf91b91a15176bd
    https://github.com/scummvm/scummvm/commit/255f2b4c82867b3c113743d02cf91b91a15176bd
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-09-29T15:08:52-07:00

Commit Message:
GLK: QUEST: Initial subengine commit

Changed paths:
  A engines/glk/quest/detection.cpp
  A engines/glk/quest/detection.h
  A engines/glk/quest/detection_tables.h
  A engines/glk/quest/geas_file.cpp
  A engines/glk/quest/geas_file.h
  A engines/glk/quest/geas_glk.cpp
  A engines/glk/quest/geas_glk.h
  A engines/glk/quest/geas_impl.h
  A engines/glk/quest/geas_runner.cpp
  A engines/glk/quest/geas_runner.h
  A engines/glk/quest/geas_state.cpp
  A engines/glk/quest/geas_state.h
  A engines/glk/quest/geas_util.cpp
  A engines/glk/quest/geas_util.h
  A engines/glk/quest/limit_stack.h
  A engines/glk/quest/quest.cpp
  A engines/glk/quest/quest.h
  A engines/glk/quest/read_file.cpp
  A engines/glk/quest/read_file.h
  A engines/glk/quest/reserved_words.h
  A engines/glk/quest/streams.cpp
  A engines/glk/quest/streams.h
  A engines/glk/quest/string.cpp
  A engines/glk/quest/string.h
  A engines/glk/quest/uncas.pl
    engines/glk/detection.cpp
    engines/glk/glk_types.h
    engines/glk/module.mk
    engines/glk/quetzal.cpp


diff --git a/engines/glk/detection.cpp b/engines/glk/detection.cpp
index 60b2faa..f653172 100644
--- a/engines/glk/detection.cpp
+++ b/engines/glk/detection.cpp
@@ -39,6 +39,8 @@
 #include "glk/hugo/hugo.h"
 #include "glk/magnetic/detection.h"
 #include "glk/magnetic/magnetic.h"
+#include "glk/quest/detection.h"
+#include "glk/quest/quest.h"
 #include "glk/scott/detection.h"
 #include "glk/scott/scott.h"
 #include "glk/tads/detection.h"
@@ -163,6 +165,7 @@ Common::Error GlkMetaEngine::createInstance(OSystem *syst, Engine **engine) cons
 	else 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::Quest::QuestMetaEngine, Glk::Quest::Quest>(syst, gameDesc)) != nullptr) {}
 	else if ((*engine = create<Glk::Scott::ScottMetaEngine, Glk::Scott::Scott>(syst, gameDesc)) != nullptr) {}
 #ifndef RELEASE_BUILD
 	else if ((*engine = create<Glk::Magnetic::MagneticMetaEngine, Glk::Magnetic::Magnetic>(syst, gameDesc)) != nullptr) {}
@@ -211,6 +214,7 @@ PlainGameList GlkMetaEngine::getSupportedGames() const {
 	Glk::Frotz::FrotzMetaEngine::getSupportedGames(list);
 	Glk::Glulxe::GlulxeMetaEngine::getSupportedGames(list);
 	Glk::Hugo::HugoMetaEngine::getSupportedGames(list);
+	Glk::Quest::QuestMetaEngine::getSupportedGames(list);
 	Glk::Scott::ScottMetaEngine::getSupportedGames(list);
 #ifndef RELEASE_BUILD
 	Glk::Magnetic::MagneticMetaEngine::getSupportedGames(list);
@@ -242,6 +246,9 @@ PlainGameDescriptor GlkMetaEngine::findGame(const char *gameId) const {
 	gd = Glk::Hugo::HugoMetaEngine::findGame(gameId);
 	if (gd._description) return gd;
 
+	gd = Glk::Quest::QuestMetaEngine::findGame(gameId);
+	if (gd._description) return gd;
+
 	gd = Glk::Scott::ScottMetaEngine::findGame(gameId);
 	if (gd._description) return gd;
 
@@ -267,6 +274,7 @@ DetectedGames GlkMetaEngine::detectGames(const Common::FSList &fslist) const {
 	Glk::Frotz::FrotzMetaEngine::detectGames(fslist, detectedGames);
 	Glk::Glulxe::GlulxeMetaEngine::detectGames(fslist, detectedGames);
 	Glk::Hugo::HugoMetaEngine::detectGames(fslist, detectedGames);
+	Glk::Quest::QuestMetaEngine::detectGames(fslist, detectedGames);
 	Glk::Scott::ScottMetaEngine::detectGames(fslist, detectedGames);
 
 #ifndef RELEASE_BUILD
@@ -286,6 +294,7 @@ void GlkMetaEngine::detectClashes() const {
 	Glk::Frotz::FrotzMetaEngine::detectClashes(map);
 	Glk::Glulxe::GlulxeMetaEngine::detectClashes(map);
 	Glk::Hugo::HugoMetaEngine::detectClashes(map);
+	Glk::Quest::QuestMetaEngine::detectClashes(map);
 	Glk::Scott::ScottMetaEngine::detectClashes(map);
 
 #ifndef RELEASE_BUILD
diff --git a/engines/glk/glk_types.h b/engines/glk/glk_types.h
index ca20fe8..f99d2b1 100644
--- a/engines/glk/glk_types.h
+++ b/engines/glk/glk_types.h
@@ -48,6 +48,7 @@ enum InterpreterType {
 	INTERPRETER_JACL,
 	INTERPRETER_LEVEL9,
 	INTERPRETER_MAGNETIC,
+	INTERPRETER_QUEST,
 	INTERPRETER_SCARE,
 	INTERPRETER_SCOTT,
 	INTERPRETER_TADS2,
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index dd63c77..4f4608a 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -182,6 +182,17 @@ MODULE_OBJS := \
 	magnetic/graphics.o \
 	magnetic/magnetic.o \
 	magnetic/sound.o \
+	quest/detection.o \
+	quest/geas_file.o \
+	quest/geas_glk.o \
+	quest/geas_runner.o \
+	quest/geas_state.o \
+	quest/geas_util.o \
+	quest/quest.o \
+	quest/tstring.o \
+	quest/read_file.o \
+	quest/string.o \
+	quest/streams.o \
 	scott/detection.o \
 	scott/scott.o \
 	tads/detection.o \
diff --git a/engines/glk/quest/detection.cpp b/engines/glk/quest/detection.cpp
new file mode 100644
index 0000000..a0aa6b8
--- /dev/null
+++ b/engines/glk/quest/detection.cpp
@@ -0,0 +1,93 @@
+/* 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/quest/detection.h"
+#include "glk/quest/detection_tables.h"
+#include "common/debug.h"
+#include "common/file.h"
+#include "common/md5.h"
+#include "engines/game.h"
+
+namespace Glk {
+namespace Quest {
+
+void QuestMetaEngine::getSupportedGames(PlainGameList &games) {
+	for (const PlainGameDescriptor *pd = QUEST_GAME_LIST; pd->gameId; ++pd)
+		games.push_back(*pd);
+}
+
+GameDescriptor QuestMetaEngine::findGame(const char *gameId) {
+	for (const PlainGameDescriptor *pd = QUEST_GAME_LIST; pd->gameId; ++pd) {
+		if (!strcmp(gameId, pd->gameId))
+			return *pd;
+	}
+
+	return GameDescriptor::empty();
+}
+
+bool QuestMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
+	// Loop through the files of the folder
+	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
+		// Check for a recognised filename
+		if (file->isDirectory())
+			continue;
+
+		Common::String filename = file->getName();
+		if (!filename.hasSuffixIgnoreCase(".quest"))
+			continue;
+
+		Common::File gameFile;
+		if (!gameFile.open(*file))
+			continue;
+
+		gameFile.seek(0);
+		Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
+		uint32 filesize = gameFile.size();
+
+		// Scan through the Quest game list for a match
+		const GlkDetectionEntry *p = QUEST_GAMES;
+		while (p->_md5 && p->_filesize != filesize && md5 != p->_md5)
+			++p;
+
+		if (!p->_gameId) {
+			const PlainGameDescriptor &desc = QUEST_GAME_LIST[0];
+			gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
+		} else {
+			// Found a match
+			PlainGameDescriptor gameDesc = findGame(p->_gameId);
+			gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, filename));
+		}
+	}
+
+	return !gameList.empty();
+}
+
+void QuestMetaEngine::detectClashes(Common::StringMap &map) {
+	for (const PlainGameDescriptor *pd = QUEST_GAME_LIST; pd->gameId; ++pd) {
+		if (map.contains(pd->gameId))
+			error("Duplicate game Id found - %s", pd->gameId);
+		map[pd->gameId] = "";
+	}
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/detection.h b/engines/glk/quest/detection.h
new file mode 100644
index 0000000..d94e40d
--- /dev/null
+++ b/engines/glk/quest/detection.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.
+ *
+ */
+
+#ifndef GLK_QUEST_DETECTION
+#define GLK_QUEST_DETECTION
+
+#include "common/fs.h"
+#include "common/hash-str.h"
+#include "engines/game.h"
+#include "glk/detection.h"
+
+namespace Glk {
+namespace Quest {
+
+class QuestMetaEngine {
+public:
+	/**
+	 * Get a list of supported games
+	 */
+	static void getSupportedGames(PlainGameList &games);
+
+	/**
+	 * Returns a game description for the given game Id, if it's supported
+	 */
+	static GameDescriptor findGame(const char *gameId);
+
+	/**
+	 * Detect supported games
+	 */
+	static bool detectGames(const Common::FSList &fslist, DetectedGames &gameList);
+
+	/**
+	 * Check for game Id clashes with other sub-engines
+	 */
+	static void detectClashes(Common::StringMap &map);
+};
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/detection_tables.h b/engines/glk/quest/detection_tables.h
new file mode 100644
index 0000000..8e1fe11
--- /dev/null
+++ b/engines/glk/quest/detection_tables.h
@@ -0,0 +1,81 @@
+/* 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 "engines/game.h"
+#include "common/gui_options.h"
+#include "common/language.h"
+
+namespace Glk {
+namespace Quest {
+
+const PlainGameDescriptor QUEST_GAME_LIST[] = {
+	{ "quest", "Quest Game" },
+
+	{ "attackonfrightside", "Attack On Frightside" },
+	{ "balaclava", "Balaclava" },
+	{ "bearsepicquest", "Bear's Epic Quest" },
+	{ "caught", "Caught!" },
+	{ "cuttings", "Cuttings" },
+	{ "draculacrl", "Dracula: CRL remake" },
+	{ "elections4", "It's election time in Pakistan: Go rich boy, go!" },
+	{ "everyman", "Everyman" },
+	{ "firstTimes", "First Times" },
+	{ "giftofthemagi", "Gift of the Magi" },
+	{ "medievalistsquest", "Medievalist's Quest" },
+	{ "parishotel", "Welcome to the Paris Hotel" },
+	{ "questforloot", "Quest for loot and something else" },
+	{ "sleepingassassin", "El asesino durmiente (The Sleeping Assassin)" },
+	{ "spondre", "Spondre" },
+	{ "thelasthero", "The Last Hero" },
+	{ "tokindlealight", "To Kindle a Light" },
+	{ "xanadu", "Xanadu - The World's Only Hope" },
+
+	{ nullptr, nullptr }
+};
+
+const GlkDetectionEntry QUEST_GAMES[] = {
+  DT_ENTRY0("attackonfrightside", "84542fc6460833bbf2594ed83f8b1fc7", 46019),
+  DT_ENTRY0("balaclava", "8b30af05d9986f9f962c677181ecc766", 57719),
+  DT_ENTRY0("bearsepicquest", "e6896a65527f456b4362aaebcf39e354", 62075),
+  DT_ENTRY0("caught", "4502d89d8e304fe4165d46eb22f21f10", 5168593),
+  DT_ENTRY0("cuttings", "e0ded5a6b78e8c9482e746d55f61972c", 6583866),
+  DT_ENTRY0("draculacrl", "1af3ec877584b290f7ab1a1be8f944a5", 4548737),
+  DT_ENTRY0("elections4", "d0bc0cd54182d6099808767068592b94", 591994),
+  DT_ENTRY0("everyman", "410c7211d3f0c700f34e97ed258e33f1", 56218),
+  DT_ENTRY0("firstTimes", "31d878c82d99856d473762612f154eb6", 10253826),
+  DT_ENTRY0("giftofthemagi", "b33132ce71c8a2eed0f6c1c1af284765", 78647),
+  DT_ENTRY0("medievalistsquest", "e0a15bc2a74a0bd6bb5c24661ea35829", 127977271),
+  DT_ENTRY0("parishotel", "c9a42bc3f306aba5e318b0a74115e0d4", 474983),
+  DT_ENTRY0("questforloot", "f7e32aec0f961a59a69bead3fadff4f0", 1357373),
+  DT_ENTRY0("sleepingassassin", "9c2aa213bb73d8083506ee6f64436d9d", 287227),
+  DT_ENTRY0("spondre", "c639077eb487eb6d1b63cda2c9ba5a9b", 1169469),
+  DT_ENTRY0("thelasthero", "31e10b8a7f11a6289955b89437f8178c", 62512),
+  DT_ENTRY0("tokindlealight", "5d3b57830b003046a621620ba0869d7c", 811845),
+
+
+	DT_ENTRY0("xanadu", "fef25e3473755ec572d4236d56f918e2", 396973),
+
+	DT_END_MARKER
+};
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/geas_file.cpp b/engines/glk/quest/geas_file.cpp
new file mode 100644
index 0000000..05c78e3
--- /dev/null
+++ b/engines/glk/quest/geas_file.cpp
@@ -0,0 +1,659 @@
+/* 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/quest/geas_file.h"
+#include "glk/quest/reserved_words.h"
+#include "glk/quest/read_file.h"
+#include "glk/quest/geas_util.h"
+#include "glk/quest/geas_impl.h"
+#include "glk/quest/streams.h"
+#include "glk/quest/string.h"
+
+namespace Glk {
+namespace Quest {
+
+void report_error(const String &s);
+
+reserved_words obj_tag_property("look", "examine", "speak", "take", "alias", "prefix", "suffix", "detail", "displaytype", "gender", "article", "hidden", "invisible", (char *) NULL);
+
+reserved_words room_tag_property("look", "alias", "prefix", "indescription", "description", "north", "south", "east", "west", "northwest", "northeast", "southeast", "southwest", "up", "down", "out", (char *) NULL);
+
+void GeasFile::debug_print(String s) const {
+	if (gi == NULL)
+		cerr << s << endl;
+	else
+		gi->debug_print(s);
+}
+
+const GeasBlock *GeasFile::find_by_name(String type, String name) const {
+	//name = lcase (name);
+	for (uint i = 0; i < size(type); i ++) {
+		//cerr << "find_by_name (" << type << ", " << name << "), vs. '"
+		//     << block(type, i).name << "'\n";
+		//if (block(type, i).lname == name)
+		if (ci_equal(block(type, i).name, name))
+			return &block(type, i);
+	}
+	return NULL;
+}
+
+const GeasBlock &GeasFile::block(String type, uint index) const {
+	StringArrayIntMap::const_iterator iter;
+	iter = type_indecies.find(type);
+	if (!(iter != type_indecies.end() && index < (*iter)._value.size()))
+		cerr << "Unable to find type " << type << "\n";
+
+	assert(iter != type_indecies.end() && index < (*iter)._value.size());
+	//assert (index >= 0 && index < size(type));
+	return blocks[(*iter)._value[index]];
+}
+
+uint GeasFile::size(String type) const {
+	//cerr << "GeasFile::size (" << type << ")" << endl;
+
+	// SENSITIVE?
+	//std::map<String, Common::Array<int>, CI_LESS>::const_iterator iter;
+	StringArrayIntMap::const_iterator iter;
+	//cerr << type_indecies << endl;
+	iter = type_indecies.find(type);
+	if (iter == type_indecies.end()) {
+		//cerr << "  returning 0" << endl;
+		return 0;
+	}
+	//cerr << "  returning " << (*iter)._value.size() << endl;
+	return (*iter)._value.size();
+}
+
+
+bool GeasFile::obj_has_property(String objname, String propname) const {
+	String tmp;
+	return get_obj_property(objname, propname, tmp);
+}
+
+Common::WriteStream &operator<< (Common::WriteStream &, const Set<String> &);
+
+/**
+ * Currently only works for actual objects, not rooms or the game
+ */
+//Set<String, CI_LESS> GeasFile::get_obj_keys (String obj) const
+Set<String> GeasFile::get_obj_keys(String obj) const {
+	//Set<String, CI_LESS> rv;
+	Set<String> rv;
+	get_obj_keys(obj, rv);
+	return rv;
+}
+
+void GeasFile::get_obj_keys(String obj, Set<String> &rv) const {
+	cerr << "get_obj_keys (gf, <" << obj << ">)\n";
+	//Set<String> rv;
+
+	uint c1, c2;
+	String tok, line;
+	reserved_words *rw = NULL;
+
+	const GeasBlock *gb = find_by_name("object", obj);
+	rw = &obj_tag_property;
+
+	if (gb == NULL) {
+		cerr << "No such object found, aborting\n";
+		//return rv;
+		return;
+	}
+
+	for (uint i = 0; i < gb->data.size(); i ++) {
+		line = gb->data[i];
+		cerr << "  handling line <" << line << ">\n";
+		tok = first_token(line, c1, c2);
+		// SENSITIVE?
+		if (tok == "properties") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok)) {
+				vstring params = split_param(param_contents(tok));
+				for (uint j = 0; j < params.size(); j ++) {
+					cerr << "   handling parameter <" << params[j] << ">\n";
+					uint k = params[j].find('=');
+					// SENSITIVE?
+					if (starts_with(params[j], "not ")) {
+						rv.insert(trim(params[j].substr(4)));
+						cerr << "     adding <" << trim(params[j].substr(4))
+						     << ">\n";
+					} else if (k == -1) {
+						rv.insert(params[j]);
+						cerr << "     adding <" << params[j] << ">\n";
+					} else {
+						rv.insert(trim(params[j].substr(0, k)));
+						cerr << "     adding <" << trim(params[j].substr(0, k))
+						     << ">\n";
+					}
+				}
+			}
+		}
+		// SENSITIVE?
+		else if (tok == "type") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok))
+				get_type_keys(param_contents(tok), rv);
+		}
+		//else if (has (tag_property, tok) && tag_property[tok])
+		else if (rw != NULL && rw->has(tok)) {
+			String tok1 = next_token(line, c1, c2);
+			if (is_param(tok1))
+				rv.insert(tok);
+		}
+	}
+
+	cerr << "Returning (" << rv << ")\n";
+}
+
+void GeasFile::get_type_keys(String typen, Set<String> &rv) const {
+	cerr << "get_type_keys (" << typen << ", " << rv << ")\n";
+	const GeasBlock *gb = find_by_name("type", typen);
+	if (gb == NULL) {
+		cerr << "  g_t_k: Nonexistent type\n";
+		return;
+	}
+	String line, tok;
+	uint c1, c2;
+	for (uint i = 0; i < gb->data.size(); i ++) {
+		line = gb->data[i];
+		//cerr << "    g_t_k: Handling line '" << line << "'\n";
+		tok = first_token(line, c1, c2);
+		// SENSISTIVE?
+		if (tok == "type") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok)) {
+				get_type_keys(param_contents(tok), rv);
+				cerr << "      g_t_k: Adding <" << tok << "> to rv: " << rv << "\n";
+			}
+		}
+		// SENSITIVE?
+		else if (tok == "action") {
+			cerr << "       action, skipping\n";
+		} else {
+			uint ch = line.find('=');
+			if (ch != -1) {
+				rv.insert(trim(line.substr(0, ch)));
+				cerr << "      adding <" << trim(line.substr(0, ch)) << ">\n";
+			}
+		}
+	}
+	cerr << "Returning (" << rv << ")\n";
+}
+
+bool GeasFile::get_obj_property(String objname, String propname, String &string_rv) const {
+	cerr << "g_o_p: Getting prop <" << propname << "> of obj <" << objname << ">\n";
+	string_rv = "!";
+	bool bool_rv = false;
+
+	//cerr << "obj_types == " << obj_types << endl;
+	/*
+	cerr << "obj_types == \n";
+	for (map<String, String>::const_iterator iter = obj_types.begin();
+	     iter != obj_types.end(); iter ++)
+	  cerr << "  " << (*iter)._key << " -> " << (*iter)._value << "\n";
+	cerr << ".\n";
+	*/
+
+	/*
+	String objtype;
+
+	if (objname == "game")
+	  objtype = "game";
+	else if (!has (obj_types, objname))
+	  {
+	    debug_print ("Checking property of nonexistent object " + objname);
+	    return false;
+	  }
+	else
+	  objtype = (*obj_types.find(objname))._value;
+	*/
+
+	if (!has(obj_types, objname)) {
+		debug_print("Checking nonexistent object <" + objname + "> for property <" + propname + ">");
+		return false;
+	}
+	String objtype = (*obj_types.find(objname))._value;
+
+	const GeasBlock *block = find_by_name(objtype, objname);
+
+	String not_prop = "not " + propname;
+	uint c1, c2;
+	assert(block != NULL);
+	//assert (block->data != NULL);
+	for (uint i = 0; i < block->data.size(); i ++) {
+		String line = block->data[i];
+		//cerr << "  g_o_p: Handling line <" << line << ">\n";
+		String tok = first_token(line, c1, c2);
+		// SENSITIVE?
+		if (tok == "type") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok))
+				get_type_property(param_contents(tok), propname, bool_rv, string_rv);
+			else {
+				debug_print("Expected parameter for type in " + line);
+			}
+		}
+		// SENSITIVE?
+		else if (tok == "properties") {
+			tok = next_token(line, c1, c2);
+			if (!is_param(tok)) {
+				debug_print("Expected param on line " + line);
+				continue;
+			}
+			Common::Array<String> props = split_param(param_contents(tok));
+			for (uint j = 0; j < props.size(); j ++) {
+				//cerr << "    g_o_p: Comparing against <" << props[j] << ">\n";
+				uint index;
+				if (props[j] == propname) {
+					//cerr << "      g_o_p: Present but empty, blanking\n";
+					string_rv = "";
+					bool_rv = true;
+				} else if (props[j] == not_prop) {
+					//cerr << "      g_o_p: Negation, removing\n";
+					string_rv = "!";
+					bool_rv = false;
+				} else if ((index = props[j].find('=')) != -1 &&
+				           (trim(props[j].substr(0, index)) == propname)) {
+					string_rv = props[j].substr(index + 1);
+					bool_rv = true;
+					//cerr << "      g_o_p: Normal prop, now to <" << string_rv << ">\n";
+				}
+			}
+		}
+	}
+	cerr << "g_o_p: Ultimately returning " << (bool_rv ? "true" : "false")
+	     << ", with String <" << string_rv << ">\n\n";
+	return bool_rv;
+}
+
+void GeasFile::get_type_property(String typenamex, String propname, bool &bool_rv, String &string_rv) const {
+	//cerr << "  Checking type <" << typenamex << "> for prop <" << propname << ">\n";
+	const GeasBlock *block = find_by_name("type", typenamex);
+	if (block == NULL) {
+		debug_print("Object of nonexistent type " + typenamex);
+		return;
+	}
+	for (uint i = 0; i < block->data.size(); i ++) {
+		String line = block->data[i];
+		//cerr << "    Comparing vs. line <" << line << ">\n";
+		uint c1, c2;
+		String tok = first_token(line, c1, c2);
+
+		// SENSITIVE?
+		if (tok == "type") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok))
+				get_type_property(param_contents(tok), propname, bool_rv, string_rv);
+		} else if (line == propname) {
+			bool_rv = true;
+			string_rv = "";
+		} else {
+			c1 = line.find('=');
+			if (c1 != -1) {
+				tok = trim(line.substr(0, c1));
+				if (tok == propname) {
+					string_rv = trim(line.substr(c1 + 1));
+					bool_rv = true;
+				}
+			}
+		}
+		/*
+		if (tok == propname)
+		{
+		  cerr << "      match...";
+		  tok = next_token (line, c1, c2);
+		  if (tok == "")
+		    {
+		      bool_rv = true;
+		      string_rv = "";
+		      //cerr << " present but empty\n";
+		    }
+		  else if (tok == "=")
+		    {
+		      bool_rv = true;
+		      string_rv = trim (line.substr (c2));
+		      //cerr << " now <" << string_rv << ">\n";
+		    }
+		  else
+		    {
+		      cerr << "Bad line while checking " << typenamex << " for prop "
+		      << propname << ": " << line << endl;
+		    }
+		}
+		     else if (tok == "type")
+		{
+		  tok = next_token (line, c1, c2);
+		  if (is_param (tok))
+		    get_type_property (param_contents(tok), propname, bool_rv, string_rv);
+		}
+		*/
+	}
+}
+
+
+
+bool GeasFile::obj_of_type(String objname, String typenamex) const {
+	if (!has(obj_types, objname)) {
+		debug_print("Checking nonexistent obj <" + objname + "> for type <" +
+		            typenamex + ">");
+		return false;
+	}
+	String objtype = (*obj_types.find(objname))._value;
+
+	const GeasBlock *block = find_by_name(objtype, objname);
+
+	uint c1, c2;
+	assert(block != NULL);
+	for (uint i = 0; i < block->data.size(); i ++) {
+		String line = block->data[i];
+		String tok = first_token(line, c1, c2);
+		// SENSITIVE?
+		if (tok == "type") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok)) {
+				if (type_of_type(param_contents(tok), typenamex))
+					return true;
+			} else {
+				debug_print("Eg_o_p: xpected parameter for type in " + line);
+			}
+		}
+	}
+	return false;
+}
+
+
+bool GeasFile::type_of_type(String subtype, String supertype) const {
+	if (ci_equal(subtype, supertype))
+		return true;
+	//cerr << "  Checking type <" << subtype << "> for type <" << supertype << ">\n";
+	const GeasBlock *block = find_by_name("type", subtype);
+	if (block == NULL) {
+		debug_print("t_o_t: Nonexistent type " + subtype);
+		return false;
+	}
+	for (uint i = 0; i < block->data.size(); i ++) {
+		String line = block->data[i];
+		//cerr << "    Comparing vs. line <" << line << ">\n";
+		uint c1, c2;
+		String tok = first_token(line, c1, c2);
+		// SENSITIVE?
+		if (tok == "type") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok) && type_of_type(param_contents(tok), supertype))
+				return true;
+		}
+	}
+	return false;
+}
+
+
+
+bool GeasFile::get_obj_action(String objname, String propname, String &string_rv) const {
+	cerr << "g_o_a: Getting action <" << propname << "> of object <" << objname << ">\n";
+	string_rv = "!";
+	bool bool_rv = false;
+
+	//cerr << "obj_types == " << obj_types << endl;
+	/*
+	cerr << "obj_types == \n";
+	for (map<String, String>::const_iterator iter = obj_types.begin();
+	     iter != obj_types.end(); iter ++)
+	  cerr << "  " << (*iter)._key << " -> " << (*iter)._value << "\n";
+	cerr << ".\n";
+	*/
+	if (!has(obj_types, objname)) {
+		debug_print("Checking nonexistent object <" + objname + "> for action <" + propname + ">.");
+		return false;
+	}
+	String objtype = (*obj_types.find(objname))._value;
+
+	//reserved_words *rw;
+
+	const GeasBlock *block = find_by_name(objtype, objname);
+	String not_prop = "not " + propname;
+	uint c1, c2;
+	for (uint i = 0; i < block->data.size(); i ++) {
+		String line = block->data[i];
+		//cerr << "  g_o_a: Handling line <" << line << ">\n";
+		String tok = first_token(line, c1, c2);
+		// SENSITIVE?
+		if (tok == "type") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok))
+				get_type_action(param_contents(tok), propname, bool_rv, string_rv);
+			else {
+				gi->debug_print("Expected parameter for type in " + line);
+			}
+		}
+		/*
+		else if (rw != NULL && tok == propname && rw->has(propname))
+		{
+		  tok = next_token (line, c1, c2);
+		  if (is_param(tok))
+		    {
+		      cerr << "   Parameter, skipping\n";
+		    }
+		  else
+		    {
+		      //cerr << "   Action, skipping\n";
+		      cerr << "   Action, string_rv is now <" << string_rv << ">\n";
+		      string_rv = line.substr (c1);
+		      bool_rv = true;
+		    }
+		}
+		     */
+		// SENSITIVE?
+		else if (tok == "action") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok) && param_contents(tok) == propname) {
+				if (c2 + 1 < line.length())
+					string_rv = line.substr(c2 + 1);
+				else
+					string_rv = "";
+				bool_rv = true;
+				cerr << "   Action line, string_rv now <" << string_rv << ">\n";
+			}
+		}
+	}
+
+	cerr << "g_o_a: Ultimately returning value " << (bool_rv ? "true" : "false")  << ", with String <" << string_rv << ">\n\n";
+
+	return bool_rv;
+}
+
+void GeasFile::get_type_action(String typenamex, String actname, bool &bool_rv, String &string_rv) const {
+	//cerr << "  Checking type <" << typenamex << "> for action <" << actname << ">\n";
+	const GeasBlock *block = find_by_name("type", typenamex);
+	if (block == NULL) {
+		debug_print("Object of nonexistent type " + typenamex);
+		return;
+	}
+	for (uint i = 0; i < block->data.size(); i ++) {
+		String line = block->data[i];
+		//cerr << "    g_t_a: Comparing vs. line <" << line << ">\n";
+		uint c1, c2;
+		String tok = first_token(line, c1, c2);
+		// SENSITIVE?
+		if (tok == "action") {
+			//cerr << "      match...\n";
+			tok = next_token(line, c1, c2);
+			if (is_param(tok) && param_contents(tok) == actname) {
+				bool_rv = true;
+				string_rv = line.substr(c2);
+				//cerr << " present: {" + string_rv + "}\n";
+			}
+		}
+		// SENSITIVE?
+		else if (tok == "type") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok))
+				get_type_action(param_contents(tok), actname, bool_rv, string_rv);
+		}
+	}
+}
+
+void GeasFile::register_block(String blockname, String blocktype) {
+	cerr << "registering block " << blockname << " / " << blocktype << endl;
+	if (has(obj_types, blockname)) {
+		String errdesc = "Trying to register block of named <" + blockname +
+		                 "> of type <" + blocktype + "> when there is already one, of type <" +
+		                 obj_types[blockname] + ">";
+		debug_print(errdesc);
+		throw errdesc;
+	}
+	obj_types[blockname] = blocktype;
+}
+
+String GeasFile::static_svar_lookup(String varname) const {
+	cerr << "static_svar_lookup(" << varname << ")" << endl;
+	//varname = lcase (varname);
+	for (uint i = 0; i < size("variable"); i ++)
+		//if (blocks[i].lname == varname)
+		if (ci_equal(blocks[i].name, varname)) {
+			String rv;
+			String tok;
+			uint c1, c2;
+			bool found_typeline = false;
+			for (uint j = 0; j < blocks[i].data.size(); j ++) {
+				String line = blocks[i].data[j];
+				tok = first_token(line, c1, c2);
+				// SENSITIVE?
+				if (tok == "type") {
+					tok = next_token(line, c1, c2);
+					// SENSITIVE?
+					if (tok == "numeric")
+						throw String("Trying to evaluate int var '" + varname +
+						             "' as String");
+					// SENSITIVE?
+					if (tok != "String")
+						throw String("Bad variable type " + tok);
+					found_typeline = true;
+				}
+				// SENSITIVE?
+				else if (tok == "value") {
+					tok = next_token(line, c1, c2);
+					if (!is_param(tok))
+						throw String("Expected param after value in " + line);
+					rv = param_contents(tok);
+				}
+			}
+			if (!found_typeline)
+				throw String(varname + " is a numeric variable");
+			cerr << "static_svar_lookup(" << varname << ") -> \"" << rv << "\"" << endl;
+			return rv;
+		}
+	debug_print("Variable <" + varname + "> not found.");
+	return "";
+}
+
+String GeasFile::static_ivar_lookup(String varname) const {
+	//varname = lcase (varname);
+	for (uint i = 0; i < size("variable"); i ++)
+		//if (blocks[i].lname == varname)
+		if (ci_equal(blocks[i].name, varname)) {
+			String rv;
+			String tok;
+			uint c1, c2;
+			for (uint j = 0; j < blocks[i].data.size(); j ++) {
+				String line = blocks[i].data[j];
+				tok = first_token(line, c1, c2);
+				// SENSITIVE?
+				if (tok == "type") {
+					tok = next_token(line, c1, c2);
+					// SENSITIVE?
+					if (tok == "String")
+						throw String("Trying to evaluate String var '" + varname +
+						             "' as numeric");
+					// SENSITIVE?
+					if (tok != "numeric")
+						throw String("Bad variable type " + tok);
+				}
+				// SENSITIVE?
+				else if (tok == "value") {
+					tok = next_token(line, c1, c2);
+					if (!is_param(tok))
+						throw String("Expected param after value in " + line);
+					rv = param_contents(tok);
+				}
+			}
+			return rv;
+		}
+	debug_print("Variable <" + varname + "> not found");
+	return "-32768";
+}
+
+String GeasFile::static_eval(String input) const {
+	//cerr << "static_eval (" << input << ")" << endl;
+	String rv = "";
+	for (uint i = 0; i < input.length(); i ++) {
+		if (input[i] == '#') {
+			uint j;
+			for (j = i + 1; j < input.length() && input[j] != '#'; j ++)
+				;
+			if (j == input.length())
+				throw String("Error processing '" + input + "', odd hashes");
+			uint k;
+			for (k = i + 1; k < j && input[k] != ':'; k ++)
+				;
+			if (k == ':') {
+				String objname;
+				if (input[i + 1] == '(' && input[k - 1] == ')')
+					objname = static_svar_lookup(input.substr(i + 2, k - i - 4));
+				else
+					objname = input.substr(i + 1, k - i - 2);
+				cerr << "  objname == '" << objname << endl;
+				//rv += get_obj_property (objname, input.substr (k+1, j-k-2));
+				String tmp;
+				bool had_var;
+
+				String objprop = input.substr(k + 1, j - k - 2);
+				cerr << "  objprop == " << objprop << endl;
+				had_var = get_obj_property(objname, objprop, tmp);
+				rv += tmp;
+				if (!had_var)
+					debug_print("Requesting nonexistent property <" + objprop +
+					            "> of object <" + objname + ">");
+			} else {
+				cerr << "i == " << i << ", j == " << j << ", length is " << input.length() << endl;
+				cerr << "Looking up static var " << input.substr(i + 1, j - i - 1) << endl;
+				rv += static_svar_lookup(input.substr(i + 1, j - i - 1));
+			}
+			i = j;
+		} else if (input[i] == '%') {
+			uint j;
+			for (j = i; j < input.length() && input[j] != '%'; j ++)
+				;
+			if (j == input.length())
+				throw String("Error processing '" + input + "', unmatched %");
+			rv += static_ivar_lookup(input.substr(i + 1, j - i - 2));
+			i = j;
+		} else
+			rv += input[i];
+	}
+	if (rv != input)
+		cerr << "*** CHANGED ***\n";
+	//cerr << "static_eval (" << input << ") --> \"" << rv << "\"" << endl;
+	return rv;
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/geas_file.h b/engines/glk/quest/geas_file.h
new file mode 100644
index 0000000..e9558ca
--- /dev/null
+++ b/engines/glk/quest/geas_file.h
@@ -0,0 +1,123 @@
+/* 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_QUEST_GEAS_FILE
+#define GLK_QUEST_GEAS_FILE
+
+#include "glk/quest/string.h"
+#include "common/algorithm.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "common/stream.h"
+
+namespace Glk {
+namespace Quest {
+
+class GeasInterface;
+
+class reserved_words;
+
+/**
+ * Ordered array of items
+ */
+template<class T>
+class Set : public Common::Array<T> {
+public:
+	/**
+	 * Insert a new item
+	 */
+	void insert(T val) {
+		this->push_back(val);
+		Common::sort(this->begin(), this->end());
+	}
+};
+
+struct GeasBlock {
+	////// lname == lowercase name
+	////// nname == normal name
+	////// parent == initial parent object (lowercased)
+	// name == name
+	// parent == initial parent object
+	String blocktype, name, parent;
+	Common::Array<String> data;
+	//GeasBlock (const Common::Array<String> &, String, uint, bool);
+	GeasBlock() {}
+};
+
+struct GeasFile {
+	GeasInterface *gi;
+	void debug_print(String s) const;
+
+	//vector<GeasBlock> rooms, objects, textblocks, functions, procedures, types;
+	//GeasBlock synonyms, game;
+	Common::Array <GeasBlock> blocks;
+
+	//Common::Array<GeasBlock> rooms, objects, textblocks, functions, procedures,
+	//  types, synonyms, game, variables, timers, choices;
+	StringMap obj_types;
+	StringArrayIntMap type_indecies;
+
+	void register_block(String blockname, String blocktype);
+
+	const GeasBlock &block(String type, uint index) const;
+	uint size(String type) const;
+
+	void read_into(const Common::Array<String> &, String, uint, bool, const reserved_words &, const reserved_words &);
+
+
+
+	GeasFile() : gi(nullptr) {}
+	explicit GeasFile(const Common::Array<String> &in_data,
+	                  GeasInterface *gi);
+
+	bool obj_has_property(String objname, String propname) const;
+	bool get_obj_property(String objname, String propname,
+	                      String &rv) const;
+
+	void get_type_property(String typenamex, String propname,
+	                       bool &, String &) const;
+	bool obj_of_type(String object, String type) const;
+	bool type_of_type(String subtype, String supertype) const;
+
+	Set<String> get_obj_keys(String obj) const;
+	void get_obj_keys(String, Set<String> &) const;
+	void get_type_keys(String, Set<String> &) const;
+
+	bool obj_has_action(String objname, String propname) const;
+	bool get_obj_action(String objname, String propname,
+	                    String &rv) const;
+	void get_type_action(String typenamex, String propname,
+	                     bool &, String &) const;
+	String static_eval(String) const;
+	String static_ivar_lookup(String varname) const;
+	String static_svar_lookup(String varname) const;
+
+	const GeasBlock *find_by_name(String type, String name) const;
+};
+
+Common::WriteStream &operator<<(Common::WriteStream &, const GeasBlock &);
+Common::WriteStream &operator<<(Common::WriteStream &, const GeasFile &);
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/geas_glk.cpp b/engines/glk/quest/geas_glk.cpp
new file mode 100644
index 0000000..8f48799
--- /dev/null
+++ b/engines/glk/quest/geas_glk.cpp
@@ -0,0 +1,240 @@
+/* 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/quest/geas_glk.h"
+#include "glk/quest/geas_runner.h"
+#include "glk/quest/quest.h"
+#include "glk/quest/streams.h"
+#include "glk/windows.h"
+
+namespace Glk {
+namespace Quest {
+
+void glk_put_cstring(const char *);
+
+
+winid_t mainglkwin;
+winid_t inputwin;
+winid_t bannerwin;
+strid_t inputwinstream;
+
+const bool use_inputwindow = false;
+
+int ignore_lines;			// count of lines to ignore in game output
+
+void draw_banner() {
+	uint32 width;
+	uint32 index;
+	if (bannerwin) {
+		g_vm->glk_window_clear(bannerwin);
+		g_vm->glk_window_move_cursor(bannerwin, 0, 0);
+		strid_t stream = g_vm->glk_window_get_stream(bannerwin);
+
+		g_vm->glk_set_style_stream(stream, style_User1);
+		g_vm->glk_window_get_size(bannerwin, &width, NULL);
+		for (index = 0; index < width; index++)
+			g_vm->glk_put_char_stream(stream, ' ');
+		g_vm->glk_window_move_cursor(bannerwin, 1, 0);
+
+		if (g_vm->banner.empty())
+			g_vm->glk_put_string_stream(stream, (char *)"Geas 0.4");
+		else
+			g_vm->glk_put_string_stream(stream, (char *)g_vm->banner.c_str());
+	}
+}
+
+void glk_put_cstring(const char *s) {
+	/* The cast to remove const is necessary because g_vm->glk_put_string
+	 * receives a "char *" despite the fact that it could equally well use
+	 * "const char *". */
+	g_vm->glk_put_string((char *)s);
+}
+
+GeasResult GeasGlkInterface::print_normal(const String &s) {
+	if (!ignore_lines)
+		glk_put_cstring(s.c_str());
+	return r_success;
+}
+
+GeasResult GeasGlkInterface::print_newline() {
+	if (!ignore_lines)
+		glk_put_cstring("\n");
+	else
+		ignore_lines--;
+	return r_success;
+}
+
+GeasResult GeasGlkInterface::set_style(const GeasFontStyle &style) {
+	// Glk styles are defined before the window opens, so at this point we can only
+	// pick the most suitable style, not define a new one.
+	uint match;
+	if (style.is_italic && style.is_bold)
+		match = style_Alert;
+	else if (style.is_italic)
+		match = style_Emphasized;
+	else if (style.is_bold)
+		match = style_Subheader;
+	else if (style.is_underlined)
+		match = style_User2;
+	else
+		match = style_Normal;
+
+	g_vm->glk_set_style_stream(g_vm->glk_window_get_stream(mainglkwin), match);
+	return r_success;
+}
+
+void GeasGlkInterface::set_foreground(String s) {
+	if (s != "") {
+	}
+}
+
+void GeasGlkInterface::set_background(String s) {
+	if (s != "") {
+	}
+}
+
+/* Code lifted from GeasWindow.  Should be common.  Maybe in
+ * GeasInterface?
+ */
+String GeasGlkInterface::get_file(const String &fname) const {
+	Common::File ifs;
+	if (ifs.open(fname)) {
+		glk_put_cstring("Couldn't open ");
+		glk_put_cstring(fname.c_str());
+		g_vm->glk_put_char(0x0a);
+		return "";
+	}
+
+	// Read entirety of the file
+	char *buf = new char[ifs.size()];
+	ifs.read(buf, ifs.size());
+	
+	String result(buf, buf + ifs.size());
+	delete[] buf;
+
+	return result;
+}
+
+String GeasGlkInterface::get_string() {
+	char buf[200];
+	g_vm->glk_request_line_event(inputwin, buf, (sizeof buf) - 1, 0);
+	while (1) {
+		event_t ev;
+
+		g_vm->glk_select(&ev);
+
+		if (ev.type == evtype_LineInput && ev.window == inputwin) {
+			return String(buf, ev.val1);
+		}
+		/* All other events, including timer, are deliberately
+		 * ignored.
+		 */
+	}
+}
+
+uint GeasGlkInterface::make_choice(String label, Common::Array<String> v) {
+	size_t n;
+
+	g_vm->glk_window_clear(inputwin);
+
+	glk_put_cstring(label.c_str());
+	g_vm->glk_put_char(0x0a);
+	n = v.size();
+	for (size_t i = 0; i < n; ++i) {
+		StringStream t;
+		String s;
+		t << i + 1;
+		t >> s;
+		glk_put_cstring(s.c_str());
+		glk_put_cstring(": ");
+		glk_put_cstring(v[i].c_str());
+		glk_put_cstring("\n");
+	}
+
+	StringStream t;
+	String s;
+	String s1;
+	t << n;
+	t >> s;
+	s1 = "Choose [1-" + s + "]> ";
+	g_vm->glk_put_string_stream(inputwinstream, (char *)(s1.c_str()));
+
+	int choice = atoi(get_string().c_str());
+	if (choice < 1) {
+		choice = 1;
+	}
+	if ((size_t)choice > n) {
+		choice = n;
+	}
+
+	StringStream u;
+	u << choice;
+	u >> s;
+	s1 = "Chosen: " +  s + "\n";
+	glk_put_cstring(s1.c_str());
+
+	return choice - 1;
+}
+
+String GeasGlkInterface::absolute_name(String rel_name, String parent) const {
+	cerr << "absolute_name ('" << rel_name << "', '" << parent << "')\n";
+	if (parent[0] != '/')
+		return rel_name;
+
+	if (rel_name[0] == '/') {
+		cerr << "  --> " << rel_name << "\n";
+		return rel_name;
+	}
+	Common::Array<String> path;
+	uint dir_start = 1, dir_end;
+	while (dir_start < parent.length()) {
+		dir_end = dir_start;
+		while (dir_end < parent.length() && parent[dir_end] != '/')
+			dir_end ++;
+		path.push_back(parent.substr(dir_start, dir_end - dir_start));
+		dir_start = dir_end + 1;
+	}
+	path.pop_back();
+	dir_start = 0;
+	String tmp;
+	while (dir_start < rel_name.length()) {
+		dir_end = dir_start;
+		while (dir_end < rel_name.length() && rel_name[dir_end] != '/')
+			dir_end ++;
+		tmp = rel_name.substr(dir_start, dir_end - dir_start);
+		dir_start = dir_end + 1;
+		if (tmp == ".")
+			continue;
+		else if (tmp == "..")
+			path.pop_back();
+		else
+			path.push_back(tmp);
+	}
+	String rv;
+	for (uint i = 0; i < path.size(); i ++)
+		rv = rv + "/" + path[i];
+	cerr << " ---> " << rv << "\n";
+	return rv;
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/geas_glk.h b/engines/glk/quest/geas_glk.h
new file mode 100644
index 0000000..2647bf1
--- /dev/null
+++ b/engines/glk/quest/geas_glk.h
@@ -0,0 +1,92 @@
+/* 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_QUEST_GEAS_GLK
+#define GLK_QUEST_GEAS_GLK
+
+#include "glk/quest/geas_runner.h"
+#include "glk/windows.h"
+
+namespace Glk {
+namespace Quest {
+
+
+/* User interface bridge from Geas Core to Glk.
+
+  Glk Window arrangment.
+
+    +---------+
+    |    B    |
+    +---------+
+    |    M    |
+    |         |
+    +---------+
+    |    I    |
+    +---------+
+
+  B is a one line "banner window", showing the game name and author.  Kept
+  in the global variable, it's optional, null if unavailable.
+  optional.
+  M is the main window where the text of the game appears.  Kept in the
+  global variable mainglkwin.
+  I is a one line "input window" where the user inputs their commands.
+  Kept in the global variable inputwin, it's optional, and if not separate
+  is set to mainglkwin.
+
+  Maybe in future revisions there will be a status window (including a
+  compass rose).
+*/
+
+class GeasGlkInterface : public GeasInterface {
+protected:
+	virtual String get_file(const String &fname) const;
+	virtual GeasResult print_normal(const String &s);
+	virtual GeasResult print_newline();
+
+	virtual void set_foreground(String);
+	virtual void set_background(String);
+	virtual GeasResult set_style(const GeasFontStyle &);
+
+	virtual String get_string();
+	virtual uint make_choice(String, Common::Array<String>);
+
+	virtual String absolute_name(String, String) const;
+public:
+	GeasGlkInterface() {
+		;
+	}
+};
+
+extern winid_t mainglkwin;
+extern winid_t inputwin;
+extern winid_t bannerwin;
+extern strid_t inputwinstream;
+extern int ignore_lines;
+extern const bool use_inputwindow;
+
+extern void glk_put_cstring(const char *);
+extern void draw_banner();
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/geas_impl.h b/engines/glk/quest/geas_impl.h
new file mode 100644
index 0000000..1bdbbfd
--- /dev/null
+++ b/engines/glk/quest/geas_impl.h
@@ -0,0 +1,214 @@
+/* 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_QUEST_GEAS_IMPL
+#define GLK_QUEST_GEAS_IMPL
+
+#include "glk/quest/geas_runner.h"
+#include "glk/quest/geas_state.h"
+#include "glk/quest/limit_stack.h"
+
+namespace Glk {
+namespace Quest {
+
+struct match_binding {
+	String var_name;
+	String var_text;
+	uint start, end;
+	//operator String();
+	String tostring();
+	match_binding(String vn, uint i) : var_name(vn), start(i) {}
+	void set(String vt, uint i) {
+		var_text = vt;
+		end = i;
+	}
+};
+
+Common::WriteStream &operator<< (Common::WriteStream &, const match_binding &);
+
+
+struct match_rv {
+	bool success;
+	Common::Array<match_binding> bindings;
+	//match_rv (bool b, const Common::Array<String> &v) : success(b), bindings(v) {}
+	match_rv() : success(false) {}
+	match_rv(bool b, const match_rv &rv) : success(b), bindings(rv.bindings) {}
+	operator bool () {
+		return success;
+	}
+};
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const match_rv &rv);
+/*
+  inline ostream &operator<< (ostream &o, const match_rv &rv)
+{
+  //o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": " << rv.bindings << "}";
+  o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": [";
+  //o << rv.bindings.size();
+  //o << rv.bindings;
+  for (uint i = 0; i < rv.bindings.size(); i ++)
+    o << rv.bindings[i] << ", ";
+  o << "]}";
+  return o;
+}
+*/
+
+class geas_implementation : public GeasRunner {
+	//GeasInterface *gi;
+	GeasFile gf;
+	//bool running;
+	bool dont_process, outputting;
+	LimitStack <GeasState> undo_buffer;
+	Common::Array <String> function_args;
+	String this_object;
+	v2string current_places;
+	bool is_running_;
+	Logger logger;
+
+public:
+	geas_implementation(GeasInterface *in_gi)
+		: GeasRunner(in_gi), undo_buffer(20), is_running_(true) {}
+	void set_game(const String &fname);
+
+	bool is_running() const;
+	String get_banner();
+	void run_command(String);
+	bool try_match(String s, bool, bool);
+	match_rv match_command(String input, String action) const;
+	match_rv match_command(String input, uint ichar,
+	                       String action, uint achar, match_rv rv) const;
+	bool dereference_vars(Common::Array<match_binding> &bindings, bool is_internal) const;
+	bool dereference_vars(Common::Array<match_binding> &, const Common::Array<String> &, bool is_internal) const;
+	bool match_object(String text, String name, bool is_internal = false) const;
+	void set_vars(const Common::Array<match_binding> &v);
+	bool run_commands(String, const GeasBlock *, bool is_internal = false);
+
+	void display_error(String errorname, String object = "");
+
+	String substitute_synonyms(String) const;
+
+	void set_svar(String, String);
+	void set_svar(String, uint, String);
+	void set_ivar(String, int);
+	void set_ivar(String, uint, int);
+
+	String get_svar(String) const;
+	String get_svar(String, uint) const;
+	int get_ivar(String) const;
+	int get_ivar(String, uint) const;
+
+	bool find_ivar(String, uint &) const;
+	bool find_svar(String, uint &) const;
+
+	void regen_var_look();
+	void regen_var_dirs();
+	void regen_var_objects();
+	void regen_var_room();
+
+	void look();
+
+	String displayed_name(String object) const;
+	//String get_obj_name (const Common::Array<String> &args) const;
+	String get_obj_name(String name, const Common::Array<String> &where, bool is_internal) const;
+
+	bool has_obj_property(String objname, String propname) const;
+	bool get_obj_property(String objname, String propname,
+	                      String &rv) const;
+	bool has_obj_action(String obj, String prop) const;
+	bool get_obj_action(String objname, String actname,
+	                    String &rv) const;
+	String exit_dest(String room, String dir, bool *is_act = NULL) const;
+	Common::Array<Common::Array<String> > get_places(String room);
+
+	void set_obj_property(String obj, String prop);
+	void set_obj_action(String obj, String act);
+	void move(String obj, String dest);
+	void goto_room(String room);
+	String get_obj_parent(String obj);
+
+	void print_eval(String);
+	void print_eval_p(String);
+	String eval_string(String s);
+	String eval_param(String s) {
+		assert(is_param(s));
+		return eval_string(param_contents(s));
+	}
+
+
+	void run_script_as(String, String);
+	void run_script(String);
+	void run_script(String, String &);
+	void run_procedure(String);
+	void run_procedure(String, Common::Array<String> args);
+	String run_function(String);
+	String run_function(String, Common::Array<String> args);
+	String bad_arg_count(String);
+
+	bool eval_conds(String);
+	bool eval_cond(String);
+	GeasState state;
+
+	virtual void tick_timers();
+	virtual v2string get_inventory();
+	virtual v2string get_room_contents();
+	v2string get_room_contents(String);
+	virtual vstring get_status_vars();
+	virtual Common::Array<bool> get_valid_exits();
+
+
+	inline void print_formatted(String s) const {
+		if (outputting) gi->print_formatted(s);
+	}
+	inline void print_normal(String s) const {
+		if (outputting) gi->print_normal(s);
+	}
+	inline void print_newline() const {
+		if (outputting) gi->print_newline();
+	}
+
+	/*
+	inline void print_formatted (String s) const {
+	  if (outputting)
+	    gi->print_formatted(s);
+	  else
+	    gi->print_formatted ("{{" + s + "}}");
+	}
+	inline void print_normal (String s) const
+	{
+	  if (outputting)
+	    gi->print_normal (s);
+	  else
+	    gi->print_normal("{{" + s + "}}");
+	}
+	inline void print_newline() const {
+	  if (outputting)
+	    gi->print_newline();
+	  else
+	    gi->print_normal ("{{|n}}");
+	}
+	*/
+};
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/geas_runner.cpp b/engines/glk/quest/geas_runner.cpp
new file mode 100644
index 0000000..a029c07
--- /dev/null
+++ b/engines/glk/quest/geas_runner.cpp
@@ -0,0 +1,3675 @@
+/* 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/quest/geas_runner.h"
+#include "glk/quest/read_file.h"
+#include "glk/quest/geas_state.h"
+#include "glk/quest/geas_util.h"
+#include "glk/quest/reserved_words.h"
+#include "glk/quest/geas_impl.h"
+#include "glk/quest/quest.h"
+#include "glk/quest/streams.h"
+#include "glk/quest/String.h"
+
+namespace Glk {
+namespace Quest {
+
+class GeasInterface;
+
+static const char *dir_names[] = {"north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "up", "down", "out"};
+static const char *short_dir_names[] = {"n", "s", "e", "w", "ne", "nw", "se", "sw", "u", "d", "out"};
+
+const ObjectRecord *get_obj_record(const Common::Array<ObjectRecord> &v, const String &name) {
+	for (uint i = 0; i < v.size(); i ++)
+		if (ci_equal(v[i].name, name))
+			return &v[i];
+	return nullptr;
+}
+
+
+
+GeasRunner *GeasRunner::get_runner(GeasInterface *gi) {
+	return new geas_implementation(gi);
+}
+
+bool geas_implementation::find_ivar(String name, uint &rv) const {
+	for (uint n = 0; n < state.ivars.size(); n ++)
+		if (ci_equal(state.ivars[n].name, name)) {
+			rv = n;
+			return true;
+		}
+	return false;
+}
+
+bool geas_implementation::find_svar(String name, uint &rv) const {
+	//name = lcase (name);
+	for (uint n = 0; n < state.svars.size(); n ++)
+		if (ci_equal(state.svars[n].name, name)) {
+			rv = n;
+			return true;
+		}
+	return false;
+}
+
+void geas_implementation::set_svar(String varname, String varval) {
+	cerr << "set_svar (" << varname << ", " << varval << ")\n";
+	int i1 = varname.find('[');
+	if (i1 == -1)
+		return set_svar(varname, 0, varval);
+	if (varname[varname.length() - 1] != ']') {
+		gi->debug_print("set_svar: Badly formatted name " + varname);
+		return;
+	}
+	String arrayname = varname.substr(0, i1);
+	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2);
+	cerr << "set_svar(" << varname << ") --> set_svar (" << arrayname << ", " << indextext << ")\n";
+	for (uint c3 = 0; c3 < indextext.size(); c3 ++)
+		if (indextext[c3] < '0' || indextext[c3] > '9') {
+			set_svar(arrayname, get_ivar(indextext), varval);
+			return;
+		}
+	set_svar(arrayname, parse_int(indextext), varval);
+	return;
+}
+
+void geas_implementation::set_svar(String varname, uint index, String varval) {
+	uint n, m;
+	if (!find_svar(varname, n)) {
+		if (find_ivar(varname, m)) {
+			gi->debug_print("Defining " + varname + " as String variable when there is already a numeric variable of that name.");
+			return;
+		}
+		SVarRecord svr;
+		svr.name = varname;
+		n = state.svars.size();
+		state.svars.push_back(svr);
+	}
+	state.svars[n].set(index, varval);
+	if (index == 0) {
+		for (uint varn = 0; varn < gf.size("variable"); varn ++) {
+			const GeasBlock &go(gf.block("variable", varn));
+			if (ci_equal(go.name, varname)) {
+				String script = "";
+				uint c1, c2;
+				for (uint j = 0; j < go.data.size(); j ++)
+					// SENSITIVE ?
+					if (first_token(go.data[j], c1, c2) == "onchange")
+						script = trim(go.data[j].substr(c2 + 1));
+				if (script != "")
+					run_script(script);
+			}
+		}
+	}
+}
+
+String geas_implementation::get_svar(String varname) const {
+	int i1 = varname.find('[');
+	if (i1 == -1)
+		return get_svar(varname, 0);
+	if (varname[varname.length() - 1] != ']') {
+		gi->debug_print("get_svar: badly formatted name " + varname);
+		return "";
+	}
+	String arrayname = varname.substr(0, i1);
+	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2);
+	cerr << "get_svar(" << varname << ") --> get_svar (" << arrayname << ", " << indextext << ")\n";
+	for (uint c3 = 0; c3 < indextext.size(); c3 ++)
+		if (indextext[c3] < '0' || indextext[c3] > '9')
+			return get_svar(arrayname, get_ivar(indextext));
+	return get_svar(arrayname, parse_int(indextext));
+}
+String geas_implementation::get_svar(String varname, uint index) const {
+	for (uint i = 0; i < state.svars.size(); i ++) {
+		if (ci_equal(state.svars[i].name, varname))
+			return state.svars[i].get(index);
+	}
+
+	gi->debug_print("get_svar (" + varname + ", " + string_int(index) + "): No such variable defined.");
+	return "";
+}
+
+int geas_implementation::get_ivar(String varname) const {
+	int i1 = varname.find('[');
+	if (i1 == -1)
+		return get_ivar(varname, 0);
+	if (varname[varname.length() - 1] != ']') {
+		gi->debug_print("get_ivar: Badly formatted name " + varname);
+		return -32767;
+	}
+	String arrayname = varname.substr(0, i1);
+	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2);
+	cerr << "get_ivar(" << varname << ") --> get_ivar (" << arrayname << ", " << indextext << ")\n";
+	for (uint c3 = 0; c3 < indextext.size(); c3 ++)
+		if (indextext[c3] < '0' || indextext[c3] > '9')
+			return get_ivar(arrayname, get_ivar(indextext));
+	return get_ivar(arrayname, parse_int(indextext));
+}
+int geas_implementation::get_ivar(String varname, uint index) const {
+	for (uint i = 0; i < state.ivars.size(); i ++)
+		if (ci_equal(state.ivars[i].name, varname))
+			return state.ivars[i].get(index);
+	gi->debug_print("get_ivar: Tried to read undefined int '" + varname +
+	                "' [" + string_int(index) + "]");
+	return -32767;
+}
+void geas_implementation::set_ivar(String varname, int varval) {
+	int i1 = varname.find('[');
+	if (i1 == -1)
+		return set_ivar(varname, 0, varval);
+	if (varname[varname.length() - 1] != ']') {
+		gi->debug_print("set_ivar: Badly formatted name " + varname);
+		return;
+	}
+	String arrayname = varname.substr(0, i1);
+	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2);
+	cerr << "set_svar(" << varname << ") --> set_svar (" << arrayname << ", " << indextext << ")\n";
+	for (uint c3 = 0; c3 < indextext.size(); c3 ++)
+		if (indextext[c3] < '0' || indextext[c3] > '9') {
+			set_ivar(arrayname, get_ivar(indextext), varval);
+			return;
+		}
+	set_ivar(arrayname, parse_int(indextext), varval);
+}
+
+void geas_implementation::set_ivar(String varname, uint index, int varval) {
+	uint n, m;
+	if (!find_ivar(varname, n)) {
+		if (find_svar(varname, m)) {
+			gi->debug_print("Defining " + varname + " as numeric variable when there is already a String variable of that name.");
+			return;
+		}
+		IVarRecord ivr;
+		ivr.name = varname;
+		n = state.ivars.size();
+		state.ivars.push_back(ivr);
+	}
+	state.ivars[n].set(index, varval);
+	if (index == 0) {
+		for (uint varn = 0; varn < gf.size("variable"); varn ++) {
+			const GeasBlock &go(gf.block("variable", varn));
+			//if (go.lname == varname)
+			if (ci_equal(go.name, varname)) {
+				String script = "";
+				uint c1, c2;
+				for (uint j = 0; j < go.data.size(); j ++)
+					// SENSITIVE?
+					if (first_token(go.data[j], c1, c2) == "onchange")
+						script = trim(go.data[j].substr(c2 + 1));
+				if (script != "")
+					run_script(script);
+			}
+		}
+	}
+}
+
+
+
+Common::WriteStream &operator<<(Common::WriteStream &o, const match_binding &mb) {
+	o << "MB['" << mb.var_name << "' == '" << mb.var_text << "' @ "
+	  << mb.start << " to " << mb.end << "]";
+	return o;
+}
+
+String match_binding::tostring() {
+	ostringstream oss;
+	oss << *this;
+	return oss.str();
+}
+
+Common::WriteStream &operator<<(Common::WriteStream &o, const Set<String> &s) {
+	o << "{ ";
+	for (Set<String>::const_iterator i = s.begin(); i != s.end(); i ++) {
+		if (i != s.begin())
+			o << ", ";
+		o << (*i);
+	}
+	o << " }";
+	return o;
+}
+
+bool geas_implementation::has_obj_action(String obj, String prop) const {
+	String tmp;
+	return get_obj_action(obj, prop, tmp);
+}
+
+
+bool geas_implementation::get_obj_action(String objname, String actname,
+        String &rv) const {
+	//String backup_object = this_object;
+	//this_object = objname;
+
+	cerr << "get_obj_action (" << objname << ", " << actname << ")\n";
+	String tok;
+	uint c1, c2;
+	for (uint i = state.props.size() - 1; i + 1 > 0; i --)
+		if (state.props[i].name == objname) {
+			String line = state.props[i].data;
+			// SENSITIVE?
+			if (first_token(line, c1, c2) != "action")
+				continue;
+			tok = next_token(line, c1, c2);
+			if (!is_param(tok) || ci_equal(param_contents(tok), actname))
+				continue;
+			rv = trim(line.substr(c2));
+			cerr << "  g_o_a: returning true, \"" << rv << "\".";
+			return true;
+		}
+	return gf.get_obj_action(objname, actname, rv);
+	//bool bool_rv = gf.get_obj_action (objname, actname, rv);
+	//this_object = backup_object;
+	//return bool_rv;
+}
+
+bool geas_implementation::has_obj_property(String obj, String prop) const {
+	String tmp;
+	return get_obj_property(obj, prop, tmp);
+}
+
+bool geas_implementation::get_obj_property(String obj, String prop,
+        String &string_rv) const {
+	String is_prop = "properties " + prop;
+	String not_prop = "properties not " + prop;
+	for (uint i = state.props.size() - 1; i + 1 > 0; i --)
+		if (ci_equal(state.props[i].name, obj)) {
+			String dat = state.props[i].data;
+			//cerr << "In looking for " << obj << ":" << prop << ", got line "
+			//     << dat << endl;
+			if (ci_equal(dat, not_prop)) {
+				//cerr << "   not_prop, returning false\n";
+				string_rv = "!";
+				return false;
+			}
+			if (ci_equal(dat, is_prop)) {
+				//cerr << "   is_prop, returning true\n";
+				string_rv = "";
+				return true;
+			}
+			int index = dat.find('=');
+			if (index != -1 && ci_equal(dat.substr(0, index), is_prop)) {
+				string_rv = dat.substr(index + 1);
+				return true;
+			}
+		}
+	return gf.get_obj_property(obj, prop, string_rv);
+}
+
+void geas_implementation::set_obj_property(String obj, String prop) {
+	state.props.push_back(PropertyRecord(obj, "properties " + prop));
+	if (ci_equal(prop, "hidden") || ci_equal(prop, "not hidden") ||
+	        ci_equal(prop, "invisible") || ci_equal(prop, "not invisible")) {
+		gi->update_sidebars();
+		regen_var_objects();
+	}
+}
+
+void geas_implementation::set_obj_action(String obj, String act) {
+	state.props.push_back(PropertyRecord(obj, "action " + act));
+}
+
+void geas_implementation::move(String obj, String dest) {
+	for (uint i = 0; i < state.objs.size(); i ++)
+		if (ci_equal(state.objs[i].name, obj)) {
+			state.objs[i].parent = dest;
+			gi->update_sidebars();
+			regen_var_objects();
+			return;
+		}
+	gi->debug_print("Tried to move nonexistent object '" + obj +
+	                "' to '" + dest + "'.");
+}
+
+String geas_implementation::get_obj_parent(String obj) {
+	//obj = lcase (obj);
+	for (uint i = 0; i < state.objs.size(); i ++)
+		if (ci_equal(state.objs[i].name, obj))
+			return state.objs[i].parent;
+	gi->debug_print("Tried to find parent of nonexistent object " + obj);
+	return "";
+}
+
+void geas_implementation::goto_room(String room) {
+	state.location = room;
+	regen_var_room();
+	regen_var_dirs();
+	regen_var_look();
+	regen_var_objects();
+	String scr;
+	if (get_obj_action(room, "script", scr))
+		run_script_as(room, scr);
+	//run_script (scr);
+	look();
+}
+
+void geas_implementation::display_error(String errorname, String obj) {
+	cerr << "display_error (" << errorname << ", " << obj << ")\n";
+	if (obj != "") {
+		String tmp;
+		if (!get_obj_property(obj, "gender", tmp))
+			tmp = "it";
+		set_svar("quest.error.gender", tmp);
+
+		if (!get_obj_property(obj, "article", tmp))
+			tmp = "it";
+		set_svar("quest.error.article", tmp);
+
+		cerr << "In erroring " << errorname << " / " << obj << ", qeg == "
+		     << get_svar("quest.error.gender") << ", qea == "
+		     << get_svar("quest.error.article") << endl;
+		// TODO quest.error.charactername
+	}
+
+	const GeasBlock *game = gf.find_by_name("game", "game");
+	assert(game != NULL);
+	String tok;
+	uint c1, c2;
+	for (uint i = 0; i < game->data.size(); i ++) {
+		String line = game->data[i];
+		tok = first_token(line, c1, c2);
+		// SENSITIVE?
+		if (tok == "error") {
+			tok = next_token(line, c1, c2);
+			if (is_param(tok)) {
+				String text = param_contents(tok);
+				int index = text.find(';');
+				String errortype = trim(text.substr(0, index));
+				// SENSITIVE?
+				if (errortype == errorname) {
+					print_eval_p(trim(text.substr(index + 1)));
+					return;
+				}
+			} else
+				gi->debug_print("Bad error line: " + line);
+		}
+	}
+	//print_formatted ("Default error " + errorname);
+
+	// ARE THESE SENSITIVE?
+	if (errorname == "badcommand")
+		print_eval("I don't understand your command. Type HELP for a list of valid commands.");
+	else if (errorname == "badgo")
+		print_eval("I don't understand your use of 'GO' - you must either GO in some direction, or GO TO a place.");
+	else if (errorname == "badgive")
+		print_eval("You didn't say who you wanted to give that to.");
+	else if (errorname == "badcharacter")
+		print_eval("I can't see anybody of that name here.");
+	else if (errorname == "noitem")
+		print_eval("You don't have that.");
+	else if (errorname == "itemunwanted")
+		print_eval_p("#quest.error.gender# doesn't want #quest.error.article#.");
+	else if (errorname == "badlook")
+		print_eval("You didn't say what you wanted to look at.");
+	else if (errorname == "badthing")
+		print_eval("I can't see that here.");
+	else if (errorname == "defaultlook")
+		print_eval("Nothing out of the ordinary.");
+	else if (errorname == "defaultspeak")
+		print_eval_p("#quest.error.gender# says nothing.");
+	else if (errorname == "baditem")
+		print_eval("I can't see that anywhere.");
+	else if (errorname == "defaulttake")
+		print_eval("You pick #quest.error.article# up.");
+	else if (errorname == "baduse")
+		print_eval("You didn't say what you wanted to use that on.");
+	else if (errorname == "defaultuse")
+		print_eval("You can't use that here.");
+	else if (errorname == "defaultout")
+		print_eval("There's nowhere you can go out to around here.");
+	else if (errorname == "badplace")
+		print_eval("You can't go there.");
+	else if (errorname == "defaultexamine")
+		print_eval("Nothing out of the ordinary.");
+	else if (errorname == "badtake")
+		print_eval("You can't take #quest.error.article#.");
+	else if (errorname == "cantdrop")
+		print_eval("You can't drop that here.");
+	else if (errorname == "defaultdrop")
+		print_eval("You drop #quest.error.article#.");
+	else if (errorname == "baddrop")
+		print_eval("You are not carrying such a thing.");
+	else if (errorname == "badpronoun")
+		print_eval("I don't know what '#quest.error.pronoun#' you are referring to.");
+	else if (errorname == "badexamine")
+		print_eval("You didn't say what you wanted to examine.");
+	else
+		gi->debug_print("Bad error name " + errorname);
+}
+
+String geas_implementation::displayed_name(String obj) const {
+	String rv = obj, tmp;
+
+	if (get_obj_property(obj, "alias", tmp))
+		rv = tmp;
+	else {
+		for (uint i = 0; i < gf.blocks.size(); i ++)
+			if (ci_equal(gf.blocks[i].name, obj)) {
+				rv = gf.blocks[i].name;
+				break;
+			}
+	}
+	return rv;
+}
+
+/* For each destination, give:
+ * - printed name
+ * - accepted name, with prefix
+ * - accepted name, without prefix
+ * - destination, internal format
+ * - script (optional)
+ */
+Common::Array<Common::Array<String> > geas_implementation::get_places(String room) {
+	Common::Array<Common::Array<String> > rv;
+
+	const GeasBlock *gb = gf.find_by_name("room", room);
+	if (gb == NULL)
+		return rv;
+
+	String line, tok;
+	uint c1, c2;
+	for (uint i = 0; i < gb->data.size(); i ++) {
+		line = gb->data[i];
+		tok = first_token(line, c1, c2);
+		if (tok == "place") {
+			tok = next_token(line, c1, c2);
+			if (!is_param(tok)) {
+				gi->debug_print("Expected parameter after 'place' in " + line);
+				continue;
+			}
+			String dest_param = eval_param(tok);
+			if (dest_param == "") {
+				gi->debug_print("Parameter empty in " + line);
+				continue;
+			}
+
+			int j = dest_param.find(';');
+			String dest, prefix = "";
+			if (j == -1)
+				dest = trim(dest_param);
+			else {
+				dest = trim(dest_param.substr(j + 1));
+				prefix = trim(dest_param.substr(0, j));
+			}
+			String displayed = displayed_name(dest);
+			String printed_dest = (prefix != "" ? prefix + " " : "") +
+			                      "|b" + displayed + "|xb";
+
+			Common::Array<String> tmp;
+			tmp.push_back(printed_dest);
+			tmp.push_back(prefix + " " + displayed);
+			tmp.push_back(displayed);
+			tmp.push_back(dest);
+			String rest = trim(line.substr(c2));
+			if (rest != "")
+				tmp.push_back(rest);
+			rv.push_back(tmp);
+		}
+	}
+
+	for (uint i = 0; i < state.exits.size(); i ++) {
+		if (state.exits[i].src != room)
+			continue;
+		line = state.exits[i].dest;
+		tok = first_token(line, c1, c2);
+		if (tok == "exit") {
+			tok = next_token(line, c1, c2);
+			if (!is_param(tok))
+				continue;
+			tok = next_token(line, c1, c2);
+			assert(is_param(tok));
+			tok = param_contents(tok);
+			Common::Array<String> args = split_param(tok);
+			if (args.size() != 2) {
+				gi->debug_print("Expected two arguments in " + tok);
+				continue;
+			}
+			assert(args[0] == room);
+			Common::Array<String> tmp;
+			String displayed = displayed_name(args[1]);
+			tmp.push_back("|b" + displayed + "|xb");
+			tmp.push_back(displayed);
+			tmp.push_back(displayed);
+			tmp.push_back(args[1]);
+			rv.push_back(tmp);
+		} else if (tok == "destroy") {
+			tok = next_token(line, c1, c2);
+			assert(tok == "exit");
+			tok = next_token(line, c1, c2);
+
+			for (v2string::iterator j = rv.begin(); j != rv.end(); j ++)
+				if ((*j)[3] == tok) {
+					rv.erase(j);
+					break;
+				}
+		}
+
+
+	}
+
+	cerr << "get_places (" << room << ") -> " << rv << "\n";
+	return rv;
+}
+
+String geas_implementation::exit_dest(String room, String dir, bool *is_script) const {
+	uint c1, c2;
+	String tok;
+	if (is_script != NULL)
+		*is_script = false;
+	for (uint i = state.exits.size() - 1; i + 1 > 0; i --)
+		if (state.exits[i].src == room) {
+			String line = state.exits[i].dest;
+			cerr << "Processing exit line '" << state.exits[i].dest << "'\n";
+			tok = first_token(line, c1, c2);
+			cerr << "   first tok is " << tok << " (vs. exit)\n";
+			// SENSITIVE?
+			if (tok != "exit")
+				continue;
+			tok = next_token(line, c1, c2);
+			cerr << "   second tok is " << tok << " (vs. " << dir << ")\n";
+			if (tok != dir)
+				continue;
+			tok = next_token(line, c1, c2);
+			cerr << "   third tok is " << tok << " (expecting parameter)\n";
+			assert(is_param(tok));
+			Common::Array<String> p = split_param(param_contents(tok));
+			assert(p.size() == 2);
+			assert(ci_equal(p[0], room));
+			return p[1];
+		}
+	/*
+	if (gf.get_obj_action (room, dir, tok))
+	  {
+	    if (is_script != NULL)
+	*is_script = true;
+	    return tok;
+	  }
+	if (gf.get_obj_property (room, dir, tok))
+	  return tok;
+	else
+	  return "";
+	*/
+
+	const GeasBlock *gb = gf.find_by_name("room", room);
+	if (gb == NULL) {
+		gi->debug_print(String("Trying to find exit <") + dir +
+		                "> of nonexistent room <" + room + ">.");
+		return "";
+	}
+	// TODO: what's the priority on this?
+	for (uint i = 0; i < gb->data.size(); i ++) {
+		String line = gb->data[i];
+		tok = first_token(line, c1, c2);
+		if (tok == dir) {
+			uint line_start = c2;
+			tok = next_token(line, c1, c2);
+			if (is_param(tok))
+				return param_contents(tok);
+			if (tok != "") {
+				if (is_script != NULL)
+					*is_script = true;
+				return trim(line.substr(line_start + 1));
+			}
+			return "";
+		}
+	}
+	return "";
+}
+
+void geas_implementation::look() {
+	String tmp;
+	if (get_obj_action(state.location, "description", tmp))
+		run_script_as(state.location, tmp);
+	//run_script(tmp);
+	else if (get_obj_property(state.location, "description", tmp))
+		print_formatted(tmp);
+	else if (get_obj_action("game", "description", tmp))
+		run_script_as("game", tmp);
+	//run_script (tmp);
+	else if (get_obj_property("game", "description", tmp))
+		print_formatted(tmp);
+	else {
+		String in_desc;
+		if (get_obj_property(state.location, "indescription", tmp))
+			in_desc = tmp;
+		else
+			in_desc = "You are in";
+		print_formatted(in_desc + " " + get_svar("quest.formatroom"));
+
+		if ((tmp = get_svar("quest.formatobjects")) != "")
+			//print_formatted ("There is " + tmp + " here.");
+			print_eval("There is #quest.formatobjects# here.");
+		if ((tmp = get_svar("quest.doorways.out")) != "")
+			print_formatted("You can go out to " + tmp + ".");
+		if ((tmp = get_svar("quest.doorways.dirs")) != "")
+			//print_formatted ("You can go " + tmp + ".");
+			print_eval("You can go #quest.doorways.dirs#.");
+		if ((tmp = get_svar("quest.doorways.places")) != "")
+			print_formatted("You can go to " + tmp + ".");
+		if ((tmp = get_svar("quest.lookdesc")) != "")
+			print_formatted(tmp);
+	}
+}
+
+void geas_implementation::set_game(const String &fname) {
+	cerr << "set_game (...)\n";
+
+	gf = read_geas_file(gi, fname);
+	if (gf.blocks.size() == 0) {
+		is_running_ = false;
+		return;
+	}
+	//print_formatted ("Ready...|n|cbblack|crred|clblue|cggreen|cyyellow|n|uunderlined: |cbblack|crred|clblue|cggreen|cyyellow|xu|n");
+	//cerr << "Read game " << gf << endl;
+	uint tok_start, tok_end;
+	outputting = true;
+
+	state = GeasState(*gi, gf);
+
+	state.running = true;
+
+	for (uint gline = 0; gline < gf.block("game", 0).data.size(); gline ++) {
+		String s = gf.block("game", 0).data[gline];
+		String tok = first_token(s, tok_start, tok_end);
+		// SENSITIVE?
+		if (tok == "asl-version") {
+			String ver = next_token(s, tok_start, tok_end);
+			if (!is_param(ver)) {
+				gi->debug_print("Version " + s + " has invalid version " +
+					            ver);
+				continue;
+			}
+			int vernum = parse_int(param_contents(ver));
+			if (vernum < 311 || vernum > 353)
+				gi->debug_print("Warning: Geas only supports ASL "
+					            " versions 3.11 to 3.53");
+		}
+		// SENSITIVE?
+		else if (tok == "background") {
+			tok = next_token(s, tok_start, tok_end);
+			if (!is_param(tok))
+				gi->debug_print(nonparam("background color", s));
+			else
+				gi->set_background(param_contents(tok));
+		}
+		// SENSITIVE?
+		else if (tok == "default") {
+			tok = next_token(s, tok_start, tok_end);
+			// SENSITIVE?
+			if (tok == "fontname") {
+				tok = next_token(s, tok_start, tok_end);
+				if (!is_param(tok))
+					gi->debug_print(nonparam("font name", s));
+				else
+					gi->set_default_font(param_contents(tok));
+			}
+			// SENSITIVE?
+			else if (tok == "fontsize") {
+				tok = next_token(s, tok_start, tok_end);
+				if (!is_param(tok))
+					gi->debug_print(nonparam("font size", s));
+				else
+					gi->set_default_font_size(param_contents(tok));
+			}
+		}
+		// SENSITIVE?
+		else if (tok == "foreground") {
+			tok = next_token(s, tok_start, tok_end);
+			if (!is_param(tok))
+				gi->debug_print(nonparam("foreground color", s));
+			else
+				gi->set_foreground(param_contents(tok));
+		}
+		// SENSITIVE?
+		else if (tok == "gametype") {
+			tok = next_token(s, tok_start, tok_end);
+			// SENSITIVE?
+			if (tok == "singleplayer")
+				continue;
+			// SENSITIVE?
+			if (tok == "multiplayer")
+				throw String("Error: geas is single player only.");
+			gi->debug_print("Unexpected game type " + s);
+		}
+		// SENSITIVE?
+		else if (tok == "nodebug") {
+		}
+		// SENSITIVE?
+		else if (tok == "start") {
+			tok = next_token(s, tok_start, tok_end);
+			if (!is_param(tok))
+				gi->debug_print(nonparam("start room", s));
+			else {
+				state.location = param_contents(tok);
+			}
+		}
+	}
+
+	const GeasBlock &game = gf.block("game", 0);
+	cerr << gf << endl;
+	//print_formatted ("Done loading " + game.name);
+	uint c1, c2;
+	String tok;
+
+	/* TODO do I run the startscript or print the opening text first? */
+	run_script("displaytext <intro>");
+
+	for (uint i = 0; i < game.data.size(); i ++)
+		// SENSITIVE?
+		if (first_token(game.data[i], c1, c2) == "startscript") {
+			run_script_as("game", game.data[i].substr(c2 + 1));
+			//run_script (game.data[i].substr (c2 + 1));
+			break;
+		}
+
+	regen_var_room();
+	regen_var_objects();
+	regen_var_dirs();
+	regen_var_look();
+	look();
+
+	cerr << "s_g: done with set_game (...)\n\n";
+}
+
+void geas_implementation::regen_var_objects() {
+	String tmp;
+	Common::Array <String> objs;
+	for (uint i = 0; i < state.objs.size(); i ++) {
+		//cerr << "r_v_o: Checking '" << state.objs[i].name << "' (" << state.objs[i].parent << "): " << ((state.objs[i].parent == state.location) ? "YES" : "NO") << endl;
+		if (ci_equal(state.objs[i].parent, state.location) &&
+		        !get_obj_property(state.objs[i].name, "hidden", tmp) &&
+		        !get_obj_property(state.objs[i].name, "invisible", tmp))
+			//!state.objs[i].hidden &&
+			//!state.objs[i].invisible)
+			objs.push_back(state.objs[i].name);
+	}
+	String qobjs = "", qfobjs = "";
+	String objname, prefix, main, suffix, propval, print1, print2;
+	for (uint i = 0; i < objs.size(); i ++) {
+		objname = objs[i];
+		if (!get_obj_property(objname, "alias", main))
+			main = objname;
+		print1 = main;
+		print2 = "|b" + main + "|xb";
+		if (get_obj_property(objname, "prefix", prefix)) {
+			print1 = prefix + " " + print1;
+			print2 = prefix + " " + print2;
+		}
+		if (get_obj_property(objname, "suffix", suffix)) {
+			print1 = print1 + " " + suffix;
+			print2 = print2 + " " + suffix;
+		}
+		qobjs = qobjs + print1;
+		qfobjs = qfobjs + print2;
+		if (i + 2 < objs.size()) {
+			qobjs = qobjs + ", ";
+			qfobjs = qfobjs + ", ";
+		} else if (i + 2 == objs.size()) {
+			qobjs = qobjs + " and ";
+			qfobjs = qfobjs + " and ";
+		}
+	}
+	set_svar("quest.objects", qobjs);
+	set_svar("quest.formatobjects", qfobjs);
+}
+
+void geas_implementation::regen_var_room() {
+	set_svar("quest.currentroom", state.location);
+
+	String tmp, formatroom;
+	if (!get_obj_property(state.location, "alias", formatroom))
+		formatroom = state.location;
+	formatroom = "|cr" + formatroom + "|cb";
+	if (get_obj_property(state.location, "prefix", tmp))
+		formatroom = tmp + " " + formatroom;
+	if (get_obj_property(state.location, "suffix", tmp))
+		formatroom = formatroom + " " + tmp;
+	//set_svar ("quest.formatroom", displayed_name (state.location));
+	set_svar("quest.formatroom", formatroom);
+
+	// regen_var_objects();
+	/*
+	String out_dest = exit_dest (state.location, "out");
+	if (out_dest == "")
+	  {
+	    set_svar ("quest.doorways.out", "");
+	    set_svar ("quest.doorways.out.display", "");
+	  }
+	else
+	  {
+	    cerr << "Updating quest.doorways.out; out_dest == {" << out_dest << "}";
+	    uint i = out_dest.find (';');
+	    cerr << ", i == " << i;
+	    String prefix = "";
+	    if (i != -1)
+	{
+	  prefix = trim (out_dest.substr (0, i-1));
+	  out_dest = trim (out_dest.substr (i + 1));
+	  cerr << "; prefix == {" << prefix << "}, out_dest == {" << out_dest << "}";
+	}
+	    cerr << "  quest.doorways.out == {" << out_dest << "}";
+	    set_svar ("quest.doorways.out", out_dest);
+	    cerr << endl;
+
+	    String tmp = displayed_name (out_dest);
+
+	    cerr << ", tmp == {" << tmp << "}";
+
+	    if (tmp != "")
+	tmp = "|b" + tmp + "|xb";
+	    else if (prefix != "")
+	tmp = prefix + " |b" + out_dest + "|xb";
+	    else
+	tmp = "|b" + out_dest + "|xb";
+
+	    cerr << ",    final value {" << tmp << "}" << endl;
+
+	    set_svar ("quest.doorways.out.display", tmp);
+	  }
+	*/
+}
+
+
+void geas_implementation::regen_var_look() {
+	String look_tag;
+	if (!get_obj_property(state.location, "look", look_tag))
+		look_tag = "";
+	set_svar("quest.lookdesc", look_tag);
+}
+
+
+void geas_implementation::regen_var_dirs() {
+	Common::Array <String> dirs;
+	// the -1 is so that it skips 'out'
+	for (uint i = 0; i < ARRAYSIZE(dir_names) - 1; i ++)
+		if (exit_dest(state.location, dir_names[i]) != "")
+			dirs.push_back(dir_names[i]);
+	String exits = "";
+	if (dirs.size() == 1)
+		exits = "|b" + dirs[0] + "|xb";
+	else if (dirs.size() > 1) {
+		for (uint i = 0; i < dirs.size(); i ++) {
+			exits = exits + "|b" + dirs[i] + "|xb";
+			if (i < dirs.size() - 2)
+				exits = exits + ", ";
+			else if (i == dirs.size() - 2)
+				exits = exits + " or ";
+		}
+	}
+	set_svar("quest.doorways.dirs", exits);
+
+	/*
+	String tmp;
+	if ((tmp = exit_dest (state.location, "out")) != "")
+	  set_svar ("quest.doorways.out", displayed_name (tmp));
+	else
+	  set_svar ("quest.doorways.out", "");
+	*/
+
+	String out_dest = exit_dest(state.location, "out");
+	if (out_dest == "") {
+		set_svar("quest.doorways.out", "");
+		set_svar("quest.doorways.out.display", "");
+	} else {
+		cerr << "Updating quest.doorways.out; out_dest == {" << out_dest << "}";
+		int i = out_dest.find(';');
+		cerr << ", i == " << i;
+		String prefix = "";
+		if (i != -1) {
+			prefix = trim(out_dest.substr(0, i - 1));
+			out_dest = trim(out_dest.substr(i + 1));
+			cerr << "; prefix == {" << prefix << "}, out_dest == {" << out_dest << "}";
+		}
+		cerr << "  quest.doorways.out == {" << out_dest << "}";
+		set_svar("quest.doorways.out", out_dest);
+		cerr << endl;
+
+		String tmp = displayed_name(out_dest);
+
+		cerr << ", tmp == {" << tmp << "}";
+
+		if (tmp != "")
+			tmp = "|b" + tmp + "|xb";
+		else if (prefix != "")
+			tmp = prefix + " |b" + out_dest + "|xb";
+		else
+			tmp = "|b" + out_dest + "|xb";
+
+		cerr << ",    final value {" << tmp << "}" << endl;
+
+		set_svar("quest.doorways.out.display", tmp);
+	}
+
+	/* TODO handle this */
+	//set_svar ("quest.doorways.places", "");
+	current_places = get_places(state.location);
+	String printed_places = "";
+	for (uint i = 0; i < current_places.size(); i ++) {
+		if (i == 0)
+			printed_places = current_places[i][0];
+		else if (i < current_places.size() - 1)
+			printed_places = printed_places + ", " + current_places[i][0];
+		else if (current_places.size() == 2)
+			printed_places = printed_places + " or " + current_places[i][0];
+		else
+			printed_places = printed_places + ", or " + current_places[i][0];
+	}
+	set_svar("quest.doorways.places", printed_places);
+}
+
+
+
+// TODO:  SENSITIVE???
+String geas_implementation::substitute_synonyms(String s) const {
+	String orig = s;
+	cerr << "substitute_synonyms (" << s << ")\n";
+	const GeasBlock *gb = gf.find_by_name("synonyms", "");
+	if (gb != NULL) {
+		/* TODO: exactly in what order does it try synonyms?
+		 * Does it have to be flanked by whitespace?
+		 */
+		for (uint i = 0; i < gb->data.size(); i ++) {
+			String line = gb->data[i];
+			int index = line.find('=');
+			if (index == -1)
+				continue;
+			Common::Array<String> words = split_param(line.substr(0, index));
+			String rhs = trim(line.substr(index + 1));
+			if (rhs == "")
+				continue;
+			for (uint j = 0; j < words.size(); j ++) {
+				String lhs = words[j];
+				if (lhs == "")
+					continue;
+				int k = 0;
+				while ((k = s.find(lhs, k)) != -1) {
+					uint end_index = k + lhs.length();
+					if ((k == 0 || s[k - 1] == ' ') &&
+					        (end_index == s.length() || s[end_index] == ' ')) {
+						s = s.substr(0, k) + rhs + s.substr(k + lhs.length());
+						k = k + rhs.length();
+					} else
+						k ++;
+				}
+			}
+		}
+	}
+	cerr << "substitute_synonyms (" << orig << ") -> '" << s << "'\n";
+	return s;
+}
+
+bool geas_implementation::is_running() const {
+	return is_running_;
+}
+
+String geas_implementation::get_banner() {
+	String banner;
+	const GeasBlock *gb = gf.find_by_name("game", "game");
+	if (gb) {
+		String line = gb->data[0];
+		uint c1, c2;
+		String tok = first_token(line, c1, c2);
+		tok = next_token(line, c1, c2);
+		tok = next_token(line, c1, c2);
+		if (is_param(tok)) {
+			banner = eval_param(tok);
+
+			for (uint i = 0; i < gb->data.size(); i ++) {
+				line = gb->data[i];
+				if (first_token(line, c1, c2) == "game" &&
+				        next_token(line, c1, c2) == "version" &&
+				        is_param(tok = next_token(line, c1, c2))) {
+					banner += ", v";
+					banner += eval_param(tok);
+				}
+			}
+
+			for (uint i = 0; i < gb->data.size(); i ++) {
+				line = gb->data[i];
+				if (first_token(line, c1, c2) == "game" &&
+				        next_token(line, c1, c2) == "author" &&
+				        is_param(tok = next_token(line, c1, c2))) {
+					banner += " | ";
+					banner += eval_param(tok);
+				}
+			}
+		}
+	}
+	return banner;
+}
+
+void geas_implementation::run_command(String s) {
+	/* if s == "restore" or "restart" or "quit" or "undo" */
+
+	if (!is_running_)
+		return;
+
+	print_newline();
+	print_normal("> " + s);
+	print_newline();
+
+	if (s == "dump status") {
+		//cerr << state << endl;
+		ostringstream oss;
+		oss << state;
+		print_normal(oss.str());
+		return;
+	}
+
+	if (s == "undo") {
+		if (undo_buffer.size() < 2) {
+			print_formatted("(No more undo information available!)");
+			return;
+		}
+		undo_buffer.pop();
+		state = undo_buffer.peek();
+		print_formatted("Undone.");
+		return;
+	}
+
+	if (!state.running)
+		return;
+	// TODO: does this get the original command, or the lowercased version?
+	set_svar("quest.originalcommand", s);
+	s = substitute_synonyms(lcase(s));
+	set_svar("quest.command", s);
+
+	bool overridden = false;
+	dont_process = false;
+
+	const GeasBlock *gb = gf.find_by_name("room", state.location);
+	if (gb != NULL) {
+		String line, tok;
+		uint c1, c2;
+		for (uint i = 0; i < gb->data.size(); i ++) {
+			line = gb->data[i];
+			tok = first_token(line, c1, c2);
+			// SENSITIVE?
+			if (tok == "beforeturn") {
+				uint scr_starts = c2;
+				tok = next_token(line, c1, c2);
+				// SENSITIVE?
+				if (tok == "override") {
+					overridden = true;
+					scr_starts = c2;
+				}
+				String scr = line.substr(scr_starts);
+				run_script(state.location, scr);
+				//run_script (scr);
+			}
+		}
+	} else
+		gi->debug_print("Unable to find block " + state.location + ".\n");
+
+	if (!overridden) {
+		gb = gf.find_by_name("game", "game");
+		if (gb != NULL) {
+			String line, tok;
+			uint c1, c2;
+			for (uint i = 0; i < gb->data.size(); i ++) {
+				line = gb->data[i];
+				tok = first_token(line, c1, c2);
+				// SENSITIVE?
+				if (tok == "beforeturn") {
+					uint scr_starts = c2;
+					tok = next_token(line, c1, c2);
+					// SENSITIVE?
+					if (tok == "override") {
+						overridden = true;
+						scr_starts = c2;
+					}
+					String scr = line.substr(scr_starts);
+					run_script_as("game", scr);
+					//run_script (scr);
+				}
+			}
+		} else
+			gi->debug_print("Unable to find block game.\n");
+	}
+
+	if (!dont_process) {
+		if (try_match(s, false, false)) {
+			/* TODO TODO */
+			// run after turn events ???
+		} else
+			display_error("badcommand");
+	}
+
+	overridden = false;
+
+	gb = gf.find_by_name("room", state.location);
+	if (gb != NULL) {
+		String line, tok;
+		uint c1, c2;
+		for (uint i = 0; i < gb->data.size(); i ++) {
+			line = gb->data[i];
+			tok = first_token(line, c1, c2);
+			// SENSITIVE?
+			if (tok == "afterturn") {
+				uint scr_starts = c2;
+				tok = next_token(line, c1, c2);
+				// SENSITIVE?
+				if (tok == "override") {
+					overridden = true;
+					scr_starts = c2;
+				}
+				String scr = line.substr(scr_starts);
+				run_script_as(state.location, scr);
+				//run_script (scr);
+			}
+		}
+	}
+	if (!overridden) {
+		gb = gf.find_by_name("game", "game");
+		if (gb != NULL) {
+			String line, tok;
+			uint c1, c2;
+			for (uint i = 0; i < gb->data.size(); i ++) {
+				line = gb->data[i];
+				tok = first_token(line, c1, c2);
+				// SENSITIVE?
+				if (tok == "afterturn") {
+					uint scr_starts = c2;
+					tok = next_token(line, c1, c2);
+					// SENSITIVE?
+					if (tok == "override") {
+						overridden = true;
+						scr_starts = c2;
+					}
+					String scr = line.substr(scr_starts);
+					run_script_as("game", scr);
+					//run_script (scr);
+				}
+			}
+		}
+	}
+
+	if (state.running)
+		undo_buffer.push(state);
+}
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const match_rv &rv) {
+	//o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": " << rv.bindings << "}";
+	o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": [";
+	//o << rv.bindings.size();
+	o << rv.bindings;
+	//for (uint i = 0; i < rv.bindings.size(); i ++)
+	//  o << rv.bindings[i] << ", ";
+	o << "]}";
+	return o;
+}
+
+match_rv geas_implementation::match_command(String input, String action) const {
+	//cerr << "match_command (\"" << input << "\", \"" << action << "\")" << endl;
+	match_rv rv = match_command(input, 0, action, 0, match_rv());
+	cerr << "match_command (\"" << input << "\", \"" << action << "\") -> " << rv << endl;
+	return rv;
+	//return match_command (input, 0, action, 0, match_rv ());
+}
+
+match_rv geas_implementation::match_command(String input, uint ichar, String action, uint achar, match_rv rv) const {
+	//cerr << "match_command (\"" << input << "\", " << ichar << ", \"" << action << "\", " << achar << ", " << rv << ")" << endl;
+	for (;;) {
+		if (achar == action.length()) {
+			//cerr << "End of action, returning " << (ichar == input.length()) << "\n";
+			return match_rv(ichar == input.length(), rv);
+		}
+		if (action[achar] == '#') {
+
+			achar ++;
+			String varname;
+			while (achar != action.length() && action[achar] != '#') {
+				varname += action[achar];
+				achar ++;
+			}
+			if (achar == action.length())
+				throw String("Unpaired hashes in command String " + action);
+			//rv.bindings.push_back (varname);
+			int index = rv.bindings.size();
+			rv.bindings.push_back(match_binding(varname, ichar));
+			achar ++;
+			varname = "";
+			//rv.bindings.push_back (varname);
+			rv.bindings[index].set(varname, ichar);
+			while (ichar < input.length()) {
+				match_rv tmp = match_command(input, ichar, action, achar, rv);
+				if (tmp.success)
+					return tmp;
+				varname += input[ichar];
+				ichar ++;
+				//rv.bindings[index] = varname;
+				rv.bindings[index].set(varname, ichar);
+			}
+			return match_rv(achar == action.length(), rv);
+		}
+		// SENSITIVE?
+		if (ichar == input.length() || !c_equal_i(input[ichar], action[achar]))
+			return match_rv();
+		//cerr << "Matched " << input[ichar] << " to " << action[achar] << endl;
+		++ achar;
+		++ ichar;
+	}
+}
+
+bool match_object_alts(String text, const Common::Array<String> &alts, bool is_internal) {
+	for (uint i = 0; i < alts.size(); i ++) {
+		cerr << "m_o_a: Checking '" << text << "' v. alt '" << alts[i] << "'.\n";
+		if (starts_with(text, alts[i])) {
+			uint len = alts[i].length();
+			if (text.length() == len)
+				return true;
+			if (text.length() > len  &&  text[len] == ' '  &&
+			        match_object_alts(text.substr(len + 1), alts, is_internal))
+				return true;
+		}
+	}
+	return false;
+}
+
+
+bool geas_implementation::match_object(String text, String name, bool is_internal) const {
+	cerr << "* * * match_object (" << text << ", " << name << ", "
+	     << (is_internal ? "true" : "false") << ")\n";
+
+	String alias, alt_list, prefix, suffix;
+
+	if (is_internal && ci_equal(text, name)) return true;
+
+	if (get_obj_property(name, "prefix", prefix) &&
+	        starts_with(text, prefix + " ") &&
+	        match_object(text.substr(prefix.length() + 1), name, false))
+		return true;
+
+	if (get_obj_property(name, "suffix", suffix) &&
+	        ends_with(text, " " + suffix) &&
+	        match_object(text.substr(0, text.length() - suffix.length() - 1), name, false))
+		return true;
+
+	if (!get_obj_property(name, "alias", alias))
+		alias = name;
+	if (ci_equal(text, alias))
+		return true;
+
+	const GeasBlock *gb = gf.find_by_name("object", name);
+	if (gb != NULL) {
+		String tok, line;
+		uint c1, c2;
+		for (uint ln = 0; ln < gb->data.size(); ln ++) {
+			line = gb->data[ln];
+			tok = first_token(line, c1, c2);
+			// SENSITIVE?
+			if (tok == "alt") {
+				tok = next_token(line, c1, c2);
+				if (!is_param(tok))
+					gi->debug_print("Expected param after alt in " + line);
+				else {
+					Common::Array<String> alts = split_param(param_contents(tok));
+					cerr << "  m_o: alt == " << alts << "\n";
+					return match_object_alts(text, alts, is_internal);
+				}
+			}
+		}
+	}
+
+	return false;
+}
+
+
+bool geas_implementation::dereference_vars(Common::Array<match_binding> &bindings, bool is_internal) const {
+	/* TODO */
+	Common::Array<String> where;
+	where.push_back("inventory");
+	where.push_back(state.location);
+	return dereference_vars(bindings, where, is_internal);
+}
+
+bool geas_implementation::dereference_vars(Common::Array<match_binding> &bindings, const Common::Array<String> &where, bool is_internal) const {
+	bool rv = true;
+	for (uint i = 0; i < bindings.size(); i ++)
+		if (bindings[i].var_name[0] == '@') {
+			String obj_name = get_obj_name(bindings[i].var_text, where, is_internal);
+			if (obj_name == "!") {
+				print_formatted("You don't see any " + bindings[i].var_text + ".");
+				rv = false;
+			} else {
+				bindings[i].var_text = obj_name;
+				bindings[i].var_name = bindings[i].var_name.substr(1);
+			}
+		}
+	return rv;
+}
+
+String geas_implementation::get_obj_name(String name, const Common::Array<String> &where, bool is_internal) const {
+	Common::Array<String> objs, printed_objs;
+	for (uint objnum = 0; objnum < state.objs.size(); objnum ++) {
+		bool is_used = false;
+		for (uint j = 0; j < where.size(); j ++) {
+			cerr << "Object #" << objnum << ": " << state.objs[objnum].name
+			     << "@" << state.objs[objnum].parent << " vs. "
+			     << where[j] << endl;
+			// SENSITIVE?
+			if (where[j] == "game" || state.objs[objnum].parent == where[j])
+				is_used = true;
+		}
+		if (is_used && !has_obj_property(state.objs[objnum].name, "hidden") &&
+		        match_object(name, state.objs[objnum].name, is_internal)) {
+			String printed_name, tmp, oname = state.objs[objnum].name;
+			objs.push_back(oname);
+			if (!get_obj_property(oname, "alias", printed_name))
+				printed_name = oname;
+			if (get_obj_property(oname, "detail", tmp))
+				printed_name = tmp;
+			printed_objs.push_back(printed_name);
+		}
+	}
+	cerr << "objs == " << objs << ", printed_objs == " << printed_objs << "\n";
+	if (objs.size() > 1) {
+		//bindings[i].var_name = bindings[i].var_name.substr(1);
+		uint num = 0;
+		//if (objs.size() > 1)
+		num = gi->make_choice("Which " + name + " do you mean?", printed_objs);
+
+		//bindings[i].var_text = objs[num];
+		return objs[num];
+	}
+	if (objs.size() == 1)
+		return objs[0];
+	return "!";
+}
+
+
+void geas_implementation::set_vars(const Common::Array<match_binding> &v) {
+	for (uint i = 0; i < v.size(); i ++)
+		set_svar(v[i].var_name, v[i].var_text);
+}
+
+
+bool geas_implementation::run_commands(String cmd, const GeasBlock *room, bool is_internal) {
+	uint c1, c2;
+	String line, tok;
+	match_rv match;
+
+	if (room != NULL) {
+		for (uint i = 0; i < room->data.size(); i++) {
+			line = room->data[i];
+			tok = first_token(line, c1, c2);
+			// SENSITIVE?
+			if (tok == "command") {
+				tok = next_token(line, c1, c2);
+				if (is_param(tok)) {
+					Common::Array<String> tmp = split_param(param_contents(tok));
+
+					for (uint j = 0; j < tmp.size(); j++)
+						if (match = match_command(cmd, tmp[j])) {
+							if (!dereference_vars(match.bindings, is_internal))
+								return false;
+							set_vars(match.bindings);
+							run_script_as(state.location, line.substr(c2 + 1));
+							//run_script (line.substr (c2+1));
+							return true;
+						}
+					/*
+					  if (match = match_command (cmd, param_contents(tok)))
+					  {
+					  if (!dereference_vars (match.bindings))
+					  return false;
+					  set_vars (match.bindings);
+					  run_script (line.substr (c2+1));
+					  return true;
+					  }
+					*/
+				} else {
+					gi->debug_print("Bad command line: " + line);
+				}
+			}
+		}
+	} else
+		gi->debug_print("room is null\n");
+
+	return false;
+}
+
+bool geas_implementation::try_match(String cmd, bool is_internal, bool is_normal) {
+	//print_formatted ("geas_impl registers " + cmd);
+
+	String line, tok;
+	match_rv match;
+
+	if (!is_normal) {
+		if (run_commands(cmd, gf.find_by_name("room", state.location)) ||
+		        run_commands(cmd, gf.find_by_name("game", "game")))
+			return true;
+	}
+
+	if ((match = match_command(cmd, "look at #@object#")) ||
+	        (match = match_command(cmd, "look #@object#"))) {
+		if (!dereference_vars(match.bindings, is_internal))
+			return true;
+
+		String object = match.bindings[0].var_text;
+
+		if (get_obj_action(object, "look", tok))
+			run_script_as(object, tok);
+		//run_script (tok);
+		else if (get_obj_property(object, "look", tok))
+			print_formatted(tok);
+		else
+			display_error("defaultlook", object);
+
+		return true;
+	}
+
+	if ((match = match_command(cmd, "examine #@object#")) ||
+	        (match = match_command(cmd, "x #@object#"))) {
+		if (!dereference_vars(match.bindings, is_internal))
+			return true;
+
+		String object = match.bindings[0].var_text;
+		if (get_obj_action(object, "examine", tok))
+			run_script_as(object, tok);
+		//run_script (tok);
+		else if (get_obj_property(object, "examine", tok))
+			print_formatted(tok);
+		else if (get_obj_action(object, "look", tok))
+			run_script_as(object, tok);
+		//run_script (tok);
+		else if (get_obj_property(object, "look", tok))
+			print_formatted(tok);
+		else
+			display_error("defaultexamine", object);
+		return true;
+	}
+
+	if (match = match_command(cmd, "look")) {
+		look();
+		return true;
+	}
+
+	if (match = match_command(cmd, "give #@first# to #@second#")) {
+		if (!dereference_vars(match.bindings, is_internal))
+			return true;
+		String script, first = match.bindings[0].var_text, second = match.bindings[1].var_text;
+		if (! ci_equal(get_obj_parent(first), "inventory"))
+			display_error("noitem", first);
+		else if (get_obj_action(second, "give " + first, script))
+			run_script(second, script);
+		//run_script (script);
+		else if (get_obj_action(first, "give to " + second, script))
+			run_script_as(first, script);
+		//run_script (script);
+		else if (get_obj_action(second, "give anything", script)) {
+			set_svar("quest.give.object.name", first);
+			run_script_as(second, script);
+			//run_script (script);
+		} else if (get_obj_action(first, "give to anything", script)) {
+			set_svar("quest.give.object.name", second);
+			run_script_as(first, script);
+			//run_script (script);
+		} else {
+			String tmp;
+			if (!get_obj_property(second, "gender", tmp))
+				tmp = "it";
+			set_svar("quest.error.gender", tmp);
+			if (!get_obj_property(first, "article", tmp))
+				tmp = "it";
+			set_svar("quest.error.article", tmp);
+			display_error("itemunwanted");
+		}
+		return true;
+	}
+
+	if ((match = match_command(cmd, "use #@first# on #@second#")) ||
+	        (match = match_command(cmd, "use #@first# with #@second#"))) {
+		if (!dereference_vars(match.bindings, is_internal))
+			return true;
+		String script, first = match.bindings[0].var_text, second = match.bindings[1].var_text;
+		if (! ci_equal(get_obj_parent(first), "inventory"))
+			display_error("noitem", first);
+		else if (get_obj_action(second, "use " + first, script)) {
+			//set_svar ("quest.use.object", first);
+			run_script_as(second, script);
+			//run_script (script);
+		} else if (get_obj_action(first, "use on " + second, script)) {
+			//set_svar ("quest.use.object", second);
+			run_script_as(first, script);
+			//run_script (script);
+		} else if (get_obj_action(second, "use anything", script)) {
+			set_svar("quest.use.object", first);
+			run_script(second, script);
+			//run_script (script);
+		} else if (get_obj_action(first, "use on anything", script)) {
+			set_svar("quest.use.object", second);
+			run_script_as(first, script);
+			//run_script (script);
+		} else
+			display_error("defaultuse");
+
+		return true;
+	}
+
+	if (match = match_command(cmd, "use #@first#")) {
+		if (!dereference_vars(match.bindings, is_internal))
+			return true;
+		String tmp, obj = match.bindings[0].var_text;
+		if (!ci_equal(get_obj_parent(obj), "inventory"))
+			display_error("noitem", obj);
+		else if (get_obj_action(obj, "use", tmp))
+			run_script_as(obj, tmp);
+		//run_script (tmp);
+		else if (get_obj_property(obj, "use", tmp))
+			print_formatted(tmp);
+		else
+			display_error("defaultuse", obj);
+		return true;
+	}
+
+
+	if ((match = match_command(cmd, "take #@object#")) ||
+	        (match = match_command(cmd, "get #@object#"))) {
+		if (!dereference_vars(match.bindings, is_internal))
+			return true;
+
+		String object = match.bindings[0].var_text;
+		if (get_obj_action(object, "take", tok)) {
+			cerr << "Running script '" << tok << "' for take " << object << endl;
+			run_script_as(object, tok);
+			//run_script (tok);
+		} else if (get_obj_property(object, "take", tok)) {
+			cerr << "Found property '" << tok << "' for take " << object << endl;
+			if (tok != "")
+				print_formatted(tok);
+			else
+				display_error("defaulttake", object);
+			String tmp;
+			move(object, "inventory");
+			if (get_obj_action(object, "gain", tmp))
+				run_script(object, tmp);
+			//run_script (tmp);
+			else if (get_obj_property(object, "gain", tmp))
+				print_formatted(tmp);
+		} else {
+			cerr << "No match found for take " << object << endl;
+			// TODO set variable with object name
+			display_error("badtake", object);
+		}
+		return true;
+	}
+
+
+	if (match = match_command(cmd, "drop #@object#")) {
+		if (!dereference_vars(match.bindings, is_internal))
+			return true;
+		String scr, obj = match.bindings[0].var_text;
+		if (get_obj_action(obj, "drop", scr)) {
+			run_script_as(obj, scr);
+			//run_script (scr);
+			return true;
+		}
+
+		const GeasBlock *gb = gf.find_by_name("object", obj);
+		if (gb != NULL) {
+			uint c1, c2, script_begins;
+			for (uint i = 0; i < gb->data.size(); i ++) {
+				line = gb->data[i];
+				tok = first_token(line, c1, c2);
+				// SENSITIVE?
+				if (tok == "drop") {
+					script_begins = c2;
+					tok = next_token(line, c1, c2);
+					// SENSITIVE?
+					if (tok == "everywhere") {
+						tok = next_token(line, c1, c2);
+						move(obj, state.location);
+						if (is_param(tok))
+							print_eval(tok);
+						else
+							gi->debug_print("Expected param after drop everywhere in " + line);
+						return true;
+					}
+					// SENSITIVE?
+					if (tok == "nowhere") {
+						if (is_param(tok))
+							print_eval(tok);
+						else
+							gi->debug_print("Expected param after drop nowhere in " + line);
+						return true;
+					}
+					run_script_as(obj, line.substr(script_begins));
+					//run_script (line.substr (script_begins));
+					return true;
+				}
+			}
+		}
+		move(obj, state.location);
+		display_error("defaultdrop", obj);
+		return true;
+	}
+
+	if ((match = match_command(cmd, "speak to #@object#")) ||
+	        (match = match_command(cmd, "speak #@object#")) ||
+	        (match = match_command(cmd, "talk to #@object#")) ||
+	        (match = match_command(cmd, "talk #@object#"))) {
+		//print_formatted ("Talk to <" + String (match.bindings[0]) + ">");
+		if (!dereference_vars(match.bindings, is_internal))
+			return true;
+		String obj = match.bindings[0].var_text;
+		String script;
+		if (get_obj_action(obj, "speak", script))
+			run_script_as(obj, script);
+		//run_script (script);
+		else
+			display_error("defaultspeak", obj);
+		//print_formatted ("Talk to <" + String (match.bindings[0]) + ">");
+		return true;
+	}
+
+	if (cmd == "exit" || cmd == "out" || cmd == "go out") {
+		const GeasBlock *gb = gf.find_by_name("room", state.location);
+		if (gb == NULL) {
+			gi->debug_print("Bad room");
+			return true;
+		}
+
+		line = "";
+		int c1 = -1, c2 = -1;
+		uint uc1, uc2;
+		// TODO: Use the first matching line or the last?
+		for (uint i = 0; i < gb->data.size(); i ++) {
+			if (first_token(gb->data[i], uc1, uc2) == "out")
+				line = gb->data[i];
+			c1 = uc1;
+			c2 = uc2;
+		}
+
+		//gi->debug_print ("COMMAND " + cmd + ": line == " + line);
+
+		if (line == "")
+			display_error("defaultout");
+		else {
+			c1 = line.find('<');
+			if (c1 != -1)
+				c2 = line.find('>', c1);
+
+			if (c1 == -1 || c2 == -1)
+				gi->debug_print("Bad out line: " + line);
+			else {
+				String tmp = trim(line.substr(c2 + 1));
+				//gi->debug_print ("tmp1 == {" + tmp + "}");
+				if (tmp != "")
+					run_script_as(state.location, tmp);
+				//run_script (tmp);
+				else {
+					tmp = line.substr(c1, c2 - c1 + 1);
+					//gi->debug_print ("tmp2 == {" + tmp + "}");
+					assert(is_param(tmp));
+					tmp = param_contents(tmp);
+					c1 = tmp.find(';');
+					if (c1 == -1)
+						goto_room(trim(tmp));
+					else
+						goto_room(trim(tmp.substr(c1 + 1)));
+				}
+			}
+		}
+		return true;
+	}
+
+	for (uint i = 0; i < ARRAYSIZE(dir_names); i ++)
+		if (cmd == dir_names[i] || cmd == (String("go ") + dir_names[i]) ||
+		        cmd == short_dir_names[i] || cmd == (String("go ") + short_dir_names[i])) {
+			bool is_script = false;
+			//print_formatted ("Trying to go " + dir_names[i]);
+			if ((tok = exit_dest(state.location, dir_names[i], &is_script)) == "") {
+				// TODO Which display_error do I use?
+				print_formatted("You can't go that way.");
+				return true;
+			}
+			if (is_script)
+				run_script_as(state.location, tok);
+			//run_script (tok);
+			else {
+				int index = tok.find(';');
+				if (index == -1)
+					goto_room(trim(tok));
+				else
+					goto_room(trim(tok.substr(index + 1)));
+			}
+			return true;
+		}
+
+	if ((match = match_command(cmd, "go to #@room#")) ||
+	        (match = match_command(cmd, "go #@room#"))) {
+		assert(match.bindings.size() == 1);
+		String destination = match.bindings[0].var_text;
+		for (uint i = 0; i < current_places.size(); i ++) {
+			if (ci_equal(destination, current_places[i][1]) ||
+			        ci_equal(destination, current_places[i][2])) {
+				if (current_places[i].size() == 5)
+					run_script_as(state.location, current_places[i][4]);
+				//run_script (current_places[i][4]);
+				else
+					goto_room(current_places[i][3]);
+				return true;
+			}
+		}
+		display_error("badplace", destination);
+		return true;
+	}
+
+	if (ci_equal(cmd, "inventory") || ci_equal(cmd, "i")) {
+		Common::Array<Common::Array<String> > inv = get_inventory();
+		if (inv.size() == 0)
+			print_formatted("You are carrying nothing.");
+		else
+			print_formatted("You are carrying:");
+		for (uint i = 0; i < inv.size(); i ++) {
+			print_normal(inv[i][0]);
+			print_newline();
+		}
+		return true;
+	}
+
+	if (ci_equal(cmd, "help")) {
+		print_formatted("|b|cl|s14Quest Quick Help|xb|cb|s00|n|n|cl|bMoving|xb|cb Press the direction buttons in the 'Compass' pane, or type |bGO NORTH|xb, |bSOUTH|xb, |bE|xb, etc. |xnTo go into a place, type |bGO TO ...|xb . To leave a place, type |bOUT, EXIT|xb or |bLEAVE|xb, or press the '|crOUT|cb' button.|n|cl|bObjects and Characters|xb|cb Use |bTAKE ...|xb, |bGIVE ... TO ...|xb, |bTALK|xb/|bSPEAK TO ...|xb, |bUSE ... ON|xb/|bWITH ...|xb, |bLOOK AT ...|xb, etc.|n|cl|bExit Quest|xb|cb Type |bQUIT|xb to leave Quest.|n|cl|bMisc|xb|cb Type |bABOUT|xb to get information on the current game.");
+		return true;
+	}
+
+	if (ci_equal(cmd, "about")) {
+		const GeasBlock *gb = gf.find_by_name("game", "game");
+		if (gb == NULL)
+			return true;
+		cerr << *gb << endl;
+
+		uint c1, c2;
+		//print_formatted ("Game name: ");
+		line = gb->data[0];
+		tok = first_token(line, c1, c2);  // game
+		tok = next_token(line, c1, c2);  // name
+		tok = next_token(line, c1, c2);  // <whatever>
+		if (is_param(tok))
+			print_formatted("Game name: " + eval_param(tok));
+
+
+		for (uint i = 0; i < gb->data.size(); i ++) {
+			line = gb->data[i];
+			// SENSITIVE?
+			if (first_token(line, c1, c2) == "game" &&
+			        next_token(line, c1, c2) == "version" &&
+			        is_param(tok = next_token(line, c1, c2)))
+				print_formatted("Version " + eval_param(tok));
+		}
+
+		for (uint i = 0; i < gb->data.size(); i ++) {
+			line = gb->data[i];
+			// SENSITIVE?
+			if (first_token(line, c1, c2) == "game" &&
+			        next_token(line, c1, c2) == "author" &&
+			        is_param(tok = next_token(line, c1, c2)))
+				print_formatted("Author: " + eval_param(tok));
+		}
+
+		for (uint i = 0; i < gb->data.size(); i ++) {
+			line = gb->data[i];
+			// SENSITIVE?
+			if (first_token(line, c1, c2) == "game" &&
+			        next_token(line, c1, c2) == "copyright" &&
+			        is_param(tok = next_token(line, c1, c2)))
+				print_formatted("Copyright: " + eval_param(tok));
+		}
+
+		for (uint i = 0; i < gb->data.size(); i ++) {
+			line = gb->data[i];
+			// SENSITIVE?
+			if (first_token(line, c1, c2) == "game" &&
+			        next_token(line, c1, c2) == "info" &&
+			        is_param(tok = next_token(line, c1, c2)))
+				print_formatted(eval_param(tok));
+		}
+
+		return true;
+	}
+
+	if (ci_equal(cmd, "quit")) {
+		is_running_ = false;
+		return true;
+	}
+
+	return false;
+}
+
+void geas_implementation::run_script_as(String obj, String scr) {
+	String backup_object, garbage;
+	backup_object = this_object;
+	this_object = obj;
+	run_script(scr, garbage);
+	this_object = backup_object;
+}
+
+void geas_implementation::run_script(String s) {
+	String garbage;
+	run_script(s, garbage);
+}
+
+void geas_implementation::run_script(String s, String &rv) {
+	//print_formatted ("     Running script " + s + ".");
+	cerr << "Script line '" << s << "'\n";
+	String tok;
+	uint c1, c2;
+
+	tok = first_token(s, c1, c2);
+
+	if (tok == "") return;
+
+	if (tok[0] == '{') {
+		uint brace1 = c1 + 1, brace2;
+		for (brace2 = s.length() - 1; brace2 >= brace1 && s[brace2] != '}'; brace2 --)
+			;
+		if (brace2 >= brace1)
+			run_script(s.substr(brace1, brace2 - brace1));
+		else
+			gi->debug_print("Unterminated brace block in " + s);
+		return;
+	}
+
+	// SENSITIVE?
+	if (tok == "action") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after action in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		int index = tok.find(';');
+		if (index == -1) {
+			gi->debug_print("Error: no semicolon in " + s);
+			return;
+		}
+		set_obj_action(trim(tok.substr(0, index)),
+		               "<" + trim(tok.substr(index + 1)) + "> " + s.substr(c2 + 1));
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "animate") {
+	}
+	// SENSITIVE?
+	else if (tok == "background") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			gi->set_background(eval_param(tok));
+		else
+			gi->debug_print("Expected parameter after foreground in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "choose") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after choose in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		const GeasBlock *gb = gf.find_by_name("selection", tok);
+		if (gb == NULL) {
+			gi->debug_print("No selection called " + tok + " found");
+			return;
+		}
+		String question, line;
+		Common::Array<String> choices, actions;
+		for (uint ln = 0; ln < gb->data.size(); ln ++) {
+			line = gb->data[ln];
+			tok = first_token(line, c1, c2);
+			// SENSITIVE?
+			if (tok == "info") {
+				tok = next_token(line, c1, c2);
+				if (is_param(tok))
+					question = eval_param(tok);
+				else
+					gi->debug_print("Expected parameter after info in " + line);
+			}
+			// SENSITIVE?
+			else if (tok == "choice") {
+				tok = next_token(line, c1, c2);
+				if (is_param(tok)) {
+					choices.push_back(eval_param(tok));
+					actions.push_back(line.substr(c2));
+				} else
+					gi->debug_print("Expected parameter after choice in " + line);
+			} else
+				gi->debug_print("Bad line " + line + " in selection");
+		}
+		if (choices.size() == 0)
+			//gi->debug_print ("No choices in selection " + gb->lname);
+			gi->debug_print("No choices in selection " + gb->name);
+		else
+			run_script(actions[gi->make_choice(question, choices)]);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "clear") {
+		gi->clear_screen();
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "clone") {
+		/* TODO */
+	}
+	// SENSITIVE?
+	else if (tok == "create") {
+		tok = next_token(s, c1, c2);
+		// SENSITIVE?
+		if (tok == "exit") { // create exit
+			String dir = "";
+
+			tok = next_token(s, c1, c2);
+			if (!is_param(tok)) {
+				dir = tok;
+				tok = next_token(s, c1, c2);
+			}
+
+			if (!is_param(tok)) {
+				gi->debug_print("Expected param after create exit in " + s);
+				return;
+			}
+			tok = eval_param(tok);
+			Common::Array<String> args = split_param(tok);
+			if (args.size() != 2) {
+				gi->debug_print("Expected 2 elements in param in " + s);
+				return;
+			}
+			if (dir != "")
+				state.exits.push_back(ExitRecord(args[0],
+				                                 "exit " + dir + " <" + tok + ">"));
+			else
+				state.exits.push_back(ExitRecord(args[0], "exit <" + tok + ">"));
+			//gi->debug_print ("Not yet able to create place type exits");
+			regen_var_dirs();
+			return;
+		}
+		// SENSITIVE?
+		else if (tok == "object") { // create object
+			/* TODO */
+		}
+		// SENSITIVE?
+		else if (tok == "room") { // create room
+			/* TODO */
+		} else
+			gi->debug_print("Bad create line " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "debug") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			gi->debug_print(eval_param(tok));
+		else
+			gi->debug_print("Expected param after debug in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "destroy") {
+		tok = next_token(s, c1, c2);
+		if (tok != "exit") {
+			gi->debug_print("expected 'exit' after 'destroy' in " + s);
+			return;
+		}
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected param after 'destroy exit' in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		Common::Array<String> args = split_param(tok);
+		if (args.size() != 2) {
+			gi->debug_print("Expected two arguments in " + s);
+			return;
+		}
+		//state.exits.push_back (ExitRecord (args[0], "destroy exit <" + tok + ">"));
+		state.exits.push_back(ExitRecord(args[0], "destroy exit " + args[1]));
+		regen_var_dirs();
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "disconnect") {
+		/* QNSO */
+	}
+	// SENSITIVE?
+	else if (tok == "displaytext") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after displaytext in " + s);
+			return;
+		}
+		const GeasBlock *gb = gf.find_by_name("text", param_contents(tok));
+		if (gb != NULL) {
+			for (uint i = 0; i < gb->data.size(); i ++) {
+				print_formatted(gb->data[i]);
+				print_newline();
+			}
+		} else
+			gi->debug_print("No such text block " + tok);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "do") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after do in " + s);
+			return;
+		}
+		String fname = eval_param(tok);
+		int index = fname.find('(');
+		if (index != -1) {
+			int index2 = fname.find(')');
+			run_procedure(trim(fname.substr(0, index)),
+			              split_f_args(fname.substr(index + 1, index2 - index - 1)));
+		} else
+			run_procedure(fname);
+
+		//run_procedure (fname);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "doaction") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after doaction in " + s);
+			return;
+		}
+		String line = eval_param(tok);
+		int index = line.find(';');
+		String obj = trim(line.substr(0, index));
+		String act = trim(line.substr(index + 1));
+		String old_object = this_object;
+		this_object = obj;
+		if (get_obj_action(obj, act, tok))
+			run_script_as(obj, tok);
+		//run_script (tok);
+		else
+			gi->debug_print("No action defined for " + obj + " // " + act);
+		this_object = old_object;
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "dontprocess") {
+		dont_process = true;
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "enter") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after enter in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		set_svar(tok, gi->get_string());
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "exec") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after exec in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		int index = tok.find(';');
+		if (index != -1) {
+			String tmp = trim(tok.substr(index + 1));
+			// SENSITIVE?
+			if (tmp == "normal") {
+				//run_command (trim (tok.substr (0, index)), true, true);
+				try_match(trim(tok.substr(0, index)), true, true);
+			} else {
+				gi->debug_print("Bad " + tmp + " in exec in " + s);
+				//run_command (trim (tok.substr (0, index)), true, false);
+				try_match(trim(tok.substr(0, index)), true, false);
+			}
+		} else {
+			//run_command (trim (tok.substr (0, index)), true, false);
+			try_match(trim(tok.substr(0, index)), true, false);
+		}
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "flag") {
+		tok = next_token(s, c1, c2);
+		bool is_on;
+		// SENSITIVE?
+		if (tok == "on")
+			is_on = true;
+		// SENSITIVE?
+		else if (tok == "off")
+			is_on = false;
+		else {
+			gi->debug_print("Expected 'on' or 'off' after flag in " + s);
+			return;
+		}
+		String onoff = tok;
+
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			set_obj_property("game", (is_on ? "" : "not ") + eval_param(tok));
+		else
+			gi->debug_print("Expected param after flag " + onoff + " in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "font") {
+		/* TODO */
+	}
+	// SENSITIVE?
+	else if (tok == "for") {
+		tok = next_token(s, c1, c2);
+		// SENSITIVE?
+		if (tok == "each") {
+			// SENSITIVE?
+			if (next_token(s, c1, c2) == "object" &&
+			        next_token(s, c1, c2) == "in") {
+				tok = next_token(s, c1, c2);
+				// SENSITIVE?
+				if (tok == "game") {
+					/* TODO: This will run over the game, rooms, and objects */
+					/* It should just do the objects. */
+					String script = s.substr(c2);
+					// Start at 1 to skip game
+					for (uint i = 1; i < state.objs.size(); i ++) {
+						cerr << "  quest.thing -> " + state.objs[i].name + "\n";
+						set_svar("quest.thing", state.objs[i].name);
+						run_script(script);
+					}
+					return;
+				} else if (is_param(tok)) {
+					tok = trim(eval_param(tok));
+					String script = s.substr(c2);
+					for (uint i = 0; i < state.objs.size(); i ++)
+						if (state.objs[i].parent == tok) {
+							set_svar("quest.thing", state.objs[i].name);
+							run_script(script);
+						}
+					return;
+				}
+			}
+		} else if (is_param(tok)) {
+			Common::Array<String> args = split_param(eval_param(tok));
+			String varname = args[0];
+			String script = s.substr(c2);
+			int startindex = parse_int(args[1]);
+			int endindex = parse_int(args[2]);
+			int step = 1;
+			if (args.size() > 3)
+				step = parse_int(args[3]);
+			for (set_ivar(varname, startindex); get_ivar(varname) < endindex;
+			        set_ivar(varname, get_ivar(varname) + step))
+				run_script(script);
+			return;
+		}
+
+	}
+	// SENSITIVE?
+	else if (tok == "foreground") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			gi->set_foreground(eval_param(tok));
+		else
+			gi->debug_print("Expected parameter after foreground in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "give") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after give in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		move(tok, "inventory");
+		String tmp;
+		if (get_obj_action(tok, "gain", tmp))
+			run_script_as(tok, tmp);
+		//run_script (tmp);
+		else if (get_obj_property(tok, "gain", tmp))
+			print_formatted(tmp);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "goto") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			goto_room(trim(eval_param(tok)));
+		else
+			gi->debug_print("Expected parameter after goto in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "helpclear") {
+	}
+	// SENSITIVE?
+	else if (tok == "helpclose") {
+	}
+	// SENSITIVE?
+	else if (tok == "helpdisplaytext") {
+	}
+	// SENSITIVE?
+	else if (tok == "helpmsg") {
+	}
+	// SENSITIVE?
+	else if (tok == "hide") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			set_obj_property(eval_param(tok), "hidden");
+		else
+			gi->debug_print("Expected param after conceal in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "show") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			set_obj_property(eval_param(tok), "not hidden");
+		else
+			gi->debug_print("Expected param after conceal in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "if") {
+		/* TODO TODO */
+		uint begin_cond = c2 + 1, end_cond, begin_then, end_then;
+
+		do {
+			tok = next_token(s, c1, c2);
+			// SENSITIVE?
+		} while (tok != "then" && tok != "");
+
+		if (tok == "") {
+			gi->debug_print("Expected then in if: " + s);
+			return;
+		}
+		end_cond = c1;
+		String cond_str = s.substr(begin_cond, end_cond - begin_cond);
+
+		begin_then = c2 + 1;
+		int brace_count = 0;
+		do {
+			tok = next_token(s, c1, c2);
+			for (uint i = 0; i < tok.length(); i ++)
+				if (tok[i] == '{')
+					brace_count ++;
+				else if (tok[i] == '}')
+					brace_count --;
+			// SENSITIVE?
+		} while (tok != "" && !(brace_count == 0 && tok == "else"));
+		end_then = c1;
+
+
+		if (eval_conds(cond_str))
+			run_script(s.substr(begin_then, end_then - begin_then), rv);
+		else if (c2 < s.length())
+			run_script(s.substr(c2), rv);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "inc" || tok == "dec") {
+		// SENSITIVE?
+		bool is_dec = (tok == "dec");
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after inc in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		int diff;
+		int index = tok.find(';');
+		String varname;
+		if (index == -1) {
+			varname = trim(tok);
+			diff = 1;
+		} else {
+			varname = trim(tok.substr(0, index));
+			diff = eval_int(tok.substr(index + 1));
+		}
+		if (is_dec)
+			set_ivar(varname, get_ivar(varname) - diff);
+		else
+			set_ivar(varname, get_ivar(varname) + diff);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "lose") {
+		/* TODO TODO */
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after lose in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+
+		/* TODO: is the object always moved to location, or only
+		 * when it had been in the inventory ?
+		 */
+		bool was_lost = (ci_equal(get_obj_parent(tok), "inventory"));
+		if (was_lost) {
+			move(tok, state.location);
+			String tmp;
+			if (get_obj_action(tok, "lose", tmp))
+				run_script_as(tok, tmp);
+			//run_script (tmp);
+			else if (get_obj_property(tok, "lose", tmp))
+				print_formatted(tmp);
+		}
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "mailto") {
+	}
+	// SENSITIVE?
+	else if (tok == "modvolume") {
+	}
+	// SENSITIVE?
+	else if (tok == "move") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after move in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		int index = tok.find(';');
+		if (index == -1) {
+			gi->debug_print("No semi in " + tok + " in " + s);
+			return;
+		}
+		move(trim(tok.substr(0, index)), trim(tok.substr(index + 1)));
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "msg") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			print_eval(param_contents(tok));
+		else
+			gi->debug_print("Expected parameter after msg in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "msgto") {
+		/* QNSO */
+	}
+	// SENSITIVE?
+	else if (tok == "outputoff") {
+		//print_formatted ("<<");
+		outputting = false;
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "outputon") {
+		outputting = true;
+		//print_formatted (">>");
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "panes") {
+		/* TODO */
+	}
+	// SENSITIVE?
+	else if (tok == "pause") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after pause in " + s);;
+			return;
+		}
+		int i = eval_int(param_contents(tok));
+		gi->pause(i);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "picture") {
+	}
+	// SENSITIVE?
+	else if (tok == "playerlose") {
+		run_script("displaytext <lose>");
+		state.running = false;
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "playerwin") {
+		run_script("displaytext <win>");
+		state.running = false;
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "playmidi") {
+	}
+	// SENSITIVE?
+	else if (tok == "playmod") {
+	}
+	// SENSITIVE?
+	else if (tok == "playwav") {
+	}
+	// SENSITIVE?
+	else if (tok == "property") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter in '" + s + "'");
+			return;
+		}
+		Common::Array<String> args = split_param(eval_param(tok));
+		for (uint i = 1; i < args.size(); i ++) {
+			String val = args[i];
+			/*
+			if (val[0] == '[' && val[val.length() - 1] == ']')
+			  val = val.substr (1, val.length() - 2);
+			//state.props.push_back (PropertyRecord (args[0], val));
+			*/
+			val = trim_braces(val);
+			set_obj_property(args[0], val);
+		}
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "repeat") {
+		/* TODO TODO: assumes script is a "do ..." */
+		tok = next_token(s, c1, c2);
+		// SENSITIVE?
+		if (tok != "while" && tok != "until") {
+			gi->debug_print("Expected while or until after repeat in " + s);
+			return;
+		}
+		bool is_while = (tok == "while");
+		uint start_cond = c2, end_cond = (uint) -1;
+		while ((tok = next_token(s, c1, c2)) != "") {
+			// SENSITIVE?
+			if (tok == "do") {
+				end_cond = c1;
+				break; // TODO: Do I break here?
+			}
+		}
+		if (end_cond == -1) {
+			gi->debug_print("No script found after condition in " + s);
+			return;
+		}
+		String cond = trim(s.substr(start_cond, end_cond - start_cond));
+		String script = trim(s.substr(end_cond));
+		cerr << "Interpreting '" << s << "' as ("
+		     << (is_while ? "WHILE" : "UNTIL") << ") ("
+		     << cond << ") {" << script << "}\n";
+		while (eval_conds(cond) == is_while)
+			run_script(script);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "return") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			rv = eval_param(tok);
+		else
+			gi->debug_print("Expected parameter after return in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "reveal") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			set_obj_property(eval_param(tok), "not invisible");
+		else
+			gi->debug_print("Expected param after reveal in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "conceal") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			set_obj_property(eval_param(tok), "invisible");
+		else
+			gi->debug_print("Expected param after conceal in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "say") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok)) {
+			tok = eval_param(tok);
+			print_formatted("\"" + tok + "\"");
+		} else
+			gi->debug_print("Expected param after say in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "set") {
+		String vartype = "";
+		tok = next_token(s, c1, c2);
+		// SENSITIVE?
+		if (tok == "interval") {
+			tok = next_token(s, c1, c2);
+			if (!is_param(tok)) {
+				gi->debug_print("Expected param after set interval in " + s);
+				return;
+			}
+			tok = eval_param(tok);
+			int index = tok.find(';');
+			if (index == -1) {
+				gi->debug_print("No semicolon in param in " + s);
+				return;
+			}
+			//String timer_name = lcase (trim (tok.substr (0, index)));
+			String timer_name = trim(tok.substr(0, index));
+			uint time_val = parse_int(trim(tok.substr(index + 1)));
+
+			for (uint i = 0; i < state.timers.size(); i ++)
+				if (state.timers[i].name == timer_name) {
+					state.timers[i].interval = time_val;
+					return;
+				}
+			gi->debug_print("no interval named " + timer_name + " found!");
+			return;
+		}
+		// SENSITIVE?
+		if (tok == "String" || tok == "numeric") {
+			vartype = tok;
+			tok = next_token(s, c1, c2);
+		}
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter in " + s);
+			return;
+		}
+		if (tok.find(';') == -1) {
+			gi->debug_print("Only one expression in set in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		int index = tok.find(';');
+		//String varname = lcase (trim (tok.substr (0, index)));
+		String varname = trim(tok.substr(0, index));
+		if (vartype == "") {
+			for (uint varn = 0; varn < state.ivars.size(); varn ++)
+				if (state.ivars[varn].name == varname) {
+					vartype = "numeric";
+					break;
+				}
+			if (vartype == "")
+				for (uint varn = 0; varn < state.svars.size(); varn ++)
+					if (state.svars[varn].name == varname) {
+						vartype = "String";
+						break;
+					}
+		}
+		if (vartype == "") {
+			gi->debug_print("Undefined variable " + varname + " in " + s);
+			return;
+		}
+		if (vartype == "String")
+			set_svar(varname, trim_braces(trim(tok.substr(index + 1))));
+		else
+			set_ivar(varname, eval_int(tok.substr(index + 1)));
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "setstring") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter in " + s);
+			return;
+		}
+		if (tok.find(';') == -1) {
+			gi->debug_print("Only one expression in set in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		int index = tok.find(';');
+		String varname = trim(tok.substr(0, index));
+		set_svar(varname, trim_braces(trim(tok.substr(index + 1))));
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "setvar") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter in " + s);
+			return;
+		}
+		if (tok.find(';') == -1) {
+			gi->debug_print("Only one expression in set in " + s);
+			return;
+		}
+		tok = eval_param(tok);
+		int index = tok.find(';');
+		String varname = trim(tok.substr(0, index));
+		set_ivar(varname, eval_int(tok.substr(index + 1)));
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "shell") {
+
+	}
+	// SENSITIVE?
+	else if (tok == "shellexe") {
+	}
+	// SENSITIVE?
+	else if (tok == "speak") {
+		tok = next_token(s, c1, c2);
+		if (is_param(tok))
+			gi->speak(eval_param(tok));
+		else
+			gi->debug_print("Expected param after speak in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "stop") {
+		state.running = false;
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "timeron" || tok == "timeroff") {
+		// SENSITIVE?
+		bool running = (tok == "timeron");
+		//tok = lcase (next_token (s, c1, c2));
+		tok = next_token(s, c1, c2);
+		if (is_param(tok)) {
+			tok = eval_param(tok);
+			for (uint i = 0; i < state.timers.size(); i ++)
+				if (state.timers[i].name == tok) {
+					if (running)
+						state.timers[i].timeleft = state.timers[i].interval;
+					state.timers[i].is_running = running;
+					return;
+				}
+			gi->debug_print("No timer " + tok + " found");
+			return;
+		}
+		gi->debug_print(String("Expected parameter after timer") +
+		                (running ? "on" : "off") + " in " + s);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "type") {
+		/* TODO */
+	}
+	// SENSITIVE?
+	else if (tok == "wait") {
+		tok = next_token(s, c1, c2);
+		if (tok != "") {
+			if (!is_param(tok)) {
+				gi->debug_print("Expected parameter after wait in " + s);
+				return;
+			}
+			tok = eval_param(tok);
+		}
+		gi->wait_keypress(tok);
+		return;
+	}
+	// SENSITIVE?
+	else if (tok == "with") {
+		// QNSO
+	}
+	gi->debug_print("Unrecognized script " + s);
+}
+
+bool geas_implementation::eval_conds(String s) {
+	cerr << "if (" + s + ")" << endl;
+
+	uint c1, c2;
+	String tok = first_token(s, c1, c2);
+
+	if (tok == "") return true;
+
+	bool rv = eval_cond(s);
+
+	while (tok != "" && tok != "and")
+		tok = next_token(s, c1, c2);
+
+	if (tok == "and")
+		rv = rv && eval_conds(s.substr(c2));
+	else {
+		tok = first_token(s, c1, c2);
+		while (tok != "" && tok != "or")
+			tok = next_token(s, c1, c2);
+		if (tok == "or")
+			rv = rv || eval_conds(s.substr(c2));
+	}
+
+	cerr << "if (" << s << ") --> " << (rv ? "true" : "false") << endl;
+	return rv;
+}
+
+bool geas_implementation::eval_cond(String s) {
+	uint c1, c2;
+	String tok = first_token(s, c1, c2);
+	// SENSITIVE?
+	if (tok == "not")
+		return !eval_cond(s.substr(c2));
+	// SENSITIVE?
+	else if (tok == "action") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("expected parameter after property in " + s);
+			return false;
+		}
+		tok = eval_param(tok);
+		int index = tok.find(';');
+		if (index == -1) {
+			gi->debug_print("Only one argument to property in " + s);
+			return false;
+		}
+		String obj = trim(tok.substr(0, index));
+		String act = trim(tok.substr(index + 1));
+		return has_obj_action(obj, act);
+	}
+	// SENSITIVE?
+	else if (tok == "ask") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("expected parameter after ask in " + s);
+			return false;
+		}
+		tok = eval_param(tok);
+		return gi->choose_yes_no(tok);
+	}
+	// SENSITIVE?
+	else if (tok == "exists") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("expected parameter after exists in " + s);
+			return false;
+		}
+		Common::Array<String> args = split_param(eval_param(tok));
+		bool do_report = false;
+		for (uint i = 1; i < args.size(); i ++)
+			// SENSITIVE?
+			if (args[i] == "report")
+				do_report = true;
+			else
+				gi->debug_print("Got modifier " + args[i] + " after exists");
+		//args[0] = lcase (args[0]);
+		for (uint i = 0; i < state.objs.size(); i ++)
+			if (ci_equal(state.objs[i].name, args[0]))
+				return state.objs[i].parent != "";
+		if (do_report)
+			gi->debug_print("exists " + args[0] + " failed due to nonexistence");
+		return false;
+	}
+	// SENSITIVE?
+	else if (tok == "flag") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("expected parameter after flag in " + s);
+			return false;
+		}
+		tok = trim(eval_param(tok));
+		return has_obj_property("game", tok);
+	}
+	// SENSITIVE?
+	else if (tok == "got") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("expected parameter after got in " + s);
+			return false;
+		}
+		//tok = lcase (trim (eval_param (tok)));
+		tok = trim(eval_param(tok));
+		for (uint i = 0; i < state.objs.size(); i ++)
+			if (ci_equal(state.objs[i].name, tok))
+				return ci_equal(state.objs[i].parent, "inventory");
+		gi->debug_print("No object " + tok + " found while evaling " + s);
+		return false;
+	}
+	// SENSITIVE?
+	else if (tok == "here") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("expected parameter after here in " + s);
+			return false;
+		}
+		//tok = lcase (trim (eval_param (tok)));
+		tok = trim(eval_param(tok));
+		for (uint i = 0; i < state.objs.size(); i ++)
+			if (ci_equal(state.objs[i].name, tok)) {
+				//return (ci_equal (state.objs[i].parent, state.location) &&
+				//    !has_obj_property (tok, "invisible"));
+				return (ci_equal(state.objs[i].parent, state.location));
+			}
+		/* TODO: is it invisible or hidden? */
+		gi->debug_print("No object " + tok + " found while evaling " + s);
+		return false;
+	}
+	// SENSITIVE?
+	else if (tok == "is") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("expected parameter after is in " + s);
+			return false;
+		}
+		tok = eval_param(tok);
+		int index;
+		// SENSITIVE?
+		if ((index = tok.find("!=;")) != -1) {
+			uint index1 = index;
+			do {
+				-- index1;
+			} while (index1 > 0 && tok[index1] != ';');
+
+			cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index1)))
+			     << "> != <" << trim_braces(trim(tok.substr(index + 3)))
+			     << ">\n";
+			return ci_notequal(trim_braces(trim(tok.substr(0, index - 1))),
+			                   trim_braces(trim(tok.substr(index + 3))));
+		}
+		if ((index = tok.find("lt=;")) != -1) {
+			cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index)))
+			     << "> < <" << trim_braces(trim(tok.substr(index + 4)))
+			     << ">\n";
+			return eval_int(tok.substr(0, index - 1))
+			       <= eval_int(tok.substr(index + 4));
+		}
+		if ((index = tok.find("gt=;")) != -1)
+			return eval_int(tok.substr(0, index))
+			       >= eval_int(tok.substr(index + 4));
+		if ((index = tok.find("lt;")) != -1)
+			return eval_int(tok.substr(0, index))
+			       < eval_int(tok.substr(index + 3));
+		if ((index = tok.find("gt;")) != -1)
+			return eval_int(tok.substr(0, index))
+			       > eval_int(tok.substr(index + 3));
+		if ((index = tok.find(";")) != -1) {
+			cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index)))
+			     << "> == <" << trim_braces(trim(tok.substr(index + 1)))
+			     << ">\n";
+			return ci_equal(trim_braces(trim(tok.substr(0, index))),
+			                trim_braces(trim(tok.substr(index + 1))));
+		}
+		gi->debug_print("Bad is condition " + tok + " in " + s);
+		return false;
+	}
+	// SENSITIVE?
+	else if (tok == "property") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("expected parameter after property in " + s);
+			return false;
+		}
+		tok = eval_param(tok);
+		int index = tok.find(';');
+		if (index == -1) {
+			gi->debug_print("Only one argument to property in " + s);
+			return false;
+		}
+		String obj = trim(tok.substr(0, index));
+		String prop = trim(tok.substr(index + 1));
+		return has_obj_property(obj, prop);
+	}
+	// SENSITIVE?
+	else if (tok == "real") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("expected parameter after real in " + s);
+			return false;
+		}
+		Common::Array<String> args = split_param(eval_param(tok));
+		bool do_report = false;
+		for (uint i = 1; i < args.size(); i ++)
+			// SENSITIVE?
+			if (args[i] == "report")
+				do_report = true;
+			else
+				gi->debug_print("Got modifier " + args[i] + " after exists");
+		//args[0] = lcase (args[0]);
+		for (uint i = 0; i < state.objs.size(); i ++)
+			if (ci_equal(state.objs[i].name, args[0]))
+				return true;
+		if (do_report)
+			gi->debug_print("real " + args[0] + " failed due to nonexistence");
+		return false;
+	}
+	// SENSITIVE?
+	else if (tok == "type") {
+		tok = next_token(s, c1, c2);
+		if (!is_param(tok)) {
+			gi->debug_print("Expected parameter after type in " + s);
+			return false;
+		}
+		Common::Array<String> args = split_param(eval_param(tok));
+		if (args.size() != 2) {
+			gi->debug_print("Expected two parameters to type in " + s);
+			return false;
+		}
+		return gf.obj_of_type(args[0], args[1]);
+	}
+
+	gi->debug_print("Bad condition " + s);
+	return false;
+}
+
+void geas_implementation::run_procedure(String pname, Common::Array<String> args) {
+	cerr << "run_procedure " << pname << " (" << args << ")\n";
+	Common::Array<String> backup = function_args;
+	function_args = args;
+	run_procedure(pname);
+	function_args = backup;
+}
+
+void geas_implementation::run_procedure(String pname) {
+	for (uint i = 0; i < gf.size("procedure"); i ++)
+		if (ci_equal(gf.block("procedure", i).name, pname)) {
+			const GeasBlock &proc = gf.block("procedure", i);
+			//cerr << "Running procedure " << proc << endl;
+			for (uint j = 0; j < proc.data.size(); j ++) {
+				//cerr << "  Running line #" << j << ": " << proc.data[j] << endl;
+				run_script(proc.data[j]);
+			}
+			return;
+		}
+	gi->debug_print("No procedure " + pname + " found.");
+}
+
+String geas_implementation::run_function(String pname, Common::Array<String> args) {
+	cerr << "run_function (w/ args) " << pname << " (" << args << ")\n";
+	/* Parameter is handled specially because it can't change the stack */
+	// SENSITIVE?
+	if (pname == "parameter") {
+		if (args.size() != 1) {
+			gi->debug_print("parameter called with " + string_int(args.size())
+			                + " args");
+			return "";
+		}
+		uint num = parse_int(args[0]);
+		if (0 < num && num <= function_args.size()) {
+			cerr << "   --> " << function_args[num - 1] << "\n";
+			return function_args[num - 1];
+		}
+		cerr << "   --> too many arguments\n";
+		return "";
+	}
+	Common::Array<String> backup = function_args;
+	function_args = args;
+	for (uint i = 0; i < args.size(); i ++)
+		set_svar("quest.function.parameter." + string_int(i + 1), args[i]);
+	String rv = run_function(pname);
+	function_args = backup;
+	return rv;
+}
+
+String geas_implementation::bad_arg_count(String fname) {
+	gi->debug_print("Called " + fname + " with " +
+	                string_int(function_args.size()) + " arguments.");
+	return "";
+}
+
+String geas_implementation::run_function(String pname) {
+	cerr << "geas_implementation::run_function (" << pname << ", " << function_args << ")\n";
+	//pname = lcase (pname);
+	// SENSITIVE?
+	if (pname == "getobjectname") {
+		if (function_args.size() == 0)
+			return bad_arg_count(pname);
+		//return get_obj_name (function_args);
+		Common::Array<String> where;
+		for (uint i = 1; i < function_args.size(); i ++)
+			where.push_back(function_args[i]);
+		if (where.size() == 0) {
+			where.push_back(state.location);
+			where.push_back("inventory");
+		}
+		bool is_internal = false;
+		return get_obj_name(function_args[0], where, is_internal);
+	}
+	// SENSITIVE?
+	else if (pname == "loadmethod") {
+		/* TODO TODO */
+		return "normal";
+		// "loaded" on restore from qsg
+	}
+	// SENSITIVE?
+	else if (pname == "locationof") {
+		if (function_args.size() != 1)
+			return bad_arg_count(pname);
+
+		return get_obj_parent(function_args[0]);
+	}
+	// SENSITIVE?
+	else if (pname == "objectproperty") {
+		if (function_args.size() != 2)
+			return bad_arg_count(pname);
+
+		String rv;
+		get_obj_property(function_args[0], function_args[1], rv);
+		return rv;
+	}
+	// SENSITIVE?
+	else if (pname == "timerstate") {
+		if (function_args.size() != 1)
+			return bad_arg_count(pname);
+
+		//String timername = lcase (function_args[0]);
+		String timername = function_args[0];
+		for (uint i = 0; i < state.timers.size(); i ++)
+			if (state.timers[i].name == timername)
+				return state.timers[i].is_running ? "1" : "0";
+		return "!";
+	}
+	// SENSITIVE?
+	else if (pname == "displayname") {
+		if (function_args.size() != 1)
+			return bad_arg_count(pname);
+
+		return displayed_name(function_args[0]);
+	}
+	// SENSITIVE?
+	else if (pname == "capfirst") {
+		if (function_args.size() != 1)
+			return bad_arg_count(pname);
+
+		return pcase(function_args[0]);
+	}
+	// SENSITIVE?
+	else if (pname == "instr") {
+		/* TODO What if it's not present? */
+		if (function_args.size() != 2 && function_args.size() != 3)
+			return bad_arg_count(pname);
+
+		int rv;
+		if (function_args.size() == 2)
+			rv = function_args[0].find(function_args[1]);
+		else
+			rv = function_args[1].find(function_args[2],
+			                           parse_int(function_args[0]));
+
+		if (rv == -1)
+			return string_int(rv); // TODO What goes here?
+		else
+			return string_int(rv);
+	}
+	// SENSITIVE?
+	else if (pname == "lcase") {
+		if (function_args.size() != 1)
+			return bad_arg_count(pname);
+
+		String rv = function_args[0];
+		for (uint i = 0; i < rv.size(); i ++)
+			rv[i] = tolower(rv[i]);
+		return rv;
+	}
+	// SENSITIVE?
+	else if (pname == "left") {
+		if (function_args.size() != 2)
+			return bad_arg_count(pname);
+
+		uint i = parse_int(function_args[1]);
+		if (i > function_args[0].length())
+			return function_args[0];
+		else
+			return function_args[0].substr(0, i);
+	}
+	// SENSITIVE?
+	else if (pname == "lengthof") {
+		if (function_args.size() != 1)
+			return bad_arg_count(pname);
+
+		return string_int(function_args[0].length());
+	}
+	// SENSITIVE?
+	else if (pname == "mid") {
+		if (function_args.size() != 3)
+			return bad_arg_count(pname);
+
+		uint start = parse_int(function_args[1]),
+		     len = parse_int(function_args[2]);
+		if (start > function_args[0].length())
+			return "";
+		if (start + len > function_args[0].length())
+			return function_args[0].substr(start);
+		return function_args[0].substr(start, len);
+	}
+	// SENSITIVE?
+	else if (pname == "right") {
+		if (function_args.size() != 2)
+			return bad_arg_count(pname);
+
+		uint size = parse_int(function_args[1]);
+		if (size > function_args[0].length())
+			return function_args[0];
+		return function_args[0].substr(function_args[0].length() - size);
+	} else if (pname == "ubound") {
+		if (function_args.size() != 1)
+			return bad_arg_count(pname);
+
+		/* TODO TODO */
+		return "";
+	}
+	// SENSITIVE?
+	else if (pname == "ucase") {
+		if (function_args.size() != 1)
+			return bad_arg_count(pname);
+
+		String rv = function_args[0];
+		for (uint i = 0; i < rv.length(); i ++)
+			rv[i] = toupper(rv[i]);
+		return rv;
+	}
+	// SENSITIVE?
+	else if (pname == "rand") {
+		if (function_args.size() != 2)
+			return bad_arg_count(pname);
+		uint lower = parse_int(function_args[0]),
+		     upper = parse_int(function_args[1]);
+
+		// TODO: change this to use the high order bits of the random # instead
+		return string_int(lower + (g_vm->getRandomNumber(0x7fffff) % (upper + 1 - lower)));
+	}
+	// SENSITIVE?
+	else if (pname == "speechenabled") {
+		if (function_args.size() != 0)
+			return bad_arg_count(pname);
+
+		return "0";
+		/* TODO: return 1 if speech is enabled */
+	}
+	// SENSITIVE?
+	else if (pname == "symbol") {
+		if (function_args.size() != 1)
+			return bad_arg_count(pname);
+
+		// SENSITIVE?
+		if (function_args[0] == "gt")
+			return ">";
+		// SENSITIVE?
+		else if (function_args[0] == "lt")
+			return "<";
+		gi->debug_print("Bad symbol argument: " + function_args[0]);
+		return "";
+	}
+	// SENSITIVE?
+	else if (pname == "numberparameters")
+		return string_int(function_args.size());
+	// SENSITIVE?
+	else if (pname == "thisobject")
+		return this_object;
+
+	/* disconnectedby, id, name, removeformatting */
+
+	String rv = "";
+
+	for (uint i = 0; i < gf.size("function"); i ++)
+		if (ci_equal(gf.block("function", i).name, pname)) {
+			const GeasBlock &proc = gf.block("function", i);
+			cerr << "Running function " << proc << endl;
+			for (uint j = 0; j < proc.data.size(); j ++) {
+				cerr << "  Running line #" << j << ": " << proc.data[j] << endl;
+				run_script(proc.data[j], rv);
+			}
+			return rv;
+		}
+	gi->debug_print("No function " + pname + " found.");
+	return "";
+}
+
+
+
+v2string geas_implementation::get_inventory() {
+	return get_room_contents("inventory");
+}
+
+v2string geas_implementation::get_room_contents() {
+	return get_room_contents(state.location);
+}
+
+v2string geas_implementation::get_room_contents(String room) {
+	v2string rv;
+	String objname;
+	for (uint i = 0; i < state.objs.size(); i ++)
+		if (state.objs[i].parent == room) {
+			objname = state.objs[i].name;
+			if (!has_obj_property(objname, "invisible") &&
+			        !has_obj_property(objname, "hidden")) {
+				vstring tmp;
+
+				String print_name, temp_str;
+				if (!get_obj_property(objname, "alias", print_name))
+					print_name = objname;
+				/*
+				if (get_obj_property (objname, "prefix", temp_str))
+				  print_name = temp_str + " " + print_name;
+				if (get_obj_property (objname, "suffix", temp_str))
+				  print_name = print_name + " " + temp_str;
+				*/
+				tmp.push_back(print_name);
+
+				String otype;
+				if (!get_obj_property(objname, "displaytype", otype))
+					otype = "object";
+				tmp.push_back(otype);
+				rv.push_back(tmp);
+			}
+		}
+	return rv;
+}
+
+vstring geas_implementation::get_status_vars() {
+	vstring rv;
+
+	String tok, line;
+	uint c1, c2;
+
+	for (uint i = 0; i < gf.size("variable"); i ++) {
+		const GeasBlock &gb = gf.block("variable", i);
+
+		bool nozero = false;
+		String disp;
+		bool is_numeric = true;
+
+		cerr << "g_s_v: " << gb << endl;
+
+		for (uint j = 0; j < gb.data.size(); j ++) {
+			line = gb.data[j];
+			cerr << "  g_s_v:  " << line << endl;
+			tok = first_token(line, c1, c2);
+			// SENSITIVE?
+			if (tok == "display") {
+				tok = next_token(line, c1, c2);
+
+				// SENSITIVE?
+				if (tok == "nozero") {
+					nozero = true;
+					tok = next_token(line, c1, c2);
+				}
+				if (!is_param(tok))
+					gi->debug_print("Expected param after display: " + line);
+				else
+					disp = tok;
+			}
+			// SENSITIVE?
+			else if (tok == "type") {
+				tok = next_token(line, c1, c2);
+				// SENSITIVE?
+				if (tok == "String")
+					is_numeric = false;
+			}
+		}
+
+		cerr << "  g_s_v, block 2, tok == '" << tok << "'" << endl;
+		if (!(is_numeric && nozero && get_ivar(gb.name) == 0) && disp != "") {
+			disp = param_contents(disp);
+			String outval = "";
+			for (uint j = 0; j < disp.length(); j ++)
+				if (disp[j] == '!') {
+					if (is_numeric)
+						outval = outval + string_int(get_ivar(gb.name));
+					//outval = outval + string_int (get_ivar (gb.lname));
+					else
+						outval = outval + get_svar(gb.name);
+					//outval = outval + get_svar (gb.lname);
+				} else if (disp[j] == '*') {
+					uint k;
+					for (k = j + 1; k < disp.length() && disp[k] != '*'; k ++)
+						;
+					//if (!is_numeric || get_ivar (gb.lname) != 1)
+					if (!is_numeric || get_ivar(gb.name) != 1)
+						outval = outval + disp.substr(j + 1, k - j - 1);
+					j = k;
+				} else
+					outval = outval + disp[j];
+			rv.push_back(eval_string(outval));
+		}
+	}
+	return rv;
+}
+
+Common::Array<bool> geas_implementation::get_valid_exits() {
+	Common::Array<bool> rv;
+	cerr << "Getting valid exits\n";
+	rv.push_back(exit_dest(state.location, "northwest") != "");
+	rv.push_back(exit_dest(state.location, "north") != "");
+	rv.push_back(exit_dest(state.location, "northeast") != "");
+	rv.push_back(exit_dest(state.location, "west") != "");
+	rv.push_back(exit_dest(state.location, "out") != "");
+	rv.push_back(exit_dest(state.location, "east") != "");
+	rv.push_back(exit_dest(state.location, "southwest") != "");
+	rv.push_back(exit_dest(state.location, "south") != "");
+	rv.push_back(exit_dest(state.location, "southeast") != "");
+	rv.push_back(exit_dest(state.location, "up") != "");
+	rv.push_back(exit_dest(state.location, "down") != "");
+	cerr << "Done getting valid exits\n";
+
+	return rv;
+}
+
+void geas_implementation::print_eval_p(String s) {
+	print_formatted(pcase(eval_string(s)));
+}
+
+void geas_implementation::print_eval(String s) {
+	print_formatted(eval_string(s));
+}
+
+String geas_implementation::eval_string(String s) {
+	String rv;
+	uint i, j;
+	bool do_print = (s.find('$') != -1);
+	if (do_print) cerr << "eval_string (" << s << ")\n";
+	for (i = 0; i < s.length(); i ++) {
+		//if (do_print) cerr << "e_s: i == " << i << ", s[i] == '" << s[i] << "'\n";
+		if (i + 1 < s.length() && s[i] == '#' && s[i + 1] == '@') {
+			for (j = i + 1; j < s.length() && s[j] != '#'; j ++)
+				;
+			if (j == s.length()) {
+				gi->debug_print("eval_string: Unmatched hash in " + s);
+				break;
+			}
+			//cerr << "dereferencing " + s.substr (i+2, j-i-2) << endl;
+			rv = rv + displayed_name(get_svar(s.substr(i + 2, j - i - 2)));
+			i = j;
+		} else if (s[i] == '#') {
+			for (j = i + 1; j < s.length() && s[j] != '#'; j ++)
+				;
+			if (j == s.length()) {
+				gi->debug_print("eval_string: Unmatched hash in " + s);
+				break;
+			}
+			uint k;
+			for (k = i + 1; k < j && s[k] != ':'; k ++)
+				;
+			if (k == j && j == i + 1)
+				rv += "#";
+			else if (k == j)
+				rv += get_svar(s.substr(i + 1, j - i - 1));
+			else {
+				String propname = s.substr(k + 1, j - k - 1);
+				if (s[i + 1] == '(') {
+					if (s[k - 1] != ')') {
+						gi->debug_print("e_s: Missing paren in '" +
+						                s.substr(i, j - i) + "' of '" + s + "'");
+						break;
+					}
+					String objvar = s.substr(i + 2, k - i - 3);
+					String objname = get_svar(objvar);
+					/*
+					cerr << "e_s: Getting prop [(" << objvar << ")] --> ["
+					     << objname
+					     << "]:[" << propname << "] for " << s << endl;
+					*/
+					String tmp;
+					if (get_obj_property(objname, propname, tmp))
+						rv += tmp;
+					else
+						gi->debug_print("e_s: Evaluating nonexistent object prop "
+						                "{" + objname + "}:{" + propname + "}");
+				} else {
+					String objname = s.substr(i + 1, k - i - 1);
+					/*
+					cerr << "e_s: Getting prop [" << objname << "]:["
+					     << propname << "] for " << s << endl;
+					*/
+					String tmp;
+					if (get_obj_property(objname, propname, tmp))
+						rv += tmp;
+					else
+						gi->debug_print("e_s: Evaluating nonexistent var " + objname);
+				}
+			}
+			i = j;
+		} else if (s[i] == '%') {
+			for (j = i + 1; j < s.length() && s[j] != '%'; j ++)
+				;
+			if (j == s.length()) {
+				gi->debug_print("e_s: Unmatched %s in " + s);
+				break;
+			}
+			//cerr << "e_s: Getting ivar [" << s.substr (i+1, j-i-1) << "] for " << s << endl;
+			if (j == i + 1)
+				rv += "%";
+			else
+				rv += string_int(get_ivar(s.substr(i + 1, j - i - 1)));
+			i = j;
+		} else if (s[i] == '$') {
+			j = s.find('$', i + 1);
+			/*
+			for (j = i + 1; j < s.length() && s[j] != '$'; j ++)
+			  {
+			    cerr << "  In searching for partner, j == " << j
+			   << ", s[j] == '" << s[j] << "', (s[j] == '$') == "
+			   << ((s[j] == '$') ? "true" : "false") << "\n";
+			  }
+			*/
+			//if (j == rv.size())
+			if (j == -1) {
+				gi->debug_print("Unmatched $s in " + s);
+				return rv + s.substr(i);
+			}
+			String tmp1 = s.substr(i + 1, j - i - 1);
+			cerr << "e_s: first substr was '" << tmp1 << "'\n";
+			String tmp = eval_string(tmp1);
+			//String tmp = eval_string (s.substr (i + 1, j - i - 2));
+			//cerr << "Taking substring of '" + s + "': '" + tmp + "'\n";
+			cerr << "e_s: eval substr " + s + "': '" + tmp + "'\n";
+
+			String func_eval;
+
+			int paren_open, paren_close;
+			if ((paren_open = tmp.find('(')) == -1)
+				func_eval = run_function(tmp);
+			else {
+				paren_close = tmp.find(')', paren_open);
+				if (paren_close == -1)
+					gi->debug_print("No matching right paren in " + tmp);
+				else {
+					String f_name = tmp.substr(0, paren_open);
+					String f_args = tmp.substr(paren_open + 1,
+					                           paren_close - paren_open - 1);
+					func_eval = run_function(f_name, split_f_args(f_args));
+				}
+			}
+			rv = rv + func_eval;
+			i = j;
+		} else
+			rv += s[i];
+	}
+
+
+	//cerr << "eval_string (" << s << ") -> <" << rv << ">\n";
+	return rv;
+}
+
+void geas_implementation::tick_timers() {
+	if (!state.running)
+		return;
+	//cerr << "tick_timers()\n";
+	for (uint i = 0; i < state.timers.size(); i ++) {
+		TimerRecord &tr = state.timers[i];
+		//cerr << "  Examining " << tr << "\n";
+		if (tr.is_running) {
+			//cerr << "    Advancing " << tr.name << ": " << tr.timeleft
+			//     << " / " << tr.interval << "\n";
+			if (tr.timeleft != 0)
+				tr.timeleft --;
+			else {
+				tr.is_running = false;
+				tr.timeleft = tr.interval;
+				const GeasBlock *gb = gf.find_by_name("timer", tr.name);
+				if (gb != NULL) {
+					//cout << "Running it!\n";
+					String tok, line;
+					uint c1, c2;
+					for (uint j = 0; j < gb->data.size(); j ++) {
+						line = gb->data[j];
+						tok = first_token(line, c1, c2);
+						// SENSITIVE?
+						if (tok == "action") {
+							run_script(line.substr(c2));
+							break;
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+/***********************************
+ *                                 *
+ *                                 *
+ *                                 *
+ * GeasInterface related functions *
+ *                                 *
+ *                                 *
+ *                                 *
+ ***********************************/
+
+
+GeasResult GeasInterface::print_formatted(String s, bool with_newline) {
+	unsigned int i, j;
+
+	//cerr << "print_formatted (" << s << ", " << with_newline << ")" << endl;
+
+	for (i = 0; i < s.length(); i ++) {
+		//std::cerr << "i == " << i << std::endl;
+		if (s[i] == '|') {
+			// changed indicated whether cur_style has been changed
+			// and update_style should be called at the end.
+			// it is true unless cleared (by |n or |w).
+			bool changed = true;
+			j = i;
+			i ++;
+			if (i == s.length())
+				continue;
+
+			// Are the | codes case-sensitive?
+			switch (s[i]) {
+			case 'u':
+				cur_style.is_underlined = true;
+				break;
+			case 'i':
+				cur_style.is_italic     = true;
+				break;
+			case 'b':
+				cur_style.is_bold       = true;
+				break;
+			case 'c':
+				i ++;
+
+				if (i == s.length()) {
+					clear_screen();
+					break;
+				}
+
+				switch (s[i]) {
+				case 'y':
+					cur_style.color = "#ffff00";
+					break;
+				case 'g':
+					cur_style.color = "#00ff00";
+					break;
+				case 'l':
+					cur_style.color = "#0000ff";
+					break;
+				case 'r':
+					cur_style.color = "#ff0000";
+					break;
+				case 'b':
+					cur_style.color = "";
+					break;
+
+				default:
+					clear_screen();
+					--i;
+				}
+				break;
+
+			case 's': {
+				i ++;
+				if (i == s.length() || !(s[i] >= '0' && s[i] <= '9'))
+					continue;
+				i ++;
+				if (i == s.length() || !(s[i] >= '0' && s[i] <= '9'))
+					continue;
+
+				int newsize = parse_int(s.substr(j, i - j));
+				if (newsize > 0)
+					cur_style.size = newsize;
+				else
+					cur_style.size = default_size;
+			}
+			break;
+
+			case 'j':
+				i ++;
+
+				if (i == s.length() ||
+				        !(s[i] == 'l' || s[i] == 'c' || s[i] == 'r'))
+					continue;
+				if (s[i] == 'l') cur_style.justify = JUSTIFY_LEFT;
+				else if (s[i] == 'r') cur_style.justify = JUSTIFY_RIGHT;
+				else if (s[i] == 'c') cur_style.justify = JUSTIFY_CENTER;
+				break;
+
+			case 'n':
+				print_newline();
+				changed = false;
+				break;
+
+			case 'w':
+				wait_keypress("");
+				changed = false;
+				break;
+
+			case 'x':
+				i ++;
+
+				if (s[i] == 'b')
+					cur_style.is_bold = false;
+				else if (s[i] == 'u')
+					cur_style.is_underlined = false;
+				else if (s[i] == 'i')
+					cur_style.is_italic = false;
+				else if (s[i] == 'n' && i + 1 == s.length())
+					changed = with_newline = false;
+				break;
+
+			default:
+				cerr << "p_f: Fallthrough " << s[i] << endl;
+				changed = false;
+			}
+			if (changed)
+				update_style();
+		} else {
+			for (j = i; i != s.length() && s[i] != '|'; i ++)
+				;
+			print_normal(s.substr(j, i - j));
+			if (s[i] == '|')
+				-- i;
+		}
+	}
+	if (with_newline)
+		print_newline();
+	return r_success;
+}
+
+void GeasInterface::set_default_font_size(String size) {
+	default_size = parse_int(size);
+}
+
+void GeasInterface::set_default_font(String font) {
+	default_font = font;
+}
+
+bool GeasInterface::choose_yes_no(String question) {
+	Common::Array<String> v;
+	v.push_back("Yes");
+	v.push_back("No");
+	return (make_choice(question, v) == 0);
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/geas_runner.h b/engines/glk/quest/geas_runner.h
new file mode 100644
index 0000000..bc6d858
--- /dev/null
+++ b/engines/glk/quest/geas_runner.h
@@ -0,0 +1,220 @@
+/* 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_QUEST_GEAS_RUNNER
+#define GLK_QUEST_GEAS_RUNNER
+
+#include "glk/quest/string.h"
+#include "common/array.h"
+#include "common/stream.h"
+
+namespace Glk {
+namespace Quest {
+
+typedef Common::Array<String> vstring;
+typedef Common::Array<vstring> v2string;
+
+enum geas_justification { JUSTIFY_LEFT, JUSTIFY_RIGHT, JUSTIFY_CENTER };
+
+struct GeasFontStyle {
+	bool is_underlined, is_italic, is_bold;
+	String color, font;
+	int size;
+	geas_justification justify;
+
+	GeasFontStyle() : is_underlined(false), is_italic(false), is_bold(false),
+		color(""), font(""), size(10), justify(JUSTIFY_LEFT) {}
+};
+
+class GeasFontStyleCompare {
+public:
+	int operator()(const GeasFontStyle &a, const GeasFontStyle &b) {
+		if (a.size           !=  b.size)           return a.size < b.size;
+		if (a.is_underlined  !=  b.is_underlined)  return a.is_underlined;
+		if (a.is_bold        !=  b.is_bold)        return a.is_bold;
+		if (a.is_italic      !=  b.is_italic)      return a.is_italic;
+		if (a.color          !=  b.color)          return a.color < b.color;
+		if (a.justify        !=  b.justify)        return a.justify < b.justify;
+		return 0;
+	}
+};
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const GeasFontStyle &gfs);
+
+enum GeasResult {
+	r_success,
+	r_failure,
+	r_not_supported
+};
+
+/*  Callback object used to pass information from GeasCore
+ *  to the interface objects
+ */
+class GeasInterface {
+private:
+	GeasFontStyle cur_style;
+	String default_font;
+	int default_size;
+	//string fgcolor, bgcolor;
+
+public:
+	/* Takes 1 argument, a string with Quest markup
+	 * Will output it to the user interface
+	 * If the with_newline flag is set, it will print a newline afterwords
+	 *     unless the string ends in "|xn"
+	 */
+	GeasResult print_formatted(String s, bool with_newline = true);
+
+	/* Takes one argument; that string is printed without interpretation
+	 * Must be implemented
+	 * Called by print_formatted and by Geas directly.
+	 */
+	virtual GeasResult print_normal(const String &s) = 0;
+
+	virtual GeasResult print_newline() = 0;
+
+protected:
+
+	void update_style() {
+		set_style(cur_style);
+	}
+
+	/* Changes style of output text.
+	 * Need not be implemented
+	 * Only called by update_style()
+	 */
+	virtual GeasResult set_style(const GeasFontStyle &) {
+		return r_not_supported;
+	}
+
+public:
+	virtual String absolute_name(String rel_name, String parent) const = 0;
+	virtual String get_file(const String &filename) const = 0;
+	virtual void debug_print(const String &s) {
+		warning("%s", s.c_str());
+	}
+	virtual GeasResult wait_keypress(String) {
+		return r_not_supported;
+	}
+	virtual GeasResult pause(int msec) {
+		return r_not_supported;
+	}
+	virtual GeasResult clear_screen() {
+		return r_not_supported;
+	}
+
+	//virtual GeasResult set_foreground (string) { return r_not_supported; }
+	//virtual GeasResult set_background (string) { return r_not_supported; }
+	virtual void set_foreground(String) = 0;
+	virtual void set_background(String) = 0;
+	void set_default_font_size(String s);
+	void set_default_font(String s);
+
+	/* Unsure what arguments this will take.
+	 * May also add animated, persistent, close image
+	 */
+	virtual GeasResult show_image(String filename, String resolution,
+	                              String caption, ...) {
+		return r_not_supported;
+	}
+
+	/* Again, unsure what arguments to give
+	 * May add sound type
+	 * If sync is true, do not return until file ends
+	 * If filename is "", stop playing sounds.
+	 */
+	virtual GeasResult play_sound(String filename, bool looped, bool sync) {
+		return r_not_supported;
+	}
+
+	/* Asks the user to type a free format string
+	 */
+	virtual String get_string() = 0;
+
+	/* Presents a list with header 'info', and prompts the user to
+	 * choose one item from 'choices'.
+	 * returns the index chosen.
+	 */
+	virtual uint make_choice(String info, Common::Array<String> choices) = 0;
+
+	/* Asks the user a yes/no question
+	 * (If not overridden, this has an implementation that uses make_choice()
+	 */
+	virtual bool choose_yes_no(String question);
+
+	/* args holds arguments sent to program.
+	 * if active is true, geas should retain focus
+	 * returns -   0 if disallowed
+	 *         -   1 if succeeded
+	 *         -   2 if file not found
+	 *         -   3 if it couldn't find a program to run it
+	 *         -   4 if it ran out of memory
+	 */
+	virtual int shell(Common::Array<String> args, bool active) {
+		return 0;
+	}
+
+	/* say the argument using text-to-speech
+	 */
+	virtual GeasResult speak(String) {
+		return r_not_supported;
+	}
+
+	virtual ~GeasInterface() {}
+
+	/* This is a notification that some object has changed, and
+	 * the interpreter may want to update the inventory or room object
+	 * listings.
+	 */
+	virtual void update_sidebars() { }
+};
+
+
+/* Callback for passing information from the UI to the execution core
+ */
+class GeasRunner {
+protected:
+	GeasInterface *gi;
+
+public:
+	GeasRunner(GeasInterface *_gi) : gi(_gi) {}
+
+	virtual bool is_running() const = 0;
+	virtual String get_banner() = 0;
+	virtual void run_command(String) = 0;
+
+	virtual v2string get_inventory() = 0;
+	virtual v2string get_room_contents() = 0;
+	virtual vstring  get_status_vars() = 0;
+	virtual Common::Array<bool> get_valid_exits() = 0;
+
+	virtual void tick_timers() = 0;
+
+	virtual ~GeasRunner() {  }
+	virtual void set_game(const String &fname) = 0;
+	static GeasRunner *get_runner(GeasInterface *gi);
+};
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/geas_state.cpp b/engines/glk/quest/geas_state.cpp
new file mode 100644
index 0000000..985b4e9
--- /dev/null
+++ b/engines/glk/quest/geas_state.cpp
@@ -0,0 +1,356 @@
+/* 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/quest/geas_state.h"
+#include "glk/quest/geas_runner.h"
+#include "glk/quest/geas_util.h"
+#include "glk/quest/read_file.h"
+#include "glk/quest/streams.h"
+
+namespace Glk {
+namespace Quest {
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const StringMap &m) {
+	for (StringMap::iterator i = m.begin(); i != m.end(); ++i)
+		o << (*i)._key << " -> " << (*i)._value << "\n";
+
+	return o;
+}
+
+class GeasOutputStream {
+	Common::WriteStream *_ws;
+public:
+	GeasOutputStream &put(const String &s) {
+		_ws->writeString(s);
+		_ws->writeByte(0);
+		return *this;
+	}
+	GeasOutputStream &put(char ch) {
+		_ws->writeByte(ch);
+		return *this;
+	}
+	GeasOutputStream &put(int i) {
+		Common::String s = Common::String::format("%d", i);
+		_ws->writeString(s);
+		_ws->writeByte(0);
+		return *this;
+	}
+	GeasOutputStream &put(uint i) {
+		Common::String s = Common::String::format("%u", i);
+		_ws->writeString(s);
+		_ws->writeByte(0);
+		return *this;
+	}
+	GeasOutputStream &put(unsigned long i) {
+		Common::String s = Common::String::format("%lu", i);
+		_ws->writeString(s);
+		_ws->writeByte(0);
+		return *this;
+	}
+
+	void write_out(const String &gameName, const String &saveName) {
+#ifdef TODO
+		ofstream ofs;
+		ofs.open(savename.c_str());
+		if (!ofs.is_open())
+			throw String("Unable to open \"" + savename + "\"");
+		ofs << "QUEST300" << char(0) << gamename << char(0);
+		String tmp = o.str();
+		for (uint i = 0; i < tmp.size(); i ++)
+			ofs << char (255 - tmp[i]);
+		cerr << "Done writing save game\n";
+#else
+		error("TODO");
+#endif
+	}
+};
+
+template <class T> void write_to(GeasOutputStream &gos, const Common::Array<T> &v) {
+	gos.put(v.size());
+	for (uint i = 0; i < v.size(); i ++)
+		write_to(gos, v[i]);
+}
+
+void write_to(GeasOutputStream &gos, const PropertyRecord &pr) {
+	gos.put(pr.name).put(pr.data);
+}
+
+void write_to(GeasOutputStream &gos, const ObjectRecord &pr) {
+	gos.put(pr.name).put(char(pr.hidden ? 0 : 1))
+	.put(char(pr.invisible ? 0 : 1)).put(pr.parent);
+}
+
+void write_to(GeasOutputStream &gos, const ExitRecord &er) {
+	gos.put(er.src).put(er.dest);
+}
+
+void write_to(GeasOutputStream &gos, const TimerRecord &tr) {
+	gos.put(tr.name).put(tr.is_running ? 0 : 1).put(tr.interval)
+	.put(tr.timeleft);
+}
+
+
+void write_to(GeasOutputStream &gos, const SVarRecord &svr) {
+	gos.put(svr.name);
+	gos.put(svr.max());
+	for (uint i = 0; i < svr.size(); i ++)
+		gos.put(svr.get(i));
+}
+
+void write_to(GeasOutputStream &gos, const IVarRecord &ivr) {
+	gos.put(ivr.name);
+	gos.put(ivr.max());
+	for (uint i = 0; i < ivr.size(); i ++)
+		gos.put(ivr.get(i));
+}
+
+void write_to(GeasOutputStream &gos, const GeasState &gs) {
+	gos.put(gs.location);
+	write_to(gos, gs.props);
+	write_to(gos, gs.objs);
+	write_to(gos, gs.exits);
+	write_to(gos, gs.timers);
+	write_to(gos, gs.svars);
+	write_to(gos, gs.ivars);
+}
+
+void save_game_to(String gamename, String savename, const GeasState &gs) {
+	GeasOutputStream gos;
+	write_to(gos, gs);
+	gos.write_out(gamename, savename);
+}
+
+GeasState::GeasState(GeasInterface &gi, const GeasFile &gf) {
+	running = false;
+
+	cerr << "GeasState::GeasState()" << endl;
+	for (uint i = 0; i < gf.size("game"); i ++) {
+		//const GeasBlock &go = gf.game[i];
+		//register_block ("game", "game");
+		ObjectRecord data;
+		data.name = "game";
+		data.parent = "";
+		data.hidden = false;
+		data.invisible = true;
+		objs.push_back(data);
+	}
+
+	cerr << "GeasState::GeasState() done setting game" << endl;
+	for (uint i = 0; i < gf.size("room"); i ++) {
+		const GeasBlock &go = gf.block("room", i);
+		ObjectRecord data;
+		//data.name = go.lname;
+		data.name = go.name;
+		data.parent = "";
+		data.hidden = data.invisible = true;
+		//register_block (data.name, "room");
+		objs.push_back(data);
+	}
+
+	cerr << "GeasState::GeasState() done setting rooms" << endl;
+	for (uint i = 0; i < gf.size("object"); i++) {
+		const GeasBlock &go = gf.block("object", i);
+		ObjectRecord data;
+		//data.name = go.lname;
+		data.name = go.name;
+		if (go.parent == "")
+			data.parent = "";
+		else
+			//data.parent = lcase (param_contents (go.parent));
+			data.parent = param_contents(go.parent);
+		//register_block (data.name, "object");
+		data.hidden = data.invisible = false;
+		objs.push_back(data);
+	}
+
+	cerr << "GeasState::GeasState() done setting objects" << endl;
+	for (uint i = 0; i < gf.size("timer"); i ++) {
+		const GeasBlock &go = gf.block("timer", i);
+		//cerr << "GS::GS: Handling timer " << go << "\n";
+		TimerRecord tr;
+		String interval = "", status = "";
+		for (uint j = 0; j < go.data.size(); j ++) {
+			String line = go.data[j];
+			uint c1, c2;
+			String tok = first_token(line, c1, c2);
+			if (tok == "interval") {
+				tok = next_token(line, c1, c2);
+				if (!is_param(tok))
+					gi.debug_print(nonparam("interval", line));
+				else
+					interval = param_contents(tok);
+			} else if (tok == "enabled" || tok == "disabled") {
+				if (status != "")
+					gi.debug_print("Repeated status for timer");
+				else
+					status = tok;
+			} else if (tok == "action") {
+			} else {
+				gi.debug_print("Bad timer line " + line);
+			}
+		}
+		//tr.name = go.lname;
+		tr.name = go.name;
+		tr.is_running = (status == "enabled");
+		tr.interval = tr.timeleft = parse_int(interval);
+		//register_block (tr.name, "timer");
+		timers.push_back(tr);
+	}
+
+	cerr << "GeasState::GeasState() done with timers" << endl;
+	for (uint i = 0; i < gf.size("variable"); i ++) {
+		const GeasBlock &go(gf.block("variable", i));
+		cerr << "GS::GS: Handling variable #" << i << ": " << go << endl;
+		String vartype;
+		String value;
+		for (uint j = 0; j < go.data.size(); j ++) {
+			String line = go.data[j];
+			cerr << "   Line #" << j << " of var: \"" << line << "\"" << endl;
+			uint c1, c2;
+			String tok = first_token(line, c1, c2);
+			if (tok == "type") {
+				tok = next_token(line, c1, c2);
+				if (tok == "")
+					gi.debug_print(String("Missing variable type in ")
+					               + string_geas_block(go));
+				else if (vartype != "")
+					gi.debug_print(String("Redefining var. type in ")
+					               + string_geas_block(go));
+				else if (tok == "numeric" || tok == "String")
+					vartype = tok;
+				else
+					gi.debug_print(String("Bad var. type ") + line);
+			} else if (tok == "value") {
+				tok = next_token(line, c1, c2);
+				if (!is_param(tok))
+					gi.debug_print(String("Expected parameter in " + line));
+				else
+					value = param_contents(tok);
+			} else if (tok == "display" || tok == "onchange") {
+			} else {
+				gi.debug_print(String("Bad var. line: ") + line);
+			}
+		}
+		if (vartype == "" || vartype == "numeric") {
+			IVarRecord ivr;
+			//ivr.name = go.lname;
+			ivr.name = go.name;
+			ivr.set(0, parse_int(value));
+			ivars.push_back(ivr);
+			//register_block (ivr.name, "numeric");
+		} else {
+			SVarRecord svr;
+			//svr.name = go.lname;
+			svr.name = go.name;
+			svr.set(0, value);
+			svars.push_back(svr);
+			//register_block (svr.name, "String");
+		}
+	}
+	//cerr << obj_types << endl;
+	cerr << "GeasState::GeasState() done with variables" << endl;
+}
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const PropertyRecord &pr) {
+	o << pr.name << ", data == " << pr.data;
+	return o;
+}
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const ObjectRecord &objr) {
+	o << objr.name << ", parent == " << objr.parent;
+	if (objr.hidden)
+		o << ", hidden";
+	if (objr.invisible)
+		o << ", invisible";
+	return o;
+}
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const ExitRecord er) {
+	return o << er.src << ": " << er.dest;
+}
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const TimerRecord &tr) {
+	return o << tr.name << ": " << (tr.is_running ? "" : "not ") << "running ("
+	       << tr.timeleft << " // " << tr.interval << ")";
+}
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const SVarRecord &sr) {
+	o << sr.name << ": ";
+	if (sr.size() == 0)
+		o << "(empty)";
+	else if (sr.size() <= 1)
+		o << "<" << sr.get(0) << ">";
+	else
+		for (uint i = 0; i < sr.size(); i ++) {
+			o << i << ": <" << sr.get(i) << ">";
+			if (i + 1 < sr.size())
+				o << ", ";
+		}
+	return o;
+}
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const IVarRecord &ir) {
+	o << ir.name << ": ";
+	if (ir.size() == 0)
+		o << "(empty)";
+	else if (ir.size() <= 1)
+		o << ir.get(0);
+	else
+		for (uint i = 0; i < ir.size(); i ++) {
+			o << i << ": " << ir.get(i);
+			if (i + 1 < ir.size())
+				o << ", ";
+		}
+	return o;
+}
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const GeasState &gs) {
+	o << "location == " << gs.location << "\nprops: \n";
+
+	for (uint i = 0; i < gs.props.size(); i ++)
+		o << "    " << i << ": " << gs.props[i] << "\n";
+
+	o << "objs:\n";
+	for (uint i = 0; i < gs.objs.size(); i ++)
+		o << "    " << i << ": " << gs.objs[i] << "\n";
+
+	o << "exits:\n";
+	for (uint i = 0; i < gs.exits.size(); i ++)
+		o << "    " << i << ": " << gs.exits[i] << "\n";
+
+	o << "timers:\n";
+	for (uint i = 0; i < gs.timers.size(); i ++)
+		o << "    " << i << ": " << gs.timers[i] << "\n";
+
+	o << "String variables:\n";
+	for (uint i = 0; i < gs.svars.size(); i ++)
+		o << "    " << i << ": " << gs.svars[i] << "\n";
+
+	o << "integer variables:\n";
+	for (uint i = 0; i < gs.svars.size(); i ++)
+		o << "    " << i << ": " << gs.svars[i] << "\n";
+
+	return o;
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/geas_state.h b/engines/glk/quest/geas_state.h
new file mode 100644
index 0000000..70365f1
--- /dev/null
+++ b/engines/glk/quest/geas_state.h
@@ -0,0 +1,171 @@
+/* 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_QUEST_GEAS_STATE
+#define GLK_QUEST_GEAS_STATE
+
+#include "glk/quest/string.h"
+#include "common/array.h"
+#include "common/stream.h"
+
+namespace Glk {
+namespace Quest {
+
+struct PropertyRecord {
+	String name, data;
+	PropertyRecord(String in_name, String in_data) : name(in_name), data(in_data) {}
+};
+
+struct ObjectRecord {
+	String name, parent;
+	bool hidden, invisible;
+
+	//ObjectRecord (String in_name, String in_parent) : name (in_name), parent (in_parent), hidden (false), concealed (false) {}
+
+};
+
+struct ExitRecord {
+	String src, dest;
+	ExitRecord(String in_src, String in_dest) : src(in_src), dest(in_dest) {}
+};
+
+struct TimerRecord {
+	String name;
+	bool is_running;
+	uint interval, timeleft;
+};
+
+struct SVarRecord {
+private:
+	Common::Array<String> data;
+public:
+	String name;
+
+	SVarRecord() {}
+	SVarRecord(String in_name) : name(in_name) {
+		set(0, "");
+	}
+	uint size() const {
+		return data.size();
+	}
+	uint max() const {
+		return size() - 1;
+	}
+	void set(uint i, String val) {
+		if (i >= size()) data.resize(i + 1);
+		data[i] = val;
+	}
+	String get(uint i) const {
+		if (i < size()) return data[i];
+		return "!";
+	}
+	void set(String val) {
+		data[0] = val;
+	}
+	String get() const {
+		return data[0];
+	}
+};
+
+struct IVarRecord {
+private:
+	Common::Array<int> data;
+public:
+	String name;
+
+	IVarRecord() {}
+	IVarRecord(String in_name) : name(in_name) {
+		set(0, 0);
+	}
+	uint size() const {
+		return data.size();
+	}
+	uint max() const {
+		return size() - 1;
+	}
+	void set(uint i, int val) {
+		if (i >= size()) data.resize(i + 1);
+		data[i] = val;
+	}
+	int get(uint i) const {
+		if (i < size()) return data[i];
+		else return -32767;
+	}
+	void set(int val) {
+		data[0] = val;
+	}
+	int get() const {
+		return data[0];
+	}
+};
+
+struct GeasFile;
+class GeasInterface;
+
+struct GeasState {
+	//private:
+	//std::auto_ptr<GeasFile> gf;
+
+public:
+	bool running;
+	String location;
+	Common::Array<PropertyRecord> props;
+	Common::Array<ObjectRecord> objs;
+	Common::Array<ExitRecord> exits;
+	Common::Array<TimerRecord> timers;
+	Common::Array<SVarRecord> svars;
+	Common::Array<IVarRecord> ivars;
+	//std::map <String, String> obj_types;
+
+	//void register_block (String blockname, String blocktype);
+
+	GeasState() {}
+	//GeasState (GeasRunner &, const GeasFile &);
+	GeasState(GeasInterface &, const GeasFile &);
+	/*
+	bool has_svar (string s) { for (uint i = 0; i < svars.size(); i ++) if (svars[i].name == s) return true; }
+	uint find_svar (string s) { for (uint i = 0; i < svars.size(); i ++) if (svars[i].name == s) return i; svars.push_back (SVarRecord (s)); return svars.size() - 1;}
+	string get_svar (string s, uint index) { if (!has_svar(s)) return "!"; return svars[find_svar(s)].get(index); }
+	string get_svar (string s) { return get_svar (s, 0); }
+
+	bool has_ivar (string s) { for (uint i = 0; i < ivars.size(); i ++) if (ivars[i].name == s) return true; }
+	uint find_ivar (string s) { for (uint i = 0; i < ivars.size(); i ++) if (ivars[i].name == s) return i; ivars.push_back (IVarRecord (s)); return ivars.size() - 1;}
+	int get_ivar (string s, uint index) { if (!has_ivar(s)) return -32767; return ivars[find_ivar(s)].get(index); }
+	int get_ivar (string s) { return get_ivar (s, 0); }
+	*/
+};
+
+void save_game_to(String gamename, String savename, const GeasState &gs);
+
+Common::WriteStream &operator<< (Common::WriteStream &o, const StringMap &m);
+Common::WriteStream &operator<< (Common::WriteStream &o, const PropertyRecord &pr);
+Common::WriteStream &operator<< (Common::WriteStream &o, const ObjectRecord &objr);
+Common::WriteStream &operator<< (Common::WriteStream &o, const ExitRecord er);
+Common::WriteStream &operator<< (Common::WriteStream &o, const TimerRecord &tr);
+Common::WriteStream &operator<< (Common::WriteStream &o, const SVarRecord &sr);
+Common::WriteStream &operator<< (Common::WriteStream &o, const IVarRecord &ir);
+Common::WriteStream &operator<< (Common::WriteStream &o, const GeasState &gs);
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/geas_util.cpp b/engines/glk/quest/geas_util.cpp
new file mode 100644
index 0000000..bc5eaac
--- /dev/null
+++ b/engines/glk/quest/geas_util.cpp
@@ -0,0 +1,245 @@
+/* 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/quest/geas_util.h"
+#include "glk/quest/geas_file.h"
+#include "glk/quest/streams.h"
+#include "glk/quest/string.h"
+
+namespace Glk {
+namespace Quest {
+
+int eval_int(String s) {
+	cerr << "eval_int (" << s << ")" << endl;
+
+	uint index = 0, index2;
+	String tmp;
+	while (index < s.length() && Common::isSpace(s[index])) {
+		cerr << "  index == " << index << endl;
+		index ++;
+	}
+	if (index == s.length() || !Common::isDigit(s[index])) {
+		cerr << "Failed to match, returning 0" << endl;
+		return 0;
+	}
+	for (index2 = index; index2 < s.length() && Common::isDigit(s[index2]); index2 ++) {
+		cerr << "  index2 == " << index2 << endl;
+	}
+	//;
+	tmp = s.substr(index, index2 - index);
+	cerr << "tmp == < " << tmp << ">" << endl;
+
+	//cerr << "index == " << index << ", index2 == " << index2
+	//     << ", tmp == " << tmp << endl;
+
+	int arg1 = atoi(tmp.c_str());
+	cerr << "arg1 == " << arg1 << endl;
+	index = index2;
+	while (index < s.length() && Common::isSpace(s[index]))
+		++ index;
+	if (index == s.length())
+		return arg1;
+
+	//cerr << "index == " << index << ", s.length() == " << s.length() << endl;
+
+	char symbol = s[index];
+
+	//cerr << "symbol == " << symbol << "; find --> "
+	//     << String("+-*/").find (symbol) << endl;
+
+	if (String("+-*/").find(symbol) == String::npos)
+		return arg1;
+
+	++ index;
+	while (index < s.length() && Common::isSpace(s[index]))
+		++ index;
+	if (index == s.length() || ! Common::isDigit(s[index])) {
+		if (symbol == '*')
+			return 0;
+		return arg1;
+	}
+	index2 = index + 1;
+	while (index2 < s.length() && Common::isDigit(s[index2]))
+		++ index2;
+	tmp = s.substr(index, index2 - index);
+	int arg2 = atoi(tmp.c_str());
+
+	switch (symbol) {
+	case '+':
+		return arg1 + arg2;
+	case '-':
+		return arg1 - arg2;
+	case '*':
+		return arg1 * arg2;
+	case '/':
+		return arg1 / arg2;
+		// TODO: division should use accountant's round
+	}
+	return 0;
+}
+
+String trim_braces(String s) {
+	if (s.length() > 1 && s[0] == '[' && s[s.length() - 1] == ']')
+		return s.substr(1, s.length() - 2);
+	else
+		return s;
+}
+
+bool is_param(String s) {
+	return s.length() > 1 && s[0] == '<' && s[s.length() - 1] == '>';
+}
+
+String param_contents(String s) {
+	//cerr << "param_contents (" << s << ")" << endl;
+	assert(is_param(s));
+	return s.substr(1, s.length() - 2);
+}
+
+String nonparam(String type, String var) {
+	return "Non-parameter for " + type + " in \"" + var + "\"";
+}
+
+//ostream &operator << (ostream &o, const GeasBlock &gb) { return o; }
+//String trim (String s, trim_modes) { return s; }
+
+String string_geas_block(const GeasBlock &gb) {
+	ostringstream oss;
+	oss << gb;  // temporary removed TODO
+	return oss.str();
+}
+
+
+bool starts_with(String a, String b) {
+	return (a.length() >= b.length()) && (a.substr(0, b.length()) == b);
+}
+bool ends_with(String a, String b) {
+	return (a.length() >= b.length()) &&
+	       (a.substr(a.length() - b.length(), b.length()) == b);
+}
+
+bool starts_with_i(String a, String b) {
+	return (a.length() >= b.length()) && ci_equal(a.substr(0, b.length()), b);
+	//  return starts_with (lcase(a), lcase(b));
+}
+bool ends_with_i(String a, String b) {
+	return (a.length() >= b.length()) &&
+	       ci_equal(a.substr(a.length() - b.length(), b.length()), b);
+	//return ends_with (lcase(a), lcase(b));
+}
+
+String pcase(String s) {
+	if (s.length() == 0)
+		return s;
+	if (Common::isLower(s[0]))
+		s[0] = toupper(s[0]);
+	return s;
+}
+
+String ucase(String s) {
+	for (uint i = 0; i < s.length(); i ++)
+		s[i] = toupper(s[i]);
+	return s;
+}
+
+// There's a good chance s is already all-lowercase, in which case
+// the test will avoid making a copy
+String lcase(String s) {
+	for (uint i = 0; i < s.length(); i ++)
+		if (Common::isUpper(s[i]))
+			s[i] = tolower(s[i]);
+	return s;
+}
+
+Common::Array<String> split_param(String s) {
+	Common::Array<String> rv;
+	uint c1 = 0, c2;
+
+	for (;;) {
+		c2 = s.find(';', c1);
+		if (c2 == -1) {
+			rv.push_back(s.substr(c1).trim());
+			return rv;
+		}
+		rv.push_back(s.substr(c1, c2 - c1).trim());
+		c1 = c2 + 1;
+	}
+}
+
+Common::Array<String> split_f_args(String s) {
+	Common::Array<String> rv = split_param(s);
+	for (uint i = 0; i < rv.size(); i ++) {
+		String tmp = rv[i];
+		if (tmp[0] == '_')
+			rv[i][0] = ' ';
+		if (tmp[tmp.length() - 1] == '_')
+			rv[i][tmp.length() - 1] = ' ';
+	}
+	return rv;
+}
+
+void show_split(String s) {
+	Common::Array<String> tmp = split_param(s);
+	cerr << "Splitting <" << s << ">: ";
+	for (uint i = 0; i < tmp.size(); i ++)
+		cerr << "<" << tmp[i] << ">, ";
+	cerr << "\n";
+}
+
+Logger::Nullstreambuf Logger::cnull;
+
+Logger::Logger() : logfilestr_(NULL) { //, cerrbuf_(NULL) {
+/*
+	cerr.flush();
+
+	const char *const logfile = getenv("GEAS_LOGFILE");
+	if (logfile) {
+		ofstream *filestr = new ofstream(logfile);
+		if (filestr->fail())
+			delete filestr;
+		else {
+			logfilestr_ = filestr;
+			cerrbuf_ = cerr.rdbuf(filestr->rdbuf());
+		}
+	}
+
+	if (!cerrbuf_)
+		cerrbuf_ = cerr.rdbuf(&cnull);
+		*/
+}
+
+Logger::~Logger() {
+	/*
+	cerr.flush();
+
+	cerr.rdbuf(cerrbuf_);
+	cerrbuf_ = NULL;
+
+	if (logfilestr_) {
+		logfilestr_->close();
+		delete logfilestr_;
+		logfilestr_ = NULL;
+	}
+	*/
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/geas_util.h b/engines/glk/quest/geas_util.h
new file mode 100644
index 0000000..2cedadd
--- /dev/null
+++ b/engines/glk/quest/geas_util.h
@@ -0,0 +1,97 @@
+/* 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_QUEST_GEAS_UTIL
+#define GLK_QUEST_GEAS_UTIL
+
+#include "glk/quest/read_file.h"
+#include "common/stream.h"
+
+namespace Glk {
+namespace Quest {
+
+typedef Common::Array<String> vstring;
+
+inline int parse_int(const String &s) {
+	return atoi(s.c_str());
+}
+
+vstring split_param(String s);
+vstring split_f_args(String s);
+
+bool is_param(String s);
+String param_contents(String s);
+
+String nonparam(String, String);
+
+String string_geas_block(const GeasBlock &);
+
+bool starts_with(String, String);
+bool ends_with(String, String);
+
+String string_int(int i);
+
+String trim_braces(String s);
+
+int eval_int(String s);
+
+String pcase(String s);
+String ucase(String s);
+String lcase(String s);
+
+
+template<class T> Common::WriteStream &operator<<(Common::WriteStream &o, Common::Array<T> v) {
+	o << "{ '";
+	for (uint i = 0; i < v.size(); i ++) {
+		o << v[i];
+		if (i + 1 < v.size())
+			o << "', '";
+	}
+	o << "' }";
+	return o;
+}
+
+template <class KEYTYPE, class VALTYPE>
+bool has(Common::HashMap<KEYTYPE, VALTYPE, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> m, KEYTYPE key) {
+	return m.contains(key);
+};
+
+class Logger {
+public:
+	Logger();
+	~Logger();
+
+private:
+	class Nullstreambuf : public Common::WriteStream {
+		virtual uint32 write(const void *dataPtr, uint32 dataSize) { return dataSize; }
+		virtual int32 pos() const { return 0; }
+	};
+
+	Common::WriteStream *logfilestr_;
+//	std::streambuf *cerrbuf_;
+	static Nullstreambuf cnull;
+};
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/limit_stack.h b/engines/glk/quest/limit_stack.h
new file mode 100644
index 0000000..75cad44
--- /dev/null
+++ b/engines/glk/quest/limit_stack.h
@@ -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.
+ *
+ */
+
+#ifndef GLK_QUEST_LIMIT_STACK
+#define GLK_QUEST_LIMIT_STACK
+
+namespace Glk {
+namespace Quest {
+
+template <class T> class LimitStack {
+	uint stack_size, cur_ptr, end_ptr;
+	Common::Array<T> data;
+	//bool last_push;
+
+
+	uint dofwd(uint i) const {
+		i ++;
+		return i == stack_size ? 0 : i;
+	}
+	uint dobwd(uint i) const {
+		return (i == 0 ? stack_size : i) - 1;
+	}
+	void fwd(uint &i) const {
+		i = dofwd(i);
+	}
+	void bwd(uint &i) const {
+		i = dobwd(i);
+	}
+
+	/*
+	void fwd (uint &i) { i ++; if (i == stack_size) i = 0; }
+	void bwd (uint &i) { i = (i == 0 ? stack_size : i) - 1; }
+	uint dofwd (uint i) { uint rv = i; fwd(rv); return rv; }
+	uint dobwd (uint i) { uint rv = i; bwd(rv); return rv; }
+	*/
+
+public:
+	LimitStack(uint size) : stack_size(size), cur_ptr(0), end_ptr(size - 1), data(Common::Array<T> (size)) { }
+
+	void push(T &item) {
+		if (cur_ptr == end_ptr)
+			fwd(end_ptr);
+		data[cur_ptr] = item;
+		fwd(cur_ptr);
+	}
+
+	T &pop() {
+		assert(!is_empty());
+		bwd(cur_ptr);
+		return data[cur_ptr];
+	}
+
+	bool is_empty() {
+		return dobwd(cur_ptr) == end_ptr;
+	}
+
+	uint size() {
+		if (cur_ptr > end_ptr)
+			return cur_ptr - end_ptr - 1;
+		else
+			return (cur_ptr + stack_size) - end_ptr - 1;
+	}
+
+	void dump(Common::WriteStream &o) {
+		o << size() << ": < ";
+		for (uint i = dobwd(cur_ptr); i != end_ptr; bwd(i))
+			o << data[i] << " ";
+		o << ">";
+	}
+
+	T &peek() {
+		return data[dobwd(cur_ptr)];
+	}
+};
+
+template<class T> Common::WriteStream &operator<< (Common::WriteStream &o, LimitStack<T> st) {
+	st.dump(o);
+	return o;
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/quest.cpp b/engines/glk/quest/quest.cpp
new file mode 100644
index 0000000..93de43f
--- /dev/null
+++ b/engines/glk/quest/quest.cpp
@@ -0,0 +1,149 @@
+/* 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/quest/quest.h"
+#include "glk/quest/geas_glk.h"
+#include "glk/quest/geas_glk.h"
+#include "glk/quest/streams.h"
+#include "common/config-manager.h"
+#include "common/translation.h"
+
+namespace Glk {
+namespace Quest {
+
+Quest *g_vm;
+
+void Quest::runGame() {
+	// Check for savegame
+	_saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
+
+	if (!initialize()) {
+		GUIErrorMessage(_("Could not start Quest game"));
+		return;
+	}
+
+	playGame();
+
+	deinitialize();
+}
+
+void Quest::playGame() {
+	char cur_buf[1024];
+	char buf[200];
+
+	GeasRunner *gr = GeasRunner::get_runner(new GeasGlkInterface());
+	gr->set_game(String(getFilename().c_str()));
+	banner = gr->get_banner();
+	draw_banner();
+
+	while (gr->is_running()) {
+		if (inputwin != mainglkwin)
+			glk_window_clear(inputwin);
+		else
+			glk_put_cstring("\n");
+
+		sprintf(cur_buf, "> ");
+		glk_put_string_stream(inputwinstream, cur_buf);
+
+		glk_request_line_event(inputwin, buf, (sizeof buf) - 1, 0);
+
+		event_t ev;
+		ev.type = evtype_None;
+
+		while (ev.type != evtype_LineInput) {
+			glk_select(&ev);
+
+			switch (ev.type) {
+			case evtype_LineInput:
+				if (ev.window == inputwin) {
+					String cmd = String(buf, ev.val1);
+					if (inputwin == mainglkwin)
+						ignore_lines = 2;
+					gr->run_command(cmd);
+				}
+				break;
+
+			case evtype_Timer:
+				gr->tick_timers();
+				break;
+
+			case evtype_Arrange:
+			case evtype_Redraw:
+				draw_banner();
+				break;
+			}
+		}
+	}
+}
+
+bool Quest::initialize() {
+	glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_ReverseColor, 1);
+
+	// Open the main window
+	mainglkwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
+	if (!mainglkwin)
+		return false;
+	glk_set_window(mainglkwin);
+
+	glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
+	bannerwin = glk_window_open(mainglkwin,
+		winmethod_Above | winmethod_Fixed,
+		1, wintype_TextGrid, 0);
+
+	if (use_inputwindow)
+		inputwin = glk_window_open(mainglkwin,
+			winmethod_Below | winmethod_Fixed,
+			1, wintype_TextBuffer, 0);
+	else
+		inputwin = NULL;
+
+	if (!inputwin)
+		inputwin = mainglkwin;
+
+	inputwinstream = glk_window_get_stream(inputwin);
+
+	if (!glk_gestalt(gestalt_Timer, 0)) {
+		const char *err = "\nNote -- The underlying Glk library does not support"
+			" timers.  If this game tries to use timers, then some"
+			" functionality may not work correctly.\n\n";
+		glk_put_string(err);
+	}
+
+	glk_request_timer_events(1000);
+	ignore_lines = 0;
+
+	return true;
+}
+
+void Quest::deinitialize() {
+}
+
+Common::Error Quest::readSaveData(Common::SeekableReadStream *rs) {
+	return Common::kNoError;
+}
+
+Common::Error Quest::writeGameData(Common::WriteStream *ws) {
+	return Common::kNoError;
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/quest.h b/engines/glk/quest/quest.h
new file mode 100644
index 0000000..69aa2db
--- /dev/null
+++ b/engines/glk/quest/quest.h
@@ -0,0 +1,92 @@
+/* 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_QUEST_QUEST
+#define GLK_QUEST_QUEST
+
+#include "glk/glk_api.h"
+#include "glk/quest/string.h"
+
+namespace Glk {
+namespace Quest {
+
+/**
+ * Quest game interpreter
+ */
+class Quest : public GlkAPI {
+private:
+	int _saveSlot;
+public:
+	String banner;
+private:
+	/**
+	 * Engine initialization
+	 */
+	bool initialize();
+
+	/**
+	 * Engine cleanup
+	 */
+	void deinitialize();
+
+	/**
+	 * Inner gameplay method
+	 */
+	void playGame();
+public:
+	/**
+	 * Constructor
+	 */
+	Quest(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), _saveSlot(-1) {
+		g_vm = this;
+	}
+
+	/**
+	 * Run the game
+	 */
+	virtual void runGame() override;
+
+	/**
+	 * Returns the running interpreter type
+	 */
+	virtual InterpreterType getInterpreterType() const override {
+		return INTERPRETER_QUEST;
+	}
+
+	/**
+	 * Load a savegame from the passed Quetzal file chunk stream
+	 */
+	virtual Common::Error readSaveData(Common::SeekableReadStream *rs) override;
+
+	/**
+	 * Save the game. The passed write stream represents access to the UMem chunk
+	 * in the Quetzal save file that will be created
+	 */
+	virtual Common::Error writeGameData(Common::WriteStream *ws) override;
+};
+
+extern Quest *g_vm;
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/read_file.cpp b/engines/glk/quest/read_file.cpp
new file mode 100644
index 0000000..c749bb2
--- /dev/null
+++ b/engines/glk/quest/read_file.cpp
@@ -0,0 +1,1043 @@
+/* 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/quest/read_file.h"
+#include "glk/quest/geas_util.h"
+#include "glk/quest/reserved_words.h"
+#include "glk/quest/geas_runner.h"
+#include "glk/quest/streams.h"
+#include "common/util.h"
+
+namespace Glk {
+namespace Quest {
+
+String next_token(String full, uint &tok_start, uint &tok_end, bool cvt_paren) {
+	tok_start = tok_end;
+	while (tok_start < full.size() && Common::isSpace(full[tok_start]))
+		++ tok_start;
+	if (tok_start >= full.size()) {
+		tok_start = tok_end = full.size();
+		//tok_start = tok_end = String::npos;
+		return "";
+	}
+	tok_end = tok_start + 1;
+	if (full[tok_start] == '{' || full[tok_start] == '}')
+		/* brace is a token by itself */;
+	else if (full[tok_start] == '<') {
+		while (tok_end < full.size() && full [tok_end] != '>')
+			++ tok_end;
+		if (full[tok_end] == '>')
+			++ tok_end;
+	} else if (cvt_paren && full[tok_start] == '(') {
+		uint depth = 1;
+		/*
+		while (tok_end < full.size() && full [tok_end] != ')')
+		++ tok_end;
+		     if (full[tok_end] == ')')
+		++ tok_end;
+		     */
+		do {
+			if (full[tok_end] == '(')
+				++ depth;
+			else if (full[tok_end] == ')')
+				-- depth;
+			++ tok_end;
+		} while (tok_end < full.size() && depth > 0);
+	} else
+		while (tok_end < full.size() && !Common::isSpace(full[tok_end]))
+			++ tok_end;
+	return full.substr(tok_start, tok_end - tok_start);
+}
+
+String first_token(String s, uint &t_start, uint &t_end) {
+	t_end = 0;
+	return next_token(s, t_start, t_end);
+}
+
+String nth_token(String s, int n) {
+	uint x1, x2 = 0;
+	String rv;
+	do
+		rv = next_token(s, x1, x2);
+	while (-- n > 0);
+	return rv;
+}
+
+String get_token(String s, bool cvt_paren) {
+	uint x1, x2 = 0;
+	return next_token(s, x1, x2, cvt_paren);
+}
+
+bool find_token(String s, String tok, int &tok_start, int &tok_end, bool cvt_paren) {
+	uint copy_start, copy_end;
+	copy_end = tok_end;
+
+	do {
+		String tmp = next_token(s, copy_start, copy_end, cvt_paren);
+		if (tmp == tok) {
+			tok_start = copy_start;
+			tok_end = copy_end;
+			return true;
+		}
+	} while (copy_end < s.size());
+
+	return false;
+}
+
+bool is_define(String s) {
+	return get_token(s) == "define";
+}
+
+bool is_define(String s, String t) {
+	uint t1, t2 = 0;
+	return next_token(s, t1, t2) == "define" &&
+	       next_token(s, t1, t2) == t;
+}
+
+bool is_start_textmode(String s) {
+	uint start_char, end_char = 0;
+	if (next_token(s, start_char, end_char) != "define") return false;
+	String tmp = next_token(s, start_char, end_char);
+	// SENSITIVE?
+	return tmp == "text" || tmp == "synonyms";
+}
+
+bool is_end_define(String s) {
+	uint start_char, end_char = 0;
+	// SENSITIVE?
+	return (next_token(s, start_char, end_char) == "end" &&
+	        next_token(s, start_char, end_char) == "define");
+}
+
+
+extern Common::Array<String> split_lines(String data);
+
+reserved_words dir_tag_property("north", "south", "east", "west", "northwest", "northeast", "southeast", "southwest", "up", "down", "out", (char *) NULL);
+
+void GeasFile::read_into(const Common::Array<String> &in_data,
+                         String in_parent, uint cur_line, bool recurse,
+                         const reserved_words &props,
+                         const reserved_words &actions) {
+	//cerr << "r_i: Reading in from" << cur_line << ": " << in_data[cur_line] << endl;
+	//output.push_back (GeasBlock());
+	//GeasBlock &out_block = output[output.size() - 1];
+	int blocknum = blocks.size();
+	blocks.push_back(GeasBlock());
+	GeasBlock &out_block = blocks[blocknum];
+
+	Common::Array<String> &out_data = out_block.data;
+	out_block.parent = in_parent;
+	uint t1, t2;
+	String line = in_data[cur_line];
+	// SENSITIVE?
+	assert(first_token(line, t1, t2) == "define");
+	String blocktype = out_block.blocktype = next_token(line, t1, t2);  // "object", or the like
+	//cerr << "r_i: Pushing back block of type " << blocktype << "\n";
+	type_indecies[blocktype].push_back(blocknum);
+	String name = next_token(line, t1, t2);  // "<itemname>", or the like
+
+	// SENSITIVE?
+	if (blocktype == "game") {
+		out_block.name = "game";
+		out_data.push_back("game name " + name);
+	} else if (is_param(name))
+		out_block.name = param_contents(name);
+	else if (name != "")
+		error("Expected parameter; %s found instead.", name.c_str());
+	//out_block.lname = lcase (out_block.nname);
+
+	// apparently not all block types are unique ... TODO which?
+	// SENSITIVE?
+	if (blocktype == "room" || blocktype == "object" || blocktype == "game")
+		register_block(out_block.name, blocktype);
+	//register_block (out_block.lname, blocktype);
+
+	// SENSITIVE?
+	if (blocktype == "room" && find_by_name("type", "defaultroom"))
+		out_data.push_back("type <defaultroom>");
+	// SENSITIVE?
+	if (blocktype == "object" && find_by_name("type", "default"))
+		out_data.push_back("type <default>");
+
+	cur_line ++;
+	uint depth = 1;
+	while (cur_line < in_data.size() && depth > 0) {
+		line = in_data[cur_line];
+		if (recurse && is_define(line))
+			++ depth;
+		else if (is_end_define(in_data[cur_line]))
+			-- depth;
+		else if (depth == 1) {
+			//cerr << "r_i: Processing line #" << cur_line << ": " << line << endl;
+			//String dup_data = "";
+			String tok = first_token(line, t1, t2);
+			String rest = next_token(line, t1, t2);
+
+			//cerr << "r_i: tok == '" << tok << "', props[tok] == " << props[tok]
+			//     << ", actions[tok] == " << actions[tok] << "\n";
+
+			if (props[tok] && dir_tag_property[tok])
+				out_data.push_back(line);
+
+			if (props[tok] && rest == "") {
+				//cerr << "r_i: Handling as props <tok>\n";
+				line = "properties <" + tok + ">";
+			} else if (props[tok] && is_param(rest)) {
+				//cerr << "r_i: Handling as props <tok = ...>\n";
+				line = "properties <" + tok + "=" + param_contents(rest) + ">";
+			} else if (actions[tok] &&
+			           (tok == "use" || tok == "give" || !is_param(rest))) {
+				//cerr << "r_i: Handling as action '" << tok << "'\n";
+				// SENSITIVE?
+				if (tok == "use") {
+					//cerr << "r_i: ********** Use line: <" + line + "> ---> ";
+					String lhs = "action <use ";
+					// SENSITIVE?
+					if (rest == "on") {
+						rest = next_token(line, t1, t2);
+						String rhs = line.substr(t2);
+						// SENSITIVE?
+						if (rest == "anything")
+							line = lhs + "on anything> " + rhs;
+						// SENSITIVE?
+						else if (is_param(rest))
+							line = lhs + "on " + param_contents(rest) + "> " + rhs;
+						else {
+							//cerr << "r_i: Error handling '" << line << "'" << endl;
+							line = "ERROR: " + line;
+						}
+					}
+					// SENSITIVE?
+					else if (rest == "anything")
+						line = lhs + "anything> " + line.substr(t2);
+					else if (is_param(rest))
+						line = lhs + param_contents(rest) + "> " + line.substr(t2);
+					else
+						line = "action <use> " + line.substr(t1);
+					//cerr << "r_i: <" << line << ">\n";
+				}
+				// SENSITIVE?
+				else if (tok == "give") {
+					String lhs = "action <give ";
+					// SENSITIVE?
+					if (rest == "to") {
+						rest = next_token(line, t1, t2);
+						String rhs = line.substr(t2);
+						// SENSITIVE?
+						if (rest == "anything")
+							line = lhs + "to anything> " + rhs;
+						else if (is_param(rest))
+							line = lhs + "to " + param_contents(rest) + "> " + rhs;
+						else {
+							cerr << "Error handling '" << line << "'" << endl;
+							line = "ERROR: " + line;
+						}
+					}
+					// SENSITIVE?
+					else if (rest == "anything")
+						line = lhs + "anything> " + line.substr(t2);
+					else if (is_param(rest))
+						line = lhs + param_contents(rest) + "> " + line.substr(t2);
+					else
+						line = "action <give> " + line.substr(t1);
+				} else
+					line = "action <" + tok + "> " + line.substr(t1);
+			}
+			//else
+			//  cerr << "Handling as ordinary line\n";
+
+			// recalculating tok because it might have changed
+			/* TODO: Make sure this only happens on object-type blocks */
+			tok = first_token(line, t1, t2);
+			// SENSITIVE?
+			if (tok == "properties") {
+				rest = next_token(line, t1, t2);
+				if (is_param(rest)) {
+					vstring items = split_param(param_contents(rest));
+					for (uint i = 0; i < items.size(); i ++)
+						out_data.push_back("properties <" +
+						                   static_eval(items[i]) + ">");
+				} else
+					out_data.push_back("ERROR " + line);
+			} else
+				out_data.push_back(line);
+
+			//if (dup_data != "")
+			//  out_data.push_back (dup_data);
+		}
+		cur_line ++;
+	}
+}
+
+GeasFile::GeasFile(const Common::Array<String> &v, GeasInterface *_gi) : gi(_gi) {
+	uint depth = 0;
+
+	String parentname, parenttype;
+
+	static String pass_names[] = {
+		"game", "type", "room", "variable", "object", "procedure",
+		"function", "selection", "synonyms", "text", "timer"
+	};
+
+	reserved_words recursive_passes("game", "room", (char *) NULL),
+	               object_passes("game", "room", "objects", (char *) NULL);
+
+
+	//Common::Array <GeasBlock> outv;
+	for (uint pass = 0; pass < sizeof(pass_names) / sizeof(*pass_names);
+	        pass ++) {
+		String this_pass = pass_names[pass];
+		bool recursive = recursive_passes[this_pass];
+		//bool is_object = object_passes[this_pass];
+
+		reserved_words actions((char *) NULL), props((char *) NULL);
+		// SENSITIVE?
+		if (this_pass == "room") {
+			props = reserved_words("look", "alias", "prefix", "indescription", "description", "north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "out", "up", "down", (char *) NULL);
+			actions = reserved_words("description", "script", "north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "out", "up", "down", (char *) NULL);
+		}
+		// SENSITIVE?
+		else if (this_pass == "object") {
+			props = reserved_words("look", "examine", "speak", "take", "alias", "prefix", "suffix", "detail", "displaytype", "gender", "article", "hidden", "invisible", (char *) NULL);
+			actions = reserved_words("look", "examine", "speak", "take", "gain", "lose", "use", "give", (char *) NULL);
+		}
+
+		depth = 0;
+		for (uint i = 0; i < v.size(); i ++)
+			if (is_define(v[i])) {
+				++ depth;
+				String blocktype = nth_token(v[i], 2);
+				if (depth == 1) {
+					parenttype = blocktype;
+					parentname = nth_token(v[i], 3);
+
+					// SENSITIVE?
+					if (blocktype == this_pass)
+						read_into(v, "", i, recursive, props, actions);
+				} else if (depth == 2 && blocktype == this_pass) {
+					// SENSITIVE?
+					if (this_pass == "object" && parenttype == "room")
+						read_into(v, parentname, i, false, props, actions);
+					// SENSITIVE?
+					else if (this_pass == "variable" && parenttype == "game")
+						read_into(v, "", i, false, props, actions);
+				}
+			} else if (is_end_define(v[i]))
+				-- depth;
+	}
+
+}
+
+bool decompile(String data, Common::Array<String> &rv);
+
+bool preprocess(Common::Array<String> v, String fname, Common::Array<String> &rv, GeasInterface *gi);
+
+GeasFile read_geas_file(GeasInterface *gi, const String &filename) {
+	//return GeasFile (split_lines(gi->get_file(s)), gi);
+	String file_contents = gi->get_file(filename);
+
+	if (file_contents == "")
+		return GeasFile();
+
+	Common::Array<String> data;
+	bool success;
+
+	cerr << "Header is '" << file_contents.substr(0, 7) << "'.\n";
+	if (file_contents.size() > 8 && file_contents.substr(0, 7) == "QCGF002") {
+		cerr << "Decompiling\n";
+		success = decompile(file_contents, data);
+	} else {
+		cerr << "Preprocessing\n";
+		success = preprocess(split_lines(file_contents), filename, data, gi);
+	}
+
+	cerr << "File load was " << (success ? "success" : "failure") << "\n";
+
+	if (success)
+		return GeasFile(data, gi);
+
+	gi->debug_print("Unable to read file " + filename);
+	return GeasFile();
+}
+
+Common::WriteStream &operator<<(Common::WriteStream &o, const GeasBlock &gb) {
+	//o << "Block " << gb.blocktype << " '" << gb.nname;
+	o << "Block " << gb.blocktype << " '" << gb.name;
+	if (gb.parent != "")
+		o << "' and parent '" << gb.parent;
+	o << "'\n";
+	for (uint i = 0; i < gb.data.size(); i ++)
+		o << "    " << gb.data[i] << "\n";
+	o << "\n";
+	return o;
+}
+
+void print_vblock(Common::WriteStream &o, String blockname, const Common::Array<GeasBlock> &blocks) {
+	o << blockname << ":\n";
+	for (uint i = 0; i < blocks.size(); i ++)
+		o << "  " << blocks[i] << "\n";
+	o << "\n";
+}
+
+Common::WriteStream &operator<<(Common::WriteStream &o, const GeasFile &gf) {
+	/*
+	o << "Geas File\nThing-type blocks:\n";
+	for (uint i = 0; i < gf.things.size(); i ++)
+	  o << gf.things[i];
+	o << "\nOther-type blocks:\n";
+	for (uint i = 0; i < gf.others.size(); i ++)
+	  o << gf.others[i];
+	*/
+	o << "Geas File\n";
+	for (StringArrayIntMap::const_iterator i = gf.type_indecies.begin(); i != gf.type_indecies.end(); i ++) {
+		o << "Blocks of type " << (*i)._key << "\n";
+		for (uint j = 0; j < (*i)._value.size(); j ++)
+			o << gf.blocks[(*i)._value[j]];
+		o << "\n";
+	}
+
+	/*
+	o << "Geas File\n";
+	print_vblock (o, "game",        gf.game);
+	print_vblock (o, "rooms",       gf.rooms);
+	print_vblock (o, "objects",     gf.objects);
+	print_vblock (o, "text blocks", gf.textblocks);
+	print_vblock (o, "functions",   gf.functions);
+	print_vblock (o, "procedures",  gf.procedures);
+	print_vblock (o, "types",       gf.types);
+	print_vblock (o, "synonyms",    gf.synonyms);
+	print_vblock (o, "timers",      gf.timers);
+	print_vblock (o, "variables",   gf.variables);
+	print_vblock (o, "choices",     gf.choices);
+	*/
+	o << endl;
+	return o;
+}
+
+
+
+
+
+String compilation_tokens[256] = {
+	"", "game", "procedure", "room", "object", "character", "text", "selection",
+	"define", "end", "", "asl-version", "game", "version", "author", "copyright",
+	"info", "start", "possitems", "startitems", "prefix", "look", "out", "gender",
+	"speak", "take", "alias", "place", "east", "north", "west", "south", "give",
+	"hideobject", "hidechar", "showobject", "showchar", "collectable",
+	"collecatbles", "command", "use", "hidden", "script", "font", "default",
+	"fontname", "fontsize", "startscript", "nointro", "indescription",
+	"description", "function", "setvar", "for", "error", "synonyms", "beforeturn",
+	"afterturn", "invisible", "nodebug", "suffix", "startin", "northeast",
+	"northwest", "southeast", "southwest", "items", "examine", "detail", "drop",
+	"everywhere", "nowhere", "on", "anything", "article", "gain", "properties",
+	"type", "action", "displaytype", "override", "enabled", "disabled",
+	"variable", "value", "display", "nozero", "onchange", "timer", "alt", "lib",
+	"up", "down", "gametype", "singleplayer", "multiplayer", "", "", "", "", "",
+	"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+	"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+	"", "", "", "", "", "", "", "", "", "", "", "do", "if", "got", "then", "else",
+	"has", "say", "playwav", "lose", "msg", "not", "playerlose", "playerwin",
+	"ask", "goto", "set", "show", "choice", "choose", "is", "setstring",
+	"displaytext", "exec", "pause", "clear", "debug", "enter", "movechar",
+	"moveobject", "revealchar", "revealobject", "concealchar", "concealobject",
+	"mailto", "and", "or", "outputoff", "outputon", "here", "playmidi", "drop",
+	"helpmsg", "helpdisplaytext", "helpclear", "helpclose", "hide", "show",
+	"move", "conceal", "reveal", "numeric", "String", "collectable", "property",
+	"create", "exit", "doaction", "close", "each", "in", "repeat", "while",
+	"until", "timeron", "timeroff", "stop", "panes", "on", "off", "return",
+	"playmod", "modvolume", "clone", "shellexe", "background", "foreground",
+	"wait", "picture", "nospeak", "animate", "persist", "inc", "dec", "flag",
+	"dontprocess", "destroy", "beforesave", "onload", "", "", "", "", "", "",
+	"", "", "", "", "", "", "", "", "", "", "", ""
+};
+
+
+bool decompile(String s, Common::Array<String> &rv) {
+	String cur_line, tok;
+	uint expect_text = 0, obfus = 0;
+	unsigned char ch;
+
+	for (uint i = 8; i < s.size(); i ++) {
+		ch = s[i];
+		if (obfus == 1 && ch == 0) {
+			cur_line += "> ";
+			obfus = 0;
+		} else if (obfus == 1)
+			cur_line += char (255 - ch);
+		else if (obfus == 2 && ch == 254) {
+			obfus = 0;
+			cur_line += " ";
+		} else if (obfus == 2)
+			cur_line += ch;
+		else if (expect_text == 2) {
+			if (ch == 253) {
+				expect_text = 0;
+				rv.push_back(cur_line);
+				cur_line = "";
+			} else if (ch == 0) {
+				rv.push_back(cur_line);
+				cur_line = "";
+			} else
+				cur_line += char (255 - ch);
+		} else if (obfus == 0 && ch == 10) {
+			cur_line += "<";
+			obfus = 1;
+		} else if (obfus == 0 && ch == 254)
+			obfus = 2;
+		else if (ch == 255) {
+			if (expect_text == 1)
+				expect_text = 2;
+			rv.push_back(cur_line);
+			cur_line = "";
+		} else {
+			tok = compilation_tokens[ch];
+			if ((tok == "text" || tok == "synonyms" || tok == "type") &&
+			        cur_line == "define ")
+				expect_text = 1;
+			cur_line += tok + " ";
+		}
+	}
+	rv.push_back(cur_line);
+
+	for (uint i = 0; i < rv.size(); i ++)
+		cerr << "rv[" << i << "]: " << rv[i] << "\n";
+
+	return true;
+}
+
+
+
+
+
+
+
+
+Common::Array<String> tokenize(String s) {
+	uint tok_start, tok_end;
+	String tok;
+	Common::Array<String> rv;
+	tok_end = 0;
+	while (tok_end + 1 <= s.size())
+		rv.push_back(next_token(s, tok_start, tok_end));
+	return rv;
+}
+
+String string_int(int i) {
+	ostringstream o;
+	o << i;
+	return o.str();
+}
+
+void report_error(String s) {
+	//cerr << s << endl;
+	cerr << s << endl;
+	throw s;
+}
+
+Common::Array<String> split_lines(String data) {
+	Common::Array <String> rv;
+	String tmp;
+	uint i = 0;
+	while (i < data.size()) {
+		//cerr << "data[" << i << "] == " << int(data[i]) << '\n';
+
+		if (data[i] == '\n' || data[i] == '\r') {
+			/*
+			if (data[i] == '\n' && i < data.size() && data[i+1] == '\r')
+			  ++ i;
+			*/
+			if (tmp.size() > 0 && tmp[tmp.size() - 1] == '_') {
+				//cerr << "Line with trailing underscores: " << tmp << '\n';
+				tmp.erase(tmp.size() - 1);
+				if (tmp[tmp.size() - 1] == '_')
+					tmp.erase(tmp.size() - 1);
+				if (i < data.size() && data[i] == '\r' && data[i + 1] == '\n')
+					++ i;
+				++ i;
+				//cerr << "   WSK: data[" << i<< "] == " << int(data[i]) << '\n';
+				while (i < data.size() && data[i] != '\r' &&
+				        data[i] != '\n' && Common::isSpace(data[i])) {
+					//cerr << "   WS: data[" << i<< "] = " << int(data[i]) << '\n';
+					++ i;
+				}
+				//cerr << "   WS: data[" << i<< "] == " << int(data[i]) << '\n';
+				-- i;
+			} else {
+				//cerr << "Pushing back {<{" << tmp << "}>}\n";
+				rv.push_back(tmp);
+				tmp = "";
+				if (i < data.size() && data[i] == '\r' && data[i + 1] == '\n')
+					++ i;
+			}
+		} else
+			tmp += data[i];
+		++ i;
+	}
+	if (tmp != "")
+		rv.push_back(tmp);
+	return rv;
+}
+
+void show_tokenize(String s) {
+	//cerr << "s_t: Tokenizing '" << s << "' --> " << tokenize(s) << endl;
+}
+
+void say_push(const Common::Array<String> &v) {
+	//cerr << "s_p: Pushing '" << v[v.size() - 1] << "'" << endl;
+}
+
+
+
+
+
+//enum trim_modes { TRIM_SPACES, TRIM_UNDERSCORE, TRIM_BRACE };
+
+//String trim (String s, trim_modes trim_mode = TRIM_SPACES)
+String trim(String s, trim_modes trim_mode) {
+	uint i, j;
+	/*
+	cerr << "Trimming (" << s << "): [";
+	for (i = 0; i < s.size(); i ++)
+	  cerr << int (s[i]) << "(" << s[i] << "), ";
+	cerr << "]\n";
+	*/
+	for (i = 0; i < s.size() && Common::isSpace(s[i]); i ++)
+		;
+	if (i == s.size()) return "";
+	if ((trim_mode == TRIM_UNDERSCORE   &&  s[i] == '_') ||
+	        (trim_mode == TRIM_BRACE        &&  s[i] == '['))
+		++ i;
+	if (i == s.size()) return "";
+	for (j = s.size() - 1; Common::isSpace(s[j]); j --)
+		;
+	if ((trim_mode == TRIM_UNDERSCORE && i < s.size() && s[j] == '_') ||
+	        (trim_mode == TRIM_BRACE && i < s.size()      && s[j] == ']'))
+		-- j;
+	return s.substr(i, j - i + 1);
+}
+
+/* bool is_balanced (String)
+ *
+ * Decides whether the String has balanced braces (and needn't be deinlined)
+ * - Track the nesting depth, starting at first lbrace to end of line
+ * - If it is ever at depth 0 before the end, it's balanced
+ * - Otherwise, it's unbalanced
+ */
+bool is_balanced(String str) {
+	uint index = str.find('{');
+	if (index == -1)
+		return true;
+	int depth;
+	for (depth = 1, index ++;  depth > 0 && index < str.size();  index ++)
+		if (str[index] == '{')
+			++ depth;
+		else if (str[index] == '}')
+			-- depth;
+	return depth == 0;
+}
+
+int count_depth(String str, int count) {
+	//cerr << "count_depth (" << str << ", " << count << ")" << endl;
+	uint index = 0;
+	if (count == 0)
+		index = str.find('{');
+	while (index < str.size()) {
+		if (str[index] == '{')
+			++ count;
+		else if (str[index] == '}')
+			-- count;
+		//cerr << "    After char #" << index << ", count is " << count << endl;
+		++ index;
+	}
+	//cerr << "returning " << count << endl;
+	return count;
+}
+
+void handle_includes(const Common::Array<String> &in_data, String filename, Common::Array<String> &out_data, GeasInterface *gi) {
+	String line, tok;
+	uint tok_start, tok_end;
+	for (uint ln = 0; ln < in_data.size(); ln ++) {
+		line = in_data[ln];
+		tok = first_token(line, tok_start, tok_end);
+		if (tok == "!include") {
+			tok = next_token(line, tok_start, tok_end);
+			if (!is_param(tok)) {
+				gi->debug_print("Expected parameter after !include");
+				continue;
+			}
+			//handle_includes (split_lines (gi->get_file (param_contents (tok))), out_data, gi);
+			String newname = gi->absolute_name(param_contents(tok), filename);
+			handle_includes(split_lines(gi->get_file(newname)), newname, out_data, gi);
+		} else if (tok == "!QDK") {
+			while (ln < in_data.size() &&
+			        first_token(in_data[ln], tok_start, tok_end) != "!end")
+				++ ln;
+		} else
+			out_data.push_back(line);
+	}
+}
+
+bool preprocess(Common::Array<String> v, String fname, Common::Array<String> &rv,
+                GeasInterface *gi) {
+	//cerr << "Before preprocessing:\n" << v << "\n\n" << endl;
+	/*
+	  cerr << "Before preprocessing:\n";
+	for (uint i = 0; i < v.size(); i ++)
+	  cerr << i << ": " << v[i] << "\n";
+	cerr << "\n\n";
+	*/
+
+	// TODO: Is it "!=" or "<>" or both, and if both, which has priority?
+	static String comps[][2] = {
+		{ "<>", "!=;" },  { "!=", "!=;" },  { "<=", "lt=;" },  { ">=", "gt=;" },
+		{ "<",  "lt;" },  { ">",  "gt;" },  { "=",  "" }
+	};
+
+	uint tok_start, tok_end;
+	String tok;
+
+	// preprocessing step 0:
+	// Loop through the lines.  Replace !include with appropriate text
+
+	Common::Array<String> v2;
+	handle_includes(v, fname, v2, gi);
+	v.clear();
+
+	StringArrayStringMap addtos;
+	for (uint line = 0; line < v2.size(); line ++) {
+		//cerr << "Line #" << line << ", looking for addtos: " << v2[line] << endl;
+		tok = first_token(v2[line], tok_start, tok_end);
+		if (tok == "!addto") {
+			tok = next_token(v2[line], tok_start, tok_end);
+			if (!(tok == "game" || tok == "synonyms" || tok == "type")) {
+				gi->debug_print("Error: Had addto for '" + tok + "'.");
+				continue;
+			}
+			if (tok == "type") {
+				String tmp = next_token(v2[line], tok_start, tok_end);
+				if (!(tmp == "<default>" || tmp == "<defaultroom>")) {
+					gi->debug_print("Error: Bad addto '" + v2[line] + "'\n");
+					continue;
+				}
+				tok = tok + " " + tmp;
+			}
+			++ line; // skip !addto
+			while (line < v2.size() &&
+			        first_token(v2[line], tok_start, tok_end) != "!end")
+				addtos[tok].push_back(v2[line ++]);
+		}
+	}
+	for (uint line = 0; line < v2.size(); line ++) {
+		tok = first_token(v2[line], tok_start, tok_end);
+		if (tok == "!addto") {
+			while (line < v2.size() &&
+			        first_token(v2[line], tok_start, tok_end) != "!end")
+				line ++;
+		} else {
+			v.push_back(v2[line]);
+			if (tok == "define") {
+				// TODO: What if there's a !addto for a block other than
+				// game, synonyms, default, defaultroom?
+				//
+				// Also, do the !addto'ed lines go at the front or end?
+				tok = next_token(v2[line], tok_start, tok_end);
+				if (tok == "type")
+					tok = tok + " " + next_token(v2[line], tok_start, tok_end);
+
+				if (addtos.find(tok) != addtos.end()) {
+					Common::Array<String> &lines = addtos[tok];
+					for (uint line2 = 0; line2 < lines.size(); line2 ++)
+						v.push_back(lines[line2]);
+					addtos.erase(tok);
+				}
+			}
+		}
+	}
+	//cerr << "Done looking for addtos" << endl;
+	v2.clear();
+
+	for (StringArrayStringMap::iterator i = addtos.begin(); i != addtos.end(); i ++) {
+		v.push_back("define " + i->_key);
+		for (uint j = 0; j < i->_value.size(); j ++)
+			v.push_back(i->_value[j]);
+		v.push_back("end define");
+	}
+	/*
+	if (addtos.find("<default>") != addtos.end())
+	  {
+	    Common::Array<String> &lines = addtos ["<default>"];
+	    v.push_back ("define type <default>");
+	    for (uint i = 0; i < lines.size(); i ++)
+	v.push_back (lines[i]);
+	    v.push_back ("end define");
+	  }
+
+	if (addtos.find("<defaultroom>") != addtos.end())
+	  {
+	    Common::Array<String> &lines = addtos ["<defaultroom>"];
+	    v.push_back ("define type <defaultroom>");
+	    for (uint i = 0; i < lines.size(); i ++)
+	v.push_back (lines[i]);
+	    v.push_back ("end define");
+	  }
+	*/
+	/*
+	cerr << "Done special-pushing <default> and <defaultroom>\n";
+	cerr << "After includes & addtos:\n";
+	for (uint i = 0; i < v.size(); i ++)
+	  cerr << i << ": " << v[i] << "\n";
+	cerr << "\n\n";
+	*/
+
+	// Preprocessing step 1:
+	// Loop through the lines.  Look for "if/and/or (.. <.. )" or the like
+	// If there is such a pair, convert the second to "if/and/or is <..;lt;..>"
+
+	for (uint line = 0; line < v.size(); line ++) {
+		tok_end = 0;
+		while (tok_end < v[line].size()) {
+			tok = next_token(v[line], tok_start, tok_end, false);
+			if (tok == "if" || tok == "repeat" || tok == "until" ||
+			        tok == "and" || tok == "or") {
+				tok = next_token(v[line], tok_start, tok_end, true);
+				//cerr << "Checking for comparison {" << tok << "}\n";
+				if (tok.size() > 2 && tok[0] == '(' &&
+				        tok [tok.size() - 1] == ')') {
+					//cerr << "   IT IS!\n";
+					tok = tok.substr(1, tok.size() - 2);
+					String str = v[line];
+					int cmp_start;
+					for (uint cmp = 0; cmp < ARRAYSIZE(comps); cmp ++)
+						if ((cmp_start = tok.find(comps[cmp][0])) != -1) {
+							uint cmp_end = cmp_start + comps[cmp][0].size();
+							//cerr << "Changed str from {" << str << "} to {";
+							str = str.substr(0, tok_start) +
+							      "is <" + trim(tok.substr(0, cmp_start)) + ";" +
+							      comps[cmp][1] +
+							      trim(tok.substr(cmp_end)) + ">" +
+							      str.substr(tok_end);
+							//cerr << str << "}\n";
+							cmp = ARRAYSIZE(comps);
+							v[line] = str;
+							tok_end = tok_start; // old value of tok_end invalid
+						}
+				}
+			}
+		}
+	}
+	//cerr << "Done with pass 1!" << endl;
+
+	// Pass 2:  Extract comments from non-text blocks
+	bool in_text_block = false;
+	for (uint line = 0; line < v.size(); line ++) {
+		//cerr << "Checking line '" << v[line] << "' for comments\n";
+		if (!in_text_block && is_start_textmode(v[line]))
+			in_text_block = true;
+		else if (in_text_block && is_end_define(v[line]))
+			in_text_block = false;
+		else if (!in_text_block) {
+			//cerr << "  checking...\n";
+			uint start_ch = 0, end_ch = 0;
+			while (start_ch < v[line].size())
+				if (next_token(v[line], start_ch, end_ch)[0] == '\'') {
+					v[line] = v[line].substr(0, start_ch);
+					//cerr << "  abbreviating to '" << v[line] << "'\n";
+					break;
+				}
+		}
+	}
+	//cerr << "Done with pass 2!" << endl;
+
+	/* There should be a pass 2.5: check that lbraces count equals
+	 * rbrace count, but I'm skipping that
+	 */
+
+	// Pass 3: Deinline braces
+	in_text_block = false;
+	int int_proc_count = 0;
+	for (uint line = 0; line < v.size(); line ++) {
+		//cerr << "Pass 3, line #" << line << endl;
+		String str = v[line];
+		if (!in_text_block && is_start_textmode(str))
+			in_text_block = true;
+		else if (in_text_block && is_end_define(str))
+			in_text_block = false;
+		else if (!is_balanced(str)) {
+			//cerr << "...Special line!" << endl;
+			uint init_size = v.size();
+			v.push_back("define procedure <!intproc" +
+			            string_int(++int_proc_count) + ">");
+			//cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl;
+
+
+			uint tmp_index = str.find('{');
+			v2.push_back(trim(str.substr(0, tmp_index)) +
+			             " do <!intproc" + string_int(int_proc_count) + "> ");
+			//cerr << "Done with '" << v2[v2.size()-1] << "'" << endl;
+
+			{
+				/*
+				String tmp_str = trim (str.substr (tmp_index + 1));
+				if (tmp_str != "")
+				  {
+				v.push_back (tmp_str);
+				cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl;
+				  }
+				*/
+				v.push_back(str.substr(tmp_index + 1));
+				//cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl;
+			}
+
+			int count = count_depth(str, 0);
+			while (++ line < init_size && count != 0) {
+				str = v[line];
+				count = count_depth(str, count);
+				if (count != 0) {
+					/*
+					str = trim(str);
+					if (str != "")
+					  {
+					    v.push_back (str);
+					    cerr << "Pushing back on v: '" << str << "'" << endl;
+					  }
+					*/
+					v.push_back(str);
+					//cerr << "Pushing back on v: '" << str << "'" << endl;
+				}
+			}
+			if (count != 0) {
+				report_error("Braces Unbalanced");
+				return false;
+			}
+			tmp_index = str.rfind('}');
+			{
+				/*
+				String tmp2 = trim (str.substr (0, tmp_index));
+				if (tmp2 != "")
+				  v.push_back (tmp2);
+				*/
+				v.push_back(str.substr(0, tmp_index));
+				//cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl;
+			}
+
+			v.push_back("end define");
+			//cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl;
+			//cerr << "Done with special line" << endl;
+			line --;
+			continue;
+			// The continue is to avoid the v2.push_back(...);
+			// this block pushes stuff onto v2 on its own.
+		}
+		//cerr << "Done with '" << str << "'" << endl;
+		v2.push_back(str);
+	}
+	//cerr << "Done with pass 3!" << endl;
+
+	/* Pass 4:  trim lines, drop blank lines, combine elses */
+
+	in_text_block = false;
+	int_proc_count = 0;
+	for (uint line = 0; line < v2.size(); line ++) {
+		String str = v2[line];
+		//cerr << "Pass 4, line #" << line << ", " << in_text_block << ": '" << str << "'\n";
+		if (!in_text_block && is_start_textmode(str))
+			in_text_block = true;
+		else if (in_text_block && is_end_define(str))
+			in_text_block = false;
+		else if (rv.size() > 0 && !in_text_block && get_token(str) == "else") {
+			rv[rv.size() - 1] = rv[rv.size() - 1] + " " + trim(str);
+			//cerr << "  Replacing else: " << rv[rv.size() - 1] << "\n";
+			continue;
+		}
+		if (!in_text_block)
+			str = trim(str);
+		if (in_text_block || str != "")
+			rv.push_back(str);
+		//if (rv.size() > 0)
+		//  cerr << "  Result: " << rv[rv.size() - 1] << "\n";
+	}
+
+	/*
+	cerr << "At end of procedure, v == " << v << "\n";
+	cerr << "and v2 == " << v2 << "\n";
+	cerr << "and rv == " << rv << "\n";
+	*/
+	//rv = v2;
+
+	/*
+	cerr << "After all preprocessing\n";
+	for (uint i = 0; i < rv.size(); i ++)
+	  cerr << i << ": " << rv[i] << "\n";
+	cerr << "\n\n";
+	*/
+
+	return true;
+	//return v2;
+}
+
+
+void show_find(String s, char ch) {
+	cerr << "Finding '" << ch << "' in '" << s << "': " << s.find(ch) + 1 << endl;
+}
+
+void show_trim(String s) {
+	cerr << "Trimming '" << s << "': spaces (" << trim(s)
+	     << "), underscores (" << trim(s, TRIM_UNDERSCORE)
+	     << "), braces (" << trim(s, TRIM_BRACE) << ").\n";
+
+	//cerr << "Trimming '" << s << "': '" << trim (s) << "'\n";
+}
+
+template<class T> Common::Array<T> &operator<< (Common::Array<T> &v, T val) {
+	v.push_back(val);
+	return v;
+}
+
+template<class T> class makevector {
+	Common::Array<T> dat;
+public:
+	makevector<T> &operator<<(T it) {
+		dat.push_back(it);
+		return *this;
+	}
+	operator Common::Array<T>() {
+		return dat;
+	}
+};
+
+Common::Array <String> split(String s, char ch) {
+	uint i = 0, j;
+	Common::Array<String> rv;
+	do {
+		//cerr << "split (" << s << "): i == " << i << ", j == " << j << endl;
+		j = s.find(ch, i);
+		if (i != j)
+			rv.push_back(s.substr(i, j - i));
+		i = j + 1;
+	} while (j < s.size());
+
+	//cerr << rv << endl;
+	return rv;
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/read_file.h b/engines/glk/quest/read_file.h
new file mode 100644
index 0000000..062234d
--- /dev/null
+++ b/engines/glk/quest/read_file.h
@@ -0,0 +1,49 @@
+/* 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_QUEST_READ_FILE
+#define GLK_QUEST_READ_FILE
+
+#include "glk/quest/geas_file.h"
+#include "glk/quest/string.h"
+#include "common/array.h"
+
+namespace Glk {
+namespace Quest {
+
+extern Common::Array<String> tokenize(String s);
+extern String next_token(String full, uint &tok_start, uint &tok_end, bool cvt_paren = false);
+extern String first_token(String s, uint &t_start, uint &t_end);
+extern String nth_token(String s, int n);
+extern String get_token(String s, bool cvt_paren = false);
+extern bool find_token(String s, String tok, int &tok_start, int &tok_end, bool cvt_paren = false);
+extern GeasFile read_geas_file(GeasInterface *gi, const String &filename);
+
+enum trim_modes { TRIM_SPACES, TRIM_UNDERSCORE, TRIM_BRACE };
+extern String trim(String, trim_modes mode = TRIM_SPACES);
+
+//Common::WriteStream &operator<< (Common::WriteStream &o, const Common::Array<String> &v);
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/reserved_words.h b/engines/glk/quest/reserved_words.h
new file mode 100644
index 0000000..5d73607
--- /dev/null
+++ b/engines/glk/quest/reserved_words.h
@@ -0,0 +1,84 @@
+/* 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_QUEST_RESERVED_WORDS
+#define GLK_QUEST_RESERVED_WORDS
+
+#include "glk/quest/string.h"
+#include "common/hashmap.h"
+#include "common/stream.h"
+
+namespace Glk {
+namespace Quest {
+
+class reserved_words {
+private:
+	StringBoolMap _data;
+
+public:
+	/**
+	 * Constructor
+	 */
+	reserved_words(const char *c, ...) {
+		va_list ap;
+		va_start(ap, c);
+
+		while (c != nullptr) {
+			_data[String(c)] = true;
+			c = va_arg(ap, const char *);
+		}
+	}
+
+	/**
+	 * Returns true if the passed string is a reserved word
+	 */
+	bool operator[](const String &s) const {
+		return has(s);
+	}
+
+	/**
+	 * Returns true if the passed string is a reserved word
+	 */
+	bool has(const String &s) const {
+		return _data.contains(s) && (*this)[s];
+	}
+
+	/**
+	 * Dumps the list of reserved words to the passed output stream
+	 */
+	void dump(Common::WriteStream &o) const {
+		o.writeString("RW {");
+
+		for (StringBoolMap::iterator i = _data.begin(); i != _data.end(); ++i) {
+			if (i != _data.begin())
+				o.writeString(", ");
+			o.writeString((*i)._key);
+		}
+
+		o.writeString("}");
+	}
+};
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/streams.cpp b/engines/glk/quest/streams.cpp
new file mode 100644
index 0000000..37ea042
--- /dev/null
+++ b/engines/glk/quest/streams.cpp
@@ -0,0 +1,78 @@
+/* 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/quest/streams.h"
+#include "common/debug.h"
+#include "common/str.h"
+
+namespace Glk {
+namespace Quest {
+
+ConsoleStream *g_cerr;
+const char endl = '\n';
+
+void Streams::initialize() {
+	g_cerr = new ConsoleStream();
+}
+
+void Streams::deinitialize() {
+	delete g_cerr;
+}
+
+uint32 ConsoleStream::write(const void *dataPtr, uint32 dataSize) {
+	Common::String s((const char *)dataPtr, (const char *)dataPtr + dataSize);
+	debug("%s", s.c_str());
+	return dataSize;
+}
+
+/*--------------------------------------------------------------------------*/
+
+String ostringstream::str() {
+	return String((const char *)getData(), (const char *)getData() + size());
+}
+
+/*--------------------------------------------------------------------------*/
+
+Common::WriteStream &operator<<(Common::WriteStream &ws, const String &s) {
+	ws.writeString(s);
+	return ws;
+}
+
+Common::WriteStream &operator<<(Common::WriteStream &ws, char c) {
+	ws.writeByte(c);
+	return ws;
+}
+
+Common::WriteStream &operator<<(Common::WriteStream &ws, int i) {
+	Common::String s = Common::String::format("%d", i);
+	ws.writeString(s);
+	return ws;
+}
+
+Common::WriteStream &operator<<(Common::WriteStream &ws, uint i) {
+	Common::String s = Common::String::format("%u", i);
+	ws.writeString(s);
+	return ws;
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/streams.h b/engines/glk/quest/streams.h
new file mode 100644
index 0000000..3a8d2a4
--- /dev/null
+++ b/engines/glk/quest/streams.h
@@ -0,0 +1,85 @@
+/* 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_QUEST_STREAMS
+#define GLK_QUEST_STREAMS
+
+#include "glk/quest/string.h"
+#include "common/memstream.h"
+#include "common/stream.h"
+
+namespace Glk {
+namespace Quest {
+
+/**
+ * Write stream wrapper around ScummVM debug calls. Can only handle text being written
+ */
+class ConsoleStream : public Common::WriteStream {
+public:
+	virtual uint32 write(const void *dataPtr, uint32 dataSize) override;
+	virtual int32 pos() const override { return 0; }
+};
+
+class ostringstream : public Common::MemoryWriteStreamDynamic {
+public:
+	ostringstream() : Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES) {}
+
+	String str();
+};
+class StringStream : public ostringstream {
+public:
+	StringStream &operator>>(String &rhs) {
+		rhs = str();
+		return *this;
+	}
+};
+
+/**
+ * Simple wrapper for managing streams initialization
+ */
+class Streams {
+public:
+	/**
+	 * Initialization
+	 */
+	static void initialize();
+
+	/**
+	 * Deinitialization
+	 */
+	static void deinitialize();
+};
+
+extern ConsoleStream *g_cerr;
+extern const char endl;
+
+#define cerr (*g_cerr)
+
+Common::WriteStream &operator<<(Common::WriteStream &, const String &);
+Common::WriteStream &operator<<(Common::WriteStream &, char);
+Common::WriteStream &operator<<(Common::WriteStream &, int);
+Common::WriteStream &operator<<(Common::WriteStream &, uint);
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/string.cpp b/engines/glk/quest/string.cpp
new file mode 100644
index 0000000..d6a1784
--- /dev/null
+++ b/engines/glk/quest/string.cpp
@@ -0,0 +1,99 @@
+/* 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/quest/string.h"
+
+namespace Glk {
+namespace Quest {
+
+CI_EQUAL   ci_equal_obj;
+CI_LESS    ci_less_obj;
+CI_LESS_EQ ci_less_eq_obj;
+
+String operator+(const String &x, const String &y) {
+	String temp(x);
+	temp += y;
+	return temp;
+}
+
+String operator+(const char *x, const String &y) {
+	String temp(x);
+	temp += y;
+	return temp;
+}
+
+String operator+(const String &x, const char *y) {
+	String temp(x);
+	temp += y;
+	return temp;
+}
+
+String operator+(char x, const String &y) {
+	String temp(x);
+	temp += y;
+	return temp;
+}
+
+String operator+(const String &x, char y) {
+	String temp(x);
+	temp += y;
+	return temp;
+}
+
+/*--------------------------------------------------------------------------*/
+
+// Code for testing case insensitively by John Harrison
+
+bool c_equal_i(char ch1, char ch2) {
+	return tolower((unsigned char)ch1) == tolower((unsigned char)ch2);
+}
+
+size_t ci_find(const String &str1, const String &str2) {
+	const char *pos = strstr(str1.c_str(), str2.c_str());
+	return !pos ? String::npos : pos - str1.c_str();
+}
+
+static int my_stricmp(const String &s1, const String &s2) {
+	return s1.compareToIgnoreCase(s2);
+}
+
+bool ci_equal(const String &str1, const String &str2) {
+	return my_stricmp(str1, str2) == 0;
+}
+bool ci_less_eq(const String &str1, const String &str2) {
+	return my_stricmp(str1, str2) <= 0;
+}
+bool ci_less(const String &str1, const String &str2) {
+	return my_stricmp(str1, str2) < 0;
+}
+bool ci_notequal(const String &str1, const String &str2) {
+	return !ci_equal(str1, str2);
+}
+bool ci_gt_eq(const String &str1, const String &str2) {
+	return my_stricmp(str1, str2) >= 0;
+}
+bool ci_gt(const String &str1, const String &str2) {
+	return my_stricmp(str1, str2) > 0;
+}
+
+} // End of namespace Quest
+} // End of namespace Glk
diff --git a/engines/glk/quest/string.h b/engines/glk/quest/string.h
new file mode 100644
index 0000000..43f6ab6
--- /dev/null
+++ b/engines/glk/quest/string.h
@@ -0,0 +1,134 @@
+/* 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_QUEST_STRING
+#define GLK_QUEST_STRING
+
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "common/str.h"
+
+namespace Glk {
+namespace Quest {
+
+class String;
+
+typedef Common::HashMap<String, String, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringMap;
+typedef Common::HashMap<String, bool, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringBoolMap;
+typedef Common::HashMap<String, Common::Array<int>, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringArrayIntMap;
+typedef Common::HashMap<String, Common::Array<String>, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringArrayStringMap;
+
+class String : public Common::String {
+public:
+	String() : Common::String() {}
+	String(const char *str) : Common::String(str) {}
+	String(const char *str, uint32 len) : Common::String(str, len) {}
+	String(const char *beginP, const char *endP) : Common::String(beginP, endP) {}
+	String(const String &str) : Common::String(str) {}
+	explicit String(char c) : Common::String(c) {}
+
+	char &operator[](int idx) {
+		assert(_str && idx >= 0 && idx < (int)_size);
+		return _str[idx];
+	}
+
+	inline uint length() const {
+		return size();
+	}
+
+	String substr(size_t pos, size_t len) const {
+		return String(c_str() + pos, c_str() + pos + len);
+	}
+	String substr(size_t pos) const {
+		return String(c_str() + pos);
+	}
+
+	int find(char c, int pos = 0) const {
+		const char *p = strchr(c_str() + pos, c);
+		return p ? p - c_str() : -1;
+	}
+
+	int find(const Common::String &s, int pos = 0) const {
+		const char *p = strstr(c_str() + pos, s.c_str());
+		return p ? p - c_str() : -1;
+	}
+
+	int rfind(char c) const {
+		const char *p = strrchr(c_str(), c);
+		return p ? p - c_str() : -1;
+	}
+
+	String trim() const {
+		String result = *this;
+		static_cast<Common::String>(result).trim();
+		return result;
+	}
+};
+
+// Append two strings to form a new (temp) string
+String operator+(const String &x, const String &y);
+
+String operator+(const char *x, const String &y);
+String operator+(const String &x, const char *y);
+
+String operator+(const String &x, char y);
+String operator+(char x, const String &y);
+
+
+bool c_equal_i(char ch1, char ch2);
+size_t ci_find(const String &str1, const String &str2);
+bool   ci_equal(const String &str1, const String &str2);
+bool   ci_less_eq(const String &str1, const String &str2);
+bool   ci_less(const String &str1, const String &str2);
+bool   ci_notequal(const String &str1, const String &str2);
+bool   ci_gt_eq(const String &str1, const String &str2);
+bool   ci_gt(const String &str1, const String &str2);
+
+class CI_EQUAL {
+public:
+	bool operator()(const String &str1, const String &str2) {
+		return ci_equal(str1, str2);
+	}
+};
+
+class CI_LESS_EQ {
+public:
+	bool operator()(const String &str1, const String &str2) {
+		return ci_less_eq(str1, str2);
+	}
+};
+
+class CI_LESS {
+public:
+	bool operator()(const String &str1, const String &str2) {
+		return ci_less(str1, str2);
+	}
+};
+
+extern CI_EQUAL   ci_equal_obj;
+extern CI_LESS    ci_less_obj;
+extern CI_LESS_EQ ci_less_eq_obj;
+
+} // End of namespace Quest
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/quest/uncas.pl b/engines/glk/quest/uncas.pl
new file mode 100644
index 0000000..4e5dc8e
--- /dev/null
+++ b/engines/glk/quest/uncas.pl
@@ -0,0 +1,471 @@
+#!/usr/bin/perl
+
+###############################################################################
+#                                                                             #
+#  Copyright (C) 2006 by Mark J. Tilford                                      #
+#                                                                             #
+#  This file is part of Geas.                                                 #
+#                                                                             #
+#  Geas 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.                                        #
+#                                                                             #
+#  Geas 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 Geas; if not, write to the Free Software                        #
+#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA #
+#                                                                             #
+###############################################################################
+
+use strict;
+
+sub mtext {
+	my $str = shift;
+	my $rv = chr(254);
+	foreach (split //, $str) {
+	$rv . = chr(255 - ord $_);
+}
+return $rv . chr(0);
+}
+
+
+sub obfus {
+	my $str = shift;
+	my $rv = chr(10);
+	foreach (split //, $str) {
+	$rv . = chr(255 - ord $_);
+}
+return $rv . chr(0);
+}
+
+my $is_raw = 0;
+
+my @hash_data =
+    ([1, 'game'], [2, 'procedure'], [3, 'room'], [4, 'object'],
+     [5, 'character'], [6, 'text'], [7, 'selection'], [8, 'define'],
+     [9, 'end'], [11, 'asl-version'], [12, 'game'], [13, 'version'],
+     [14, 'author'], [15, 'copyright'], [16, 'info'], [17, 'start'],
+     [18, 'possitems'], [19, 'startitems'], [20, 'prefix'], [21, 'look'],
+     [22, 'out'], [23, 'gender'], [24, 'speak'], [25, 'take'], [26, 'alias'],
+     [27, 'place'], [28, 'east'], [29, 'north'], [30, 'west'], [31, 'south'],
+     [32, 'give'], [33, 'hideobject'], [34, 'hidechar'], [35, 'showobject'],
+     [36, 'showchar'], [37, 'collectable'], [38, 'collecatbles'],
+     [39, 'command'], [40, 'use'], [41, 'hidden'], [42, 'script'],
+     [43, 'font'], [44, 'default'], [45, 'fontname'], [46, 'fontsize'],
+     [47, 'startscript'], [48, 'nointro'], [49, 'indescription'],
+     [50, 'description'], [51, 'function'], [52, 'setvar'], [53, 'for'],
+     [54, 'error'], [55, 'synonyms'], [56, 'beforeturn'], [57, 'afterturn'],
+     [58, 'invisible'], [59, 'nodebug'], [60, 'suffix'], [61, 'startin'],
+     [62, 'northeast'], [63, 'northwest'], [64, 'southeast'],
+     [65, 'southwest'], [66, 'items'], [67, 'examine'], [68, 'detail'],
+     [69, 'drop'], [70, 'everywhere'], [71, 'nowhere'], [72, 'on'],
+     [73, 'anything'], [74, 'article'], [75, 'gain'], [76, 'properties'],
+     [77, 'type'], [78, 'action'], [79, 'displaytype'], [80, 'override'],
+     [81, 'enabled'], [82, 'disabled'], [83, 'variable'], [84, 'value'],
+     [85, 'display'], [86, 'nozero'], [87, 'onchange'], [88, 'timer'],
+     [89, 'alt'], [90, 'lib'], [91, 'up'], [92, 'down'], [93, 'gametype'],
+     [94, 'singleplayer'], [95, 'multiplayer'], [150, 'do'], [151, 'if'],
+     [152, 'got'], [153, 'then'], [154, 'else'], [155, 'has'], [156, 'say'],
+     [157, 'playwav'], [158, 'lose'], [159, 'msg'], [160, 'not'],
+     [161, 'playerlose'], [162, 'playerwin'], [163, 'ask'], [164, 'goto'],
+     [165, 'set'], [166, 'show'], [167, 'choice'], [168, 'choose'],
+     [169, 'is'], [170, 'setstring'], [171, 'displaytext'], [172, 'exec'],
+     [173, 'pause'], [174, 'clear'], [175, 'debug'], [176, 'enter'],
+     [177, 'movechar'], [178, 'moveobject'], [179, 'revealchar'],
+     [180, 'revealobject'], [181, 'concealchar'], [182, 'concealobject'],
+     [183, 'mailto'], [184, 'and'], [185, 'or'], [186, 'outputoff'],
+     [187, 'outputon'], [188, 'here'], [189, 'playmidi'], [190, 'drop'],
+     [191, 'helpmsg'], [192, 'helpdisplaytext'], [193, 'helpclear'],
+     [194, 'helpclose'], [195, 'hide'], [196, 'show'], [197, 'move'],
+     [198, 'conceal'], [199, 'reveal'], [200, 'numeric'], [201, 'string'],
+     [202, 'collectable'], [203, 'property'], [204, 'create'], [205, 'exit'],
+     [206, 'doaction'], [207, 'close'], [208, 'each'], [209, 'in'],
+     [210, 'repeat'], [211, 'while'], [212, 'until'], [213, 'timeron'],
+     [214, 'timeroff'], [215, 'stop'], [216, 'panes'], [217, 'on'],
+     [218, 'off'], [219, 'return'], [220, 'playmod'], [221, 'modvolume'],
+     [222, 'clone'], [223, 'shellexe'], [224, 'background'],
+     [225, 'foreground'], [226, 'wait'], [227, 'picture'], [228, 'nospeak'],
+     [229, 'animate'], [230, 'persist'], [231, 'inc'], [232, 'dec'],
+     [233, 'flag'], [234, 'dontprocess'], [235, 'destroy'],
+     [236, 'beforesave'], [237, 'onload']);
+
+my % tokens = ();
+my % rtokens = ();
+foreach (@hash_data) {
+	if ($_->[0] >= 0 && $_->[0] < 256) {
+		if ($_->[1] eq '') {
+			$_->[1] = "[?" . $_->[0] . "?]";
+		}
+		$rtokens{chr($_->[0])} = $_->[1];
+		$tokens{$_->[1]} = chr($_->[0]);
+	}
+}
+
+#print "{";
+#for (my $i = 0; $i < 256; $i ++) {
+#    print "\"", $rtokens{chr($i)}, "\", ";
+#}
+#print "}\n";
+#die;
+
+
+my % text_block_starters = map { $_ => 1 } qw / text synonyms type /;
+
+sub uncompile_fil {
+	my $IFH;
+	open($IFH, "<", $_[0]);
+	binmode $IFH;
+	$ / = undef;
+	my $dat = < $IFH >;
+#print "uncompile_fil : ";
+#print "\$IFH == '$IFH',";
+#print "\$dat == '$dat'\n";
+	my @dat = split //, $dat;
+
+
+	my $OFH;
+	if (@_ == 1) {
+		push @_, "&STDOUT";
+	}
+	open $OFH, ">$_[1]" or die "Can't open '$_[1]' for output: $!";
+
+	my @output = ();
+	my $curline = "";
+
+
+	my $obfus = 0;
+	my $expect_text == 0;
+	my($ch, $chn, $tok);
+	for (my $n = 8; $n < @dat; $n ++) {
+		$ch = $dat[$n];
+		$chn = ord $ch;
+		$tok = $rtokens{$ch};
+		if ($obfus == 1 && $chn == 0) {
+#print $OFH "> ";
+			$curline . = "> ";
+			$obfus = 0;
+		}
+		elsif($obfus == 1) {
+#print $OFH chr (255 - $chn);
+			$curline . = chr(255 - $chn);
+		}
+		elsif($obfus == 2 && $chn == 254) {
+			$obfus = 0;
+#print $OFH " ";
+			$curline . = " ";
+		}
+		elsif($obfus == 2) {
+#print $OFH chr ($chn);
+			$curline . = chr($chn);
+		}
+		elsif($expect_text == 2) {
+			if ($chn == 253) {
+				$expect_text = 0;
+##print $OFH "\n";
+				push @output, $curline;
+				$curline = "";
+			}
+			elsif($chn == 0) {
+#print $OFH "\n";
+				push @output, $curline;
+				$curline = "";
+			}
+			else {
+#print $OFH chr (255 - $chn);
+				$curline . = chr(255 - $chn);
+			}
+		}
+		elsif($obfus == 0 && $chn == 10) {
+#print $OFH "<";
+			$curline . = "<";
+			$obfus = 1;
+		}
+		elsif($obfus == 0 && $chn == 254) {
+			$obfus = 2;
+		}
+		elsif($chn == 255) {
+			if ($expect_text == 1) {
+				$expect_text = 2;
+			}
+#print $OFH "\n";
+			push @output, $curline;
+			$curline = "";
+		}
+		else {
+			if (($tok eq 'text' || $tok eq 'synonyms' || $tok eq 'type') &&
+			        $dat[$n - 1] eq chr(8)) {
+				$expect_text = 1;
+			}
+#print $OFH "$tok ";
+			$curline . = "$tok ";
+		}
+	}
+	push @output, $curline;
+	$curline = "";
+
+	if (!$is_raw) {
+		@output = pretty_print(reinline(@output));
+	}
+
+	foreach (@output) {
+		print $OFH $_, "\r\n";
+	}
+}
+
+sub list_grab_file {
+	my $IFH;
+	open($IFH, "<:crlf", $_[0]);
+	my @rv = < $IFH >;
+	chomp @rv;
+	return @rv;
+}
+
+
+sub compile_fil {
+	my @dat = list_grab_file($ARGV[0]);
+	my $OFH;
+	open $OFH, ">$ARGV[1]";
+
+	print $OFH "QCGF200".chr(0);
+
+# Mode 0 == normal, mode 1 == block text
+	my $mode = 0;
+	for (my $n = 0; $n < @dat; $n ++) {
+		my $l = $dat[$n];
+		while (substr($l, length($l) - 1, 1) eq '_' && $n < @dat) {
+			$n ++;
+			$l = substr($l, 0, length($l) - 1) . $dat[$n];
+		}
+		if ($l = ~ / ^ !include *<([\S] *)> /) {
+			@dat = (@dat[0..$n], list_grab_file($1), @dat[$n + 1..$#dat]);
+		}
+		elsif($l = ~ / ^ !addto.* /) {
+# TODO
+		}
+		else {
+			my $i = 0;
+			my $max = length $l;
+			my @l = split //, $l;
+
+			if ($mode == 1) {
+				if ($l = ~ / ^\s * end\s * define\s*$ /) {
+					print $OFH chr(253);
+					$mode = 0;
+# FALL THROUGH
+				} else {
+#print $OFH chr(0);
+					foreach (split //, $l) {
+					         print $OFH chr(255 - ord $_);
+				}
+			next;
+		}
+	}
+	if ($l = ~ / ^\s*$ /) {
+			next;
+		}
+		if ($l = ~ / ^\s * define\s * (text | synonyms | type) /) {
+#[\s$]
+			$mode = 1;
+		}
+		while ($i < $max) {
+			while ($i <= $max && $l[$i] = ~ / \s /) {
+				++ $i;
+			}
+			if ($i == $max) {
+				next;
+			}
+
+			my $j = $i + 1;
+			if ($l[$i] eq '<') {
+				while ($j < $max && $l[$j] ne '>') {
+					++ $j;
+				}
+				if ($l[$j] eq '>') {
+					print $OFH obfus(substr($l, $i + 1, $j - $i - 1));
+					$i = $j + 1;
+					next;
+				}
+				$j = $i + 1;
+				while ($j < $max && $l[$j] ne ' ') {
+					++ $j;
+				}
+				print $OFH chr(254). substr($l, $i + 1, $j - $i - 1). chr(0);
+				$i = $j + 1;
+				next;
+			}
+			while ($j < $max && $l[$j] ne ' ') {
+				++ $j;
+			}
+			my $str = substr($l, $i, $j - $i);
+			if (defined $tokens{$str}) {
+				print $OFH $tokens{$str};
+			}
+			else {
+				print $OFH chr(254). $str. chr(254);
+			}
+			$i = $j + 1;
+		}
+	}
+	print $OFH chr(255);
+}
+}
+
+sub is_define_t {
+	my($line, $type) = (@_);
+	return ($line = ~ / ^ *define[\s] + $type + /);
+}
+sub is_define {
+	my($line) = (@_);
+	return ($line = ~ / ^ *define[\s] + [^\s] /);
+}
+sub is_end_define { return (shift = ~ / ^ *end + define *$ /); }
+
+sub trim {
+	my $tmp = trim1($_[0]);
+#print "trimming ($_[0]) -> ($tmp)\n";
+	return $tmp;
+}
+
+sub trim1 {
+	if ($_[0] = ~ / ^[\s] * (.* ?)[\s]*$ /) {
+		return $1;
+	}
+	print "* * * Huh on trimming '$_[0]' * * *\n";
+}
+
+sub reinline {
+	my % reinlined = ();
+	my @head_prog = ();
+	my @rest_prog = ();
+	while (@_) {
+		push @rest_prog, (pop @_);
+	}
+
+	while (@rest_prog) {
+		my $line = pop @rest_prog;
+#print "processing $line\n";
+		if ($line = ~ / ^ (.* |)do ( < !intproc[0 - 9] * >) * (.*)$ /) {
+#print "  reinlining...\n";
+				my($prefix, $func_name, $suffix) = ($1, $2, $3);
+				$prefix = trim($prefix);
+				$suffix = trim($suffix);
+				$reinlined{$func_name} = 1;
+				for (my $line_num = 0; $line_num < @rest_prog; $line_num ++) {
+					if ($rest_prog[$line_num] = ~ / ^ *define + procedure + $func_name *$ /) {
+						my $end_line = $line_num;
+						while (!is_end_define($rest_prog[$end_line])) {
+#print "   checking $rest_prog[$end_line]\n";
+							-- $end_line;
+						}
+						++ $end_line;
+#print "    backpushing } ".$suffix."\n";
+#push @rest_prog, trim ("} " . $suffix);
+						if ($suffix ne '') {
+							push @rest_prog, $suffix;
+						}
+						push @rest_prog, "}";
+						while ($end_line < $line_num) {
+							push @rest_prog, $rest_prog[$end_line];
+#print "    backpushing $rest_prog[$end_line]\n";
+							$end_line ++;
+						}
+#print "    backpushing $prefix {\n";
+						push @rest_prog, trim($prefix." {");
+						$line_num = scalar @rest_prog;
+					}
+				}
+			}
+		else {
+			push @head_prog, $line;
+		}
+	}
+	my @rv = ();
+	for (my $n = 0; $n < @head_prog; $n ++) {
+		if ($head_prog[$n] = ~ / ^define procedure(<.*>) *$ / &&
+		                     $reinlined{$1}) {
+			while (!is_end_define($head_prog[$n])) {
+				++ $n;
+			}
+		}
+		else {
+			push @rv, $head_prog[$n];
+		}
+	}
+#for (my $n = 0; $n < @rv; $n ++) {
+#   print "$n: $rv[$n]\n";
+#}
+	return @rv;
+}
+
+sub pretty_print {
+	my $indent = 0;
+	my $not_in_text_mode = 1;
+
+	my @rv = ();
+
+	for (my $n = 0; $n < @_; $n ++) {
+		my $line = $_[$n];
+		if (is_end_define($line)) {
+			-- $indent;
+			$not_in_text_mode = 1;
+		}
+		/ { /; if ($line = ~ / ^} /) {
+			-- $indent;
+		}
+###if (is_define ($line) && ($n == 0 || !is_define ($_[$n-1]))) { print "\n"; }
+		if (is_define($line) && ($n == 0 || !is_define($_[$n - 1]))) {
+			push @rv, "";
+		}
+###if ($in_text_mode == 0) { print "    "x$indent; }
+		push @rv, ("    "x($indent*$not_in_text_mode)).trim($line);
+###print $line, "  line $n, indent $indent, text $in_text_mode\n";
+###print $line, "\n";
+		if (is_end_define($line) && $n < @_ && !is_end_define($_[$n + 1])
+		        && !is_define($_[$n + 1])) {
+###print "\n";
+			push @rv, "";
+		}
+		if (is_define($line)) {
+			++ $indent;
+		}
+		if ($line = ~ / {$ /) {
+		++ $indent;
+	} /
+	   } /;
+	if ($line = ~ / ^ *define + text /) {
+			$not_in_text_mode = 0;
+		}
+	}
+	return @rv;
+}
+
+
+sub error_msg {
+	die "Usage: 'perl uncas.pl file.asl file2.cas' to compile to file\n".
+	"       'perl uncas.pl file.cas'           to decompile to console\n".
+	"       'perl uncas.pl file.cas file2.asl' to decompile to file\n";
+}
+
+if ($ARGV[0] eq '-raw') {
+	$is_raw = 1;
+	shift @ARGV;
+}
+
+if ($ARGV[0] = ~ / \.asl$ /) {
+	if (@ARGV != 2) {
+		error_msg();
+	}
+	compile_fil(@ARGV);
+}
+elsif($ARGV[0] = ~ / \.cas$ /) {
+#print "compile_fil (", join (", ", @ARGV), ")\n";
+	if (@ARGV != 1 && @ARGV != 2) {
+		error_msg();
+	}
+	uncompile_fil(@ARGV);
+}
diff --git a/engines/glk/quetzal.cpp b/engines/glk/quetzal.cpp
index e604602..0a2bc44 100644
--- a/engines/glk/quetzal.cpp
+++ b/engines/glk/quetzal.cpp
@@ -44,6 +44,7 @@ const uint32 INTERPRETER_IDS[INTERPRETER_TADS3 + 1] = {
 	MKTAG('J', 'A', 'C', 'L'),
 	MKTAG('L', 'V', 'L', '9'),
 	MKTAG('M', 'A', 'G', 'N'),
+	MKTAG('Q', 'E', 'S', 'T'),
 	MKTAG('S', 'C', 'A', 'R'),
 	MKTAG('S', 'C', 'O', 'T'),
 	MKTAG('T', 'A', 'D', '2'),


Commit: 30bf05479a8d4b9e63b243b0f1acac3fb946da32
    https://github.com/scummvm/scummvm/commit/30bf05479a8d4b9e63b243b0f1acac3fb946da32
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-09-29T15:08:52-07:00

Commit Message:
GLK: QUEST: Added detection entries

Changed paths:
    engines/glk/quest/detection.cpp
    engines/glk/quest/detection_tables.h
    engines/glk/quest/geas_glk.cpp
    engines/glk/quest/quest.cpp
    engines/glk/quest/read_file.cpp


diff --git a/engines/glk/quest/detection.cpp b/engines/glk/quest/detection.cpp
index a0aa6b8..1423bc1 100644
--- a/engines/glk/quest/detection.cpp
+++ b/engines/glk/quest/detection.cpp
@@ -52,7 +52,7 @@ bool QuestMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &g
 			continue;
 
 		Common::String filename = file->getName();
-		if (!filename.hasSuffixIgnoreCase(".quest"))
+		if (!filename.hasSuffixIgnoreCase(".cas") && !filename.hasSuffixIgnoreCase(".asl"))
 			continue;
 
 		Common::File gameFile;
diff --git a/engines/glk/quest/detection_tables.h b/engines/glk/quest/detection_tables.h
index 8e1fe11..4cad443 100644
--- a/engines/glk/quest/detection_tables.h
+++ b/engines/glk/quest/detection_tables.h
@@ -30,6 +30,14 @@ namespace Quest {
 const PlainGameDescriptor QUEST_GAME_LIST[] = {
 	{ "quest", "Quest Game" },
 
+	{ "adventureq", "Adventure!" },
+	{ "attemptedassassination", "Attempted Assassination" },
+	{ "hauntedhorror", "Haunted Horror" },
+	{ "magicworld", "Magic World" },
+	{ "redsaucemonday", "Red Sauce Monday" },
+	{ "worldsend", "World's End" },
+
+#ifdef QUEST_EXT
 	{ "attackonfrightside", "Attack On Frightside" },
 	{ "balaclava", "Balaclava" },
 	{ "bearsepicquest", "Bear's Epic Quest" },
@@ -48,31 +56,38 @@ const PlainGameDescriptor QUEST_GAME_LIST[] = {
 	{ "thelasthero", "The Last Hero" },
 	{ "tokindlealight", "To Kindle a Light" },
 	{ "xanadu", "Xanadu - The World's Only Hope" },
-
+#endif
 	{ nullptr, nullptr }
 };
 
 const GlkDetectionEntry QUEST_GAMES[] = {
-  DT_ENTRY0("attackonfrightside", "84542fc6460833bbf2594ed83f8b1fc7", 46019),
-  DT_ENTRY0("balaclava", "8b30af05d9986f9f962c677181ecc766", 57719),
-  DT_ENTRY0("bearsepicquest", "e6896a65527f456b4362aaebcf39e354", 62075),
-  DT_ENTRY0("caught", "4502d89d8e304fe4165d46eb22f21f10", 5168593),
-  DT_ENTRY0("cuttings", "e0ded5a6b78e8c9482e746d55f61972c", 6583866),
-  DT_ENTRY0("draculacrl", "1af3ec877584b290f7ab1a1be8f944a5", 4548737),
-  DT_ENTRY0("elections4", "d0bc0cd54182d6099808767068592b94", 591994),
-  DT_ENTRY0("everyman", "410c7211d3f0c700f34e97ed258e33f1", 56218),
-  DT_ENTRY0("firstTimes", "31d878c82d99856d473762612f154eb6", 10253826),
-  DT_ENTRY0("giftofthemagi", "b33132ce71c8a2eed0f6c1c1af284765", 78647),
-  DT_ENTRY0("medievalistsquest", "e0a15bc2a74a0bd6bb5c24661ea35829", 127977271),
-  DT_ENTRY0("parishotel", "c9a42bc3f306aba5e318b0a74115e0d4", 474983),
-  DT_ENTRY0("questforloot", "f7e32aec0f961a59a69bead3fadff4f0", 1357373),
-  DT_ENTRY0("sleepingassassin", "9c2aa213bb73d8083506ee6f64436d9d", 287227),
-  DT_ENTRY0("spondre", "c639077eb487eb6d1b63cda2c9ba5a9b", 1169469),
-  DT_ENTRY0("thelasthero", "31e10b8a7f11a6289955b89437f8178c", 62512),
-  DT_ENTRY0("tokindlealight", "5d3b57830b003046a621620ba0869d7c", 811845),
-
+	DT_ENTRY0("adventureq", "93a358f817066494dbdabf222fc20595", 6974),
+	DT_ENTRY0("attemptedassassination", "e8cf55898bcc5ee43a2527d5fefeaaff", 18833),
+	DT_ENTRY0("hauntedhorror", "89a5d511aed564d4810b372d271e33fa", 19635),
+	DT_ENTRY0("magicworld", "463cf8919c7321f3af305534b7ae78f3", 15176),
+	DT_ENTRY0("redsaucemonday", "5a2f3e25d4a8c77e0c53d980dbb37451", 20324),
+	DT_ENTRY0("worldsend", "4f5daac10085927bf5180bea24f7ef0d", 73396),
 
+#ifdef QUEST_EXT
+	DT_ENTRY0("attackonfrightside", "84542fc6460833bbf2594ed83f8b1fc7", 46019),
+	DT_ENTRY0("balaclava", "8b30af05d9986f9f962c677181ecc766", 57719),
+	DT_ENTRY0("bearsepicquest", "e6896a65527f456b4362aaebcf39e354", 62075),
+	DT_ENTRY0("caught", "4502d89d8e304fe4165d46eb22f21f10", 5168593),
+	DT_ENTRY0("cuttings", "e0ded5a6b78e8c9482e746d55f61972c", 6583866),
+	DT_ENTRY0("draculacrl", "1af3ec877584b290f7ab1a1be8f944a5", 4548737),
+	DT_ENTRY0("elections4", "d0bc0cd54182d6099808767068592b94", 591994),
+	DT_ENTRY0("everyman", "410c7211d3f0c700f34e97ed258e33f1", 56218),
+	DT_ENTRY0("firstTimes", "31d878c82d99856d473762612f154eb6", 10253826),
+	DT_ENTRY0("giftofthemagi", "b33132ce71c8a2eed0f6c1c1af284765", 78647),
+	DT_ENTRY0("medievalistsquest", "e0a15bc2a74a0bd6bb5c24661ea35829", 127977271),
+	DT_ENTRY0("parishotel", "c9a42bc3f306aba5e318b0a74115e0d4", 474983),
+	DT_ENTRY0("questforloot", "f7e32aec0f961a59a69bead3fadff4f0", 1357373),
+	DT_ENTRY0("sleepingassassin", "9c2aa213bb73d8083506ee6f64436d9d", 287227),
+	DT_ENTRY0("spondre", "c639077eb487eb6d1b63cda2c9ba5a9b", 1169469),
+	DT_ENTRY0("thelasthero", "31e10b8a7f11a6289955b89437f8178c", 62512),
+	DT_ENTRY0("tokindlealight", "5d3b57830b003046a621620ba0869d7c", 811845),
 	DT_ENTRY0("xanadu", "fef25e3473755ec572d4236d56f918e2", 396973),
+#endif
 
 	DT_END_MARKER
 };
diff --git a/engines/glk/quest/geas_glk.cpp b/engines/glk/quest/geas_glk.cpp
index 8f48799..571be78 100644
--- a/engines/glk/quest/geas_glk.cpp
+++ b/engines/glk/quest/geas_glk.cpp
@@ -116,8 +116,8 @@ void GeasGlkInterface::set_background(String s) {
  * GeasInterface?
  */
 String GeasGlkInterface::get_file(const String &fname) const {
-	Common::File ifs;
-	if (ifs.open(fname)) {
+	Common::File f;
+	if (!f.open(fname)) {
 		glk_put_cstring("Couldn't open ");
 		glk_put_cstring(fname.c_str());
 		g_vm->glk_put_char(0x0a);
@@ -125,10 +125,10 @@ String GeasGlkInterface::get_file(const String &fname) const {
 	}
 
 	// Read entirety of the file
-	char *buf = new char[ifs.size()];
-	ifs.read(buf, ifs.size());
+	char *buf = new char[f.size()];
+	f.read(buf, f.size());
 	
-	String result(buf, buf + ifs.size());
+	String result(buf, buf + f.size());
 	delete[] buf;
 
 	return result;
diff --git a/engines/glk/quest/quest.cpp b/engines/glk/quest/quest.cpp
index 93de43f..8c4a222 100644
--- a/engines/glk/quest/quest.cpp
+++ b/engines/glk/quest/quest.cpp
@@ -96,6 +96,7 @@ void Quest::playGame() {
 }
 
 bool Quest::initialize() {
+	Streams::initialize();
 	glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_ReverseColor, 1);
 
 	// Open the main window
@@ -135,6 +136,7 @@ bool Quest::initialize() {
 }
 
 void Quest::deinitialize() {
+	Streams::deinitialize();
 }
 
 Common::Error Quest::readSaveData(Common::SeekableReadStream *rs) {
diff --git a/engines/glk/quest/read_file.cpp b/engines/glk/quest/read_file.cpp
index c749bb2..766b8b5 100644
--- a/engines/glk/quest/read_file.cpp
+++ b/engines/glk/quest/read_file.cpp
@@ -352,10 +352,9 @@ bool decompile(String data, Common::Array<String> &rv);
 bool preprocess(Common::Array<String> v, String fname, Common::Array<String> &rv, GeasInterface *gi);
 
 GeasFile read_geas_file(GeasInterface *gi, const String &filename) {
-	//return GeasFile (split_lines(gi->get_file(s)), gi);
 	String file_contents = gi->get_file(filename);
 
-	if (file_contents == "")
+	if (file_contents.empty())
 		return GeasFile();
 
 	Common::Array<String> data;


Commit: 32c9deefb7331ae1c4f25f99b44e71189f977b76
    https://github.com/scummvm/scummvm/commit/32c9deefb7331ae1c4f25f99b44e71189f977b76
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-09-29T15:08:52-07:00

Commit Message:
GLK: QUEST: Fix infinite recursion

Changed paths:
    engines/glk/quest/reserved_words.h


diff --git a/engines/glk/quest/reserved_words.h b/engines/glk/quest/reserved_words.h
index 5d73607..52c3e21 100644
--- a/engines/glk/quest/reserved_words.h
+++ b/engines/glk/quest/reserved_words.h
@@ -59,7 +59,7 @@ public:
 	 * Returns true if the passed string is a reserved word
 	 */
 	bool has(const String &s) const {
-		return _data.contains(s) && (*this)[s];
+		return _data.contains(s) && _data[s];
 	}
 
 	/**


Commit: 055f6f0b483f8f6b285f223ca852392ef320c76f
    https://github.com/scummvm/scummvm/commit/055f6f0b483f8f6b285f223ca852392ef320c76f
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-09-29T15:08:52-07:00

Commit Message:
GLK: QUEST: Startup/logging fix

Changed paths:
    engines/glk/quest/geas_runner.cpp
    engines/glk/quest/quest.cpp
    engines/glk/quest/quest.h
    engines/glk/quest/streams.cpp


diff --git a/engines/glk/quest/geas_runner.cpp b/engines/glk/quest/geas_runner.cpp
index a029c07..f52a89b 100644
--- a/engines/glk/quest/geas_runner.cpp
+++ b/engines/glk/quest/geas_runner.cpp
@@ -3531,7 +3531,7 @@ void geas_implementation::tick_timers() {
 GeasResult GeasInterface::print_formatted(String s, bool with_newline) {
 	unsigned int i, j;
 
-	//cerr << "print_formatted (" << s << ", " << with_newline << ")" << endl;
+	cerr << "print_formatted (" << s << ", " << with_newline << ")" << endl;
 
 	for (i = 0; i < s.length(); i ++) {
 		//std::cerr << "i == " << i << std::endl;
diff --git a/engines/glk/quest/quest.cpp b/engines/glk/quest/quest.cpp
index 8c4a222..71e6ab7 100644
--- a/engines/glk/quest/quest.cpp
+++ b/engines/glk/quest/quest.cpp
@@ -32,6 +32,10 @@ namespace Quest {
 
 Quest *g_vm;
 
+Quest::Quest(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), _saveSlot(-1) {
+	g_vm = this;
+}
+
 void Quest::runGame() {
 	// Check for savegame
 	_saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
diff --git a/engines/glk/quest/quest.h b/engines/glk/quest/quest.h
index 69aa2db..8777560 100644
--- a/engines/glk/quest/quest.h
+++ b/engines/glk/quest/quest.h
@@ -56,9 +56,7 @@ public:
 	/**
 	 * Constructor
 	 */
-	Quest(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), _saveSlot(-1) {
-		g_vm = this;
-	}
+	Quest(OSystem *syst, const GlkGameDescription &gameDesc);
 
 	/**
 	 * Run the game
diff --git a/engines/glk/quest/streams.cpp b/engines/glk/quest/streams.cpp
index 37ea042..1b49d2a 100644
--- a/engines/glk/quest/streams.cpp
+++ b/engines/glk/quest/streams.cpp
@@ -39,8 +39,11 @@ void Streams::deinitialize() {
 }
 
 uint32 ConsoleStream::write(const void *dataPtr, uint32 dataSize) {
-	Common::String s((const char *)dataPtr, (const char *)dataPtr + dataSize);
-	debug("%s", s.c_str());
+	if (gDebugLevel > 0) {
+		Common::String s((const char *)dataPtr, (const char *)dataPtr + dataSize);
+		debug("%s", s.c_str());
+	}
+
 	return dataSize;
 }
 


Commit: 97e61ddb0ecfe410af57687f5b264992033ccf91
    https://github.com/scummvm/scummvm/commit/97e61ddb0ecfe410af57687f5b264992033ccf91
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-09-29T15:08:52-07:00

Commit Message:
GLK: QUEST: Fix crash printing formatted strings

Changed paths:
    engines/glk/quest/geas_runner.cpp


diff --git a/engines/glk/quest/geas_runner.cpp b/engines/glk/quest/geas_runner.cpp
index f52a89b..9d4fd32 100644
--- a/engines/glk/quest/geas_runner.cpp
+++ b/engines/glk/quest/geas_runner.cpp
@@ -3647,7 +3647,7 @@ GeasResult GeasInterface::print_formatted(String s, bool with_newline) {
 			for (j = i; i != s.length() && s[i] != '|'; i ++)
 				;
 			print_normal(s.substr(j, i - j));
-			if (s[i] == '|')
+			if (i != s.length() && s[i] == '|')
 				-- i;
 		}
 	}


Commit: b35088b78818290e60faa6bc5ef2cf240ea059e3
    https://github.com/scummvm/scummvm/commit/b35088b78818290e60faa6bc5ef2cf240ea059e3
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-09-29T15:08:52-07:00

Commit Message:
GLK: QUEST: Fix exitting by closing game window

Changed paths:
    engines/glk/quest/quest.cpp


diff --git a/engines/glk/quest/quest.cpp b/engines/glk/quest/quest.cpp
index 71e6ab7..15d5eda 100644
--- a/engines/glk/quest/quest.cpp
+++ b/engines/glk/quest/quest.cpp
@@ -75,6 +75,8 @@ void Quest::playGame() {
 
 		while (ev.type != evtype_LineInput) {
 			glk_select(&ev);
+			if (shouldQuit())
+				return;
 
 			switch (ev.type) {
 			case evtype_LineInput:


Commit: defc19ada4f94d727531a78f0fa1574c328ef06d
    https://github.com/scummvm/scummvm/commit/defc19ada4f94d727531a78f0fa1574c328ef06d
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-09-29T15:08:52-07:00

Commit Message:
GLK: QUEST: Savegames aren't supported for Quest games

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


diff --git a/engines/glk/quest/quest.cpp b/engines/glk/quest/quest.cpp
index 15d5eda..cbddeda 100644
--- a/engines/glk/quest/quest.cpp
+++ b/engines/glk/quest/quest.cpp
@@ -145,13 +145,5 @@ void Quest::deinitialize() {
 	Streams::deinitialize();
 }
 
-Common::Error Quest::readSaveData(Common::SeekableReadStream *rs) {
-	return Common::kNoError;
-}
-
-Common::Error Quest::writeGameData(Common::WriteStream *ws) {
-	return Common::kNoError;
-}
-
 } // End of namespace Quest
 } // End of namespace Glk
diff --git a/engines/glk/quest/quest.h b/engines/glk/quest/quest.h
index 8777560..cb0994d 100644
--- a/engines/glk/quest/quest.h
+++ b/engines/glk/quest/quest.h
@@ -71,15 +71,24 @@ public:
 	}
 
 	/**
-	 * Load a savegame from the passed Quetzal file chunk stream
+	 * Savegames aren't supported for Quest games
 	 */
-	virtual Common::Error readSaveData(Common::SeekableReadStream *rs) override;
+	virtual bool canLoadGameStateCurrently() override { return false; }
 
 	/**
-	 * Save the game. The passed write stream represents access to the UMem chunk
-	 * in the Quetzal save file that will be created
+	 * Savegames aren't supported for Quest games
 	 */
-	virtual Common::Error writeGameData(Common::WriteStream *ws) override;
+	virtual bool canSaveGameStateCurrently() override { return false; }
+
+	/**
+	 * Savegames aren't supported for Quest games
+	 */
+	virtual Common::Error readSaveData(Common::SeekableReadStream *rs) override { return Common::kUnknownError; }
+
+	/**
+	 * Savegames aren't supported for Quest games
+	 */
+	virtual Common::Error writeGameData(Common::WriteStream *ws) override { return Common::kUnknownError; }
 };
 
 extern Quest *g_vm;


Commit: 643ee33a3deab293c8377ce278866c0e6b241fab
    https://github.com/scummvm/scummvm/commit/643ee33a3deab293c8377ce278866c0e6b241fab
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-09-29T15:08:52-07:00

Commit Message:
GLK: QUEST: gcc compilation fixes

Changed paths:
    engines/glk/quest/geas_file.cpp
    engines/glk/quest/geas_glk.cpp
    engines/glk/quest/geas_runner.cpp
    engines/glk/quest/geas_state.cpp
    engines/glk/quest/geas_util.cpp
    engines/glk/quest/geas_util.h
    engines/glk/quest/quest.cpp
    engines/glk/quest/streams.cpp
    engines/glk/quest/streams.h


diff --git a/engines/glk/quest/geas_file.cpp b/engines/glk/quest/geas_file.cpp
index 05c78e3..187c7cc 100644
--- a/engines/glk/quest/geas_file.cpp
+++ b/engines/glk/quest/geas_file.cpp
@@ -89,8 +89,6 @@ bool GeasFile::obj_has_property(String objname, String propname) const {
 	return get_obj_property(objname, propname, tmp);
 }
 
-Common::WriteStream &operator<< (Common::WriteStream &, const Set<String> &);
-
 /**
  * Currently only works for actual objects, not rooms or the game
  */
@@ -130,7 +128,7 @@ void GeasFile::get_obj_keys(String obj, Set<String> &rv) const {
 				vstring params = split_param(param_contents(tok));
 				for (uint j = 0; j < params.size(); j ++) {
 					cerr << "   handling parameter <" << params[j] << ">\n";
-					uint k = params[j].find('=');
+					int k = params[j].find('=');
 					// SENSITIVE?
 					if (starts_with(params[j], "not ")) {
 						rv.insert(trim(params[j].substr(4)));
@@ -189,7 +187,7 @@ void GeasFile::get_type_keys(String typen, Set<String> &rv) const {
 		else if (tok == "action") {
 			cerr << "       action, skipping\n";
 		} else {
-			uint ch = line.find('=');
+			int ch = line.find('=');
 			if (ch != -1) {
 				rv.insert(trim(line.substr(0, ch)));
 				cerr << "      adding <" << trim(line.substr(0, ch)) << ">\n";
@@ -262,7 +260,7 @@ bool GeasFile::get_obj_property(String objname, String propname, String &string_
 			Common::Array<String> props = split_param(param_contents(tok));
 			for (uint j = 0; j < props.size(); j ++) {
 				//cerr << "    g_o_p: Comparing against <" << props[j] << ">\n";
-				uint index;
+				int index;
 				if (props[j] == propname) {
 					//cerr << "      g_o_p: Present but empty, blanking\n";
 					string_rv = "";
@@ -296,6 +294,7 @@ void GeasFile::get_type_property(String typenamex, String propname, bool &bool_r
 		String line = block->data[i];
 		//cerr << "    Comparing vs. line <" << line << ">\n";
 		uint c1, c2;
+		int p;
 		String tok = first_token(line, c1, c2);
 
 		// SENSITIVE?
@@ -307,11 +306,11 @@ void GeasFile::get_type_property(String typenamex, String propname, bool &bool_r
 			bool_rv = true;
 			string_rv = "";
 		} else {
-			c1 = line.find('=');
-			if (c1 != -1) {
-				tok = trim(line.substr(0, c1));
+			p = line.find('=');
+			if (p != -1) {
+				tok = trim(line.substr(0, p));
 				if (tok == propname) {
-					string_rv = trim(line.substr(c1 + 1));
+					string_rv = trim(line.substr(p + 1));
 					bool_rv = true;
 				}
 			}
@@ -517,8 +516,7 @@ void GeasFile::register_block(String blockname, String blocktype) {
 		String errdesc = "Trying to register block of named <" + blockname +
 		                 "> of type <" + blocktype + "> when there is already one, of type <" +
 		                 obj_types[blockname] + ">";
-		debug_print(errdesc);
-		throw errdesc;
+		error("%s", errdesc.c_str());
 	}
 	obj_types[blockname] = blocktype;
 }
@@ -526,14 +524,14 @@ void GeasFile::register_block(String blockname, String blocktype) {
 String GeasFile::static_svar_lookup(String varname) const {
 	cerr << "static_svar_lookup(" << varname << ")" << endl;
 	//varname = lcase (varname);
-	for (uint i = 0; i < size("variable"); i ++)
+	for (uint i = 0; i < size("variable"); i++) {
 		//if (blocks[i].lname == varname)
 		if (ci_equal(blocks[i].name, varname)) {
 			String rv;
 			String tok;
 			uint c1, c2;
 			bool found_typeline = false;
-			for (uint j = 0; j < blocks[i].data.size(); j ++) {
+			for (uint j = 0; j < blocks[i].data.size(); j++) {
 				String line = blocks[i].data[j];
 				tok = first_token(line, c1, c2);
 				// SENSITIVE?
@@ -541,26 +539,27 @@ String GeasFile::static_svar_lookup(String varname) const {
 					tok = next_token(line, c1, c2);
 					// SENSITIVE?
 					if (tok == "numeric")
-						throw String("Trying to evaluate int var '" + varname +
-						             "' as String");
+						error("Trying to evaluate int var '%s' as String", varname.c_str());
 					// SENSITIVE?
 					if (tok != "String")
-						throw String("Bad variable type " + tok);
+						error("Bad variable type %s", tok.c_str());
 					found_typeline = true;
 				}
 				// SENSITIVE?
 				else if (tok == "value") {
 					tok = next_token(line, c1, c2);
 					if (!is_param(tok))
-						throw String("Expected param after value in " + line);
+						error("Expected param after value in %s", line.c_str());
 					rv = param_contents(tok);
 				}
 			}
 			if (!found_typeline)
-				throw String(varname + " is a numeric variable");
+				error("%s is a numeric variable", varname.c_str());
 			cerr << "static_svar_lookup(" << varname << ") -> \"" << rv << "\"" << endl;
 			return rv;
 		}
+	}
+
 	debug_print("Variable <" + varname + "> not found.");
 	return "";
 }
@@ -581,17 +580,16 @@ String GeasFile::static_ivar_lookup(String varname) const {
 					tok = next_token(line, c1, c2);
 					// SENSITIVE?
 					if (tok == "String")
-						throw String("Trying to evaluate String var '" + varname +
-						             "' as numeric");
+						error("Trying to evaluate String var '%s' as numeric", varname.c_str());
 					// SENSITIVE?
 					if (tok != "numeric")
-						throw String("Bad variable type " + tok);
+						error("Bad variable type %s", tok.c_str());
 				}
 				// SENSITIVE?
 				else if (tok == "value") {
 					tok = next_token(line, c1, c2);
 					if (!is_param(tok))
-						throw String("Expected param after value in " + line);
+						error("Expected param after value in %s", line.c_str());
 					rv = param_contents(tok);
 				}
 			}
@@ -610,7 +608,7 @@ String GeasFile::static_eval(String input) const {
 			for (j = i + 1; j < input.length() && input[j] != '#'; j ++)
 				;
 			if (j == input.length())
-				throw String("Error processing '" + input + "', odd hashes");
+				error("Error processing '%s', odd hashes", input.c_str());
 			uint k;
 			for (k = i + 1; k < j && input[k] != ':'; k ++)
 				;
@@ -643,7 +641,7 @@ String GeasFile::static_eval(String input) const {
 			for (j = i; j < input.length() && input[j] != '%'; j ++)
 				;
 			if (j == input.length())
-				throw String("Error processing '" + input + "', unmatched %");
+				error("Error processing '%s', unmatched %%", input.c_str());
 			rv += static_ivar_lookup(input.substr(i + 1, j - i - 2));
 			i = j;
 		} else
diff --git a/engines/glk/quest/geas_glk.cpp b/engines/glk/quest/geas_glk.cpp
index 571be78..a71a775 100644
--- a/engines/glk/quest/geas_glk.cpp
+++ b/engines/glk/quest/geas_glk.cpp
@@ -56,17 +56,14 @@ void draw_banner() {
 		g_vm->glk_window_move_cursor(bannerwin, 1, 0);
 
 		if (g_vm->banner.empty())
-			g_vm->glk_put_string_stream(stream, (char *)"Geas 0.4");
+			g_vm->glk_put_string_stream(stream, "Geas 0.4");
 		else
-			g_vm->glk_put_string_stream(stream, (char *)g_vm->banner.c_str());
+			g_vm->glk_put_string_stream(stream, g_vm->banner.c_str());
 	}
 }
 
 void glk_put_cstring(const char *s) {
-	/* The cast to remove const is necessary because g_vm->glk_put_string
-	 * receives a "char *" despite the fact that it could equally well use
-	 * "const char *". */
-	g_vm->glk_put_string((char *)s);
+	g_vm->glk_put_string(s);
 }
 
 GeasResult GeasGlkInterface::print_normal(const String &s) {
@@ -152,14 +149,14 @@ String GeasGlkInterface::get_string() {
 }
 
 uint GeasGlkInterface::make_choice(String label, Common::Array<String> v) {
-	size_t n;
+	uint n;
 
 	g_vm->glk_window_clear(inputwin);
 
 	glk_put_cstring(label.c_str());
 	g_vm->glk_put_char(0x0a);
 	n = v.size();
-	for (size_t i = 0; i < n; ++i) {
+	for (uint i = 0; i < n; ++i) {
 		StringStream t;
 		String s;
 		t << i + 1;
@@ -176,13 +173,13 @@ uint GeasGlkInterface::make_choice(String label, Common::Array<String> v) {
 	t << n;
 	t >> s;
 	s1 = "Choose [1-" + s + "]> ";
-	g_vm->glk_put_string_stream(inputwinstream, (char *)(s1.c_str()));
+	g_vm->glk_put_string_stream(inputwinstream, s1.c_str());
 
 	int choice = atoi(get_string().c_str());
 	if (choice < 1) {
 		choice = 1;
 	}
-	if ((size_t)choice > n) {
+	if ((uint)choice > n) {
 		choice = n;
 	}
 
diff --git a/engines/glk/quest/geas_runner.cpp b/engines/glk/quest/geas_runner.cpp
index 9d4fd32..665f27d 100644
--- a/engines/glk/quest/geas_runner.cpp
+++ b/engines/glk/quest/geas_runner.cpp
@@ -28,7 +28,7 @@
 #include "glk/quest/geas_impl.h"
 #include "glk/quest/quest.h"
 #include "glk/quest/streams.h"
-#include "glk/quest/String.h"
+#include "glk/quest/string.h"
 
 namespace Glk {
 namespace Quest {
@@ -744,7 +744,7 @@ void geas_implementation::set_game(const String &fname) {
 				continue;
 			// SENSITIVE?
 			if (tok == "multiplayer")
-				throw String("Error: geas is single player only.");
+				error("Error: geas is single player only.");
 			gi->debug_print("Unexpected game type " + s);
 		}
 		// SENSITIVE?
@@ -1239,7 +1239,7 @@ match_rv geas_implementation::match_command(String input, uint ichar, String act
 				achar ++;
 			}
 			if (achar == action.length())
-				throw String("Unpaired hashes in command String " + action);
+				error("Unpaired hashes in command String %s", action.c_str());
 			//rv.bindings.push_back (varname);
 			int index = rv.bindings.size();
 			rv.bindings.push_back(match_binding(varname, ichar));
@@ -2491,7 +2491,7 @@ void geas_implementation::run_script(String s, String &rv) {
 			return;
 		}
 		bool is_while = (tok == "while");
-		uint start_cond = c2, end_cond = (uint) -1;
+		int start_cond = c2, end_cond = -1;
 		while ((tok = next_token(s, c1, c2)) != "") {
 			// SENSITIVE?
 			if (tok == "do") {
@@ -3444,7 +3444,7 @@ String geas_implementation::eval_string(String s) {
 			  }
 			*/
 			//if (j == rv.size())
-			if (j == -1) {
+			if (j == (uint)-1) {
 				gi->debug_print("Unmatched $s in " + s);
 				return rv + s.substr(i);
 			}
diff --git a/engines/glk/quest/geas_state.cpp b/engines/glk/quest/geas_state.cpp
index 985b4e9..3a9b411 100644
--- a/engines/glk/quest/geas_state.cpp
+++ b/engines/glk/quest/geas_state.cpp
@@ -72,7 +72,7 @@ public:
 		ofstream ofs;
 		ofs.open(savename.c_str());
 		if (!ofs.is_open())
-			throw String("Unable to open \"" + savename + "\"");
+			error("Unable to open \"%s\"", savename.c_str());
 		ofs << "QUEST300" << char(0) << gamename << char(0);
 		String tmp = o.str();
 		for (uint i = 0; i < tmp.size(); i ++)
diff --git a/engines/glk/quest/geas_util.cpp b/engines/glk/quest/geas_util.cpp
index bc5eaac..56ea70f 100644
--- a/engines/glk/quest/geas_util.cpp
+++ b/engines/glk/quest/geas_util.cpp
@@ -66,7 +66,7 @@ int eval_int(String s) {
 	//cerr << "symbol == " << symbol << "; find --> "
 	//     << String("+-*/").find (symbol) << endl;
 
-	if (String("+-*/").find(symbol) == String::npos)
+	if (String("+-*/").find(symbol) == (int)String::npos)
 		return arg1;
 
 	++ index;
@@ -171,7 +171,7 @@ String lcase(String s) {
 
 Common::Array<String> split_param(String s) {
 	Common::Array<String> rv;
-	uint c1 = 0, c2;
+	int c1 = 0, c2;
 
 	for (;;) {
 		c2 = s.find(';', c1);
diff --git a/engines/glk/quest/geas_util.h b/engines/glk/quest/geas_util.h
index 2cedadd..d63004e 100644
--- a/engines/glk/quest/geas_util.h
+++ b/engines/glk/quest/geas_util.h
@@ -24,7 +24,7 @@
 #define GLK_QUEST_GEAS_UTIL
 
 #include "glk/quest/read_file.h"
-#include "common/stream.h"
+#include "glk/quest/streams.h"
 
 namespace Glk {
 namespace Quest {
@@ -73,7 +73,7 @@ template<class T> Common::WriteStream &operator<<(Common::WriteStream &o, Common
 template <class KEYTYPE, class VALTYPE>
 bool has(Common::HashMap<KEYTYPE, VALTYPE, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> m, KEYTYPE key) {
 	return m.contains(key);
-};
+}
 
 class Logger {
 public:
diff --git a/engines/glk/quest/quest.cpp b/engines/glk/quest/quest.cpp
index cbddeda..8536f69 100644
--- a/engines/glk/quest/quest.cpp
+++ b/engines/glk/quest/quest.cpp
@@ -96,6 +96,9 @@ void Quest::playGame() {
 			case evtype_Redraw:
 				draw_banner();
 				break;
+			
+			default:
+				break;
 			}
 		}
 	}
diff --git a/engines/glk/quest/streams.cpp b/engines/glk/quest/streams.cpp
index 1b49d2a..bd64df3 100644
--- a/engines/glk/quest/streams.cpp
+++ b/engines/glk/quest/streams.cpp
@@ -60,6 +60,11 @@ Common::WriteStream &operator<<(Common::WriteStream &ws, const String &s) {
 	return ws;
 }
 
+Common::WriteStream &operator<<(Common::WriteStream &ws, const char *s) {
+	ws.write(s, strlen(s));
+	return ws;
+}
+
 Common::WriteStream &operator<<(Common::WriteStream &ws, char c) {
 	ws.writeByte(c);
 	return ws;
diff --git a/engines/glk/quest/streams.h b/engines/glk/quest/streams.h
index 3a8d2a4..8bfa6ae 100644
--- a/engines/glk/quest/streams.h
+++ b/engines/glk/quest/streams.h
@@ -75,9 +75,11 @@ extern const char endl;
 #define cerr (*g_cerr)
 
 Common::WriteStream &operator<<(Common::WriteStream &, const String &);
+Common::WriteStream &operator<<(Common::WriteStream &, const char *);
 Common::WriteStream &operator<<(Common::WriteStream &, char);
 Common::WriteStream &operator<<(Common::WriteStream &, int);
 Common::WriteStream &operator<<(Common::WriteStream &, uint);
+Common::WriteStream &operator<<(Common::WriteStream &, size_t);
 
 } // End of namespace Quest
 } // End of namespace Glk





More information about the Scummvm-git-logs mailing list