[Scummvm-git-logs] scummvm master -> 64b4f13b36030963b507be6e8fc4c4eb7bf22206

dreammaster paulfgilbert at gmail.com
Sat Oct 26 20:16:30 CEST 2019


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

Summary:
b8be17fd02 GLK: LEVEL9: Skeleton engine
d8048720fc GLK: LEVEL9: Added subengine files
8b0250cb09 GLK: LEVEL9: Janitorial
a6b1c9085f GLK: Safer interpreter tag generation for Quetzal save files
0a4d64f55e GLK: LEVEL9: Hooking up engine class to subengine code
25c682793d GLK: LEVEL9: Data loading fixes and cleanup
0ecae64fdc GLK: LEVEL9: Cleaning up initialization
d923ed3c5f GLK: LEVEL9: Fixes for exiting game by closing window
fa323c6187 GLK: LEVEL9: Further variable initialization
07c8437e26 GLK: LEVEL9: Title screen graphics now showing
4332df2bda GLK: LEVEL9: Allow for future graphic scaling
0facfc2b24 GLK: LEVEL9: Moved pre-existing detection code into separate class
51471940a9 GLK: LEVEL9: Move game scanner code to new Scanner class
d9c8237042 GLK: LEVEL9: Hook up new detection
64b4f13b36 GLK: LEVEL9: Compilation fixes


Commit: b8be17fd02fe32d6cea363cc302014090fba4348
    https://github.com/scummvm/scummvm/commit/b8be17fd02fe32d6cea363cc302014090fba4348
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Skeleton engine

Changed paths:
  A engines/glk/level9/detection.cpp
  A engines/glk/level9/detection.h
  A engines/glk/level9/detection_tables.h
  A engines/glk/level9/level9.cpp
  A engines/glk/level9/level9.h
    engines/glk/configure.engine
    engines/glk/detection.cpp
    engines/glk/module.mk


diff --git a/engines/glk/configure.engine b/engines/glk/configure.engine
index e964787..727aa59 100644
--- a/engines/glk/configure.engine
+++ b/engines/glk/configure.engine
@@ -1,6 +1,6 @@
 # This file is included from the main "configure" script
 # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine glk "Glk Interactive Fiction games" no "glk_adrift glk_advsys glk_alan2 glk_alan3 glk_frotz glk_glulxe glk_hugo glk_jacl glk_magnetic glk_quest glk_scott glk_tads" "Base" "16bit freetype2 jpeg png"
+add_engine glk "Glk Interactive Fiction games" no "glk_adrift glk_advsys glk_alan2 glk_alan3 glk_frotz glk_glulxe glk_hugo glk_jacl glk_level9 glk_magnetic glk_quest glk_scott glk_tads" "Base" "16bit freetype2 jpeg png"
 add_engine glk_adrift "ADRIFT" no
 add_engine glk_advsys "AdvSys" no
 add_engine glk_alan2 "Alan2" no
@@ -9,6 +9,7 @@ add_engine glk_frotz "Frotz" no
 add_engine glk_glulxe "Glulxe" no
 add_engine glk_hugo "Hugo" no
 add_engine glk_jacl "JACL" no
+add_engine glk_level9 "Level9" no
 add_engine glk_magnetic "Magnetic" no
 add_engine glk_quest "Quest" no
 add_engine glk_scott "Scott" no
diff --git a/engines/glk/detection.cpp b/engines/glk/detection.cpp
index 6de2a6f..b4d5bff 100644
--- a/engines/glk/detection.cpp
+++ b/engines/glk/detection.cpp
@@ -64,6 +64,11 @@
 #include "glk/jacl/jacl.h"
 #endif
 
+#ifdef ENABLE_GLK_LEVEL9
+#include "glk/level9/detection.h"
+#include "glk/level9/level9.h"
+#endif
+
 #ifdef ENABLE_GLK_MAGNETIC
 #include "glk/magnetic/detection.h"
 #include "glk/magnetic/magnetic.h"
@@ -228,6 +233,10 @@ Common::Error GlkMetaEngine::createInstance(OSystem *syst, Engine **engine) cons
 	if ((*engine = create<Glk::JACL::JACLMetaEngine, Glk::JACL::JACL>(syst, gameDesc)) != nullptr) {}
 	else
 #endif
+#ifdef ENABLE_GLK_LEVEL9
+		if ((*engine = create<Glk::Level9::Level9MetaEngine, Glk::Level9::Level9>(syst, gameDesc)) != nullptr) {}
+		else
+#endif
 #ifdef ENABLE_GLK_MAGNETIC
 	if ((*engine = create<Glk::Magnetic::MagneticMetaEngine, Glk::Magnetic::Magnetic>(syst, gameDesc)) != nullptr) {}
 	else
@@ -303,6 +312,9 @@ PlainGameList GlkMetaEngine::getSupportedGames() const {
 #ifdef ENABLE_GLK_JACL
 	Glk::JACL::JACLMetaEngine::getSupportedGames(list);
 #endif
+#ifdef ENABLE_GLK_LEVEL9
+	Glk::Level9::Level9MetaEngine::getSupportedGames(list);
+#endif
 #ifdef ENABLE_GLK_MAGNETIC
 	Glk::Magnetic::MagneticMetaEngine::getSupportedGames(list);
 #endif
@@ -348,6 +360,9 @@ PlainGameDescriptor GlkMetaEngine::findGame(const char *gameId) const {
 #ifdef ENABLE_GLK_JACL
 	FIND_GAME(JACL);
 #endif
+#ifdef ENABLE_GLK_LEVEL9
+	FIND_GAME(Level9);
+#endif
 #ifdef ENABLE_GLK_MAGNETIC
 	FIND_GAME(Magnetic);
 #endif
@@ -395,6 +410,9 @@ DetectedGames GlkMetaEngine::detectGames(const Common::FSList &fslist) const {
 #ifdef ENABLE_GLK_JACL
 	Glk::JACL::JACLMetaEngine::detectGames(fslist, detectedGames);
 #endif
+#ifdef ENABLE_GLK_LEVEL9
+	Glk::Level9::Level9MetaEngine::detectGames(fslist, detectedGames);
+#endif
 #ifdef ENABLE_GLK_MAGNETIC
 	Glk::Magnetic::MagneticMetaEngine::detectGames(fslist, detectedGames);
 #endif
@@ -437,6 +455,9 @@ void GlkMetaEngine::detectClashes() const {
 #ifdef ENABLE_GLK_JACL
 	Glk::JACL::JACLMetaEngine::detectClashes(map);
 #endif
+#ifdef ENABLE_GLK_LEVEL9
+	Glk::Level9::Level9MetaEngine::detectClashes(map);
+#endif
 #ifdef ENABLE_GLK_MAGNETIC
 	Glk::Magnetic::MagneticMetaEngine::detectClashes(map);
 #endif
diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
new file mode 100644
index 0000000..9904091
--- /dev/null
+++ b/engines/glk/level9/detection.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/level9/detection.h"
+#include "glk/level9/detection_tables.h"
+#include "glk/blorb.h"
+#include "common/debug.h"
+#include "common/file.h"
+#include "common/md5.h"
+#include "engines/game.h"
+
+namespace Glk {
+namespace Level9 {
+
+void Level9MetaEngine::getSupportedGames(PlainGameList &games) {
+	for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
+		games.push_back(*pd);
+	}
+}
+
+GameDescriptor Level9MetaEngine::findGame(const char *gameId) {
+	for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
+		if (!strcmp(gameId, pd->gameId))
+			return *pd;
+	}
+
+	return PlainGameDescriptor();
+}
+
+bool Level9MetaEngine::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(".l9"))
+			continue;
+
+		// Open up the file and calculate the md5
+		Common::File gameFile;
+		if (!gameFile.open(*file))
+			continue;
+
+		Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
+		size_t filesize = gameFile.size();
+		gameFile.seek(0);
+		bool isBlorb = Blorb::isBlorb(gameFile, ID_ADRI);
+		gameFile.close();
+
+		if (!isBlorb && Blorb::hasBlorbExt(filename))
+			continue;
+
+		// Check for known games
+		const GlkDetectionEntry *p = LEVEL9_GAMES;
+		while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize))
+			++p;
+
+		if (!p->_gameId) {
+			const PlainGameDescriptor &desc = LEVEL9_GAME_LIST[0];
+			gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
+		} else {
+			PlainGameDescriptor gameDesc = findGame(p->_gameId);
+			gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, p->_extra, filename, p->_language));
+		}
+	}
+
+	return !gameList.empty();
+}
+
+void Level9MetaEngine::detectClashes(Common::StringMap &map) {
+	for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
+		if (map.contains(pd->gameId))
+			error("Duplicate game Id found - %s", pd->gameId);
+		map[pd->gameId] = "";
+	}
+}
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
new file mode 100644
index 0000000..386e475
--- /dev/null
+++ b/engines/glk/level9/detection.h
@@ -0,0 +1,63 @@
+/* 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_LEVEL9_DETECTION
+#define GLK_LEVEL9_DETECTION
+
+#include "common/fs.h"
+#include "common/hash-str.h"
+#include "engines/game.h"
+#include "glk/detection.h"
+
+namespace Glk {
+namespace Level9 {
+
+/**
+ * Meta engine for Level 9 interpreter
+ */
+class Level9MetaEngine {
+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 Level9
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/level9/detection_tables.h b/engines/glk/level9/detection_tables.h
new file mode 100644
index 0000000..12b3b0e
--- /dev/null
+++ b/engines/glk/level9/detection_tables.h
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/game.h"
+#include "common/gui_options.h"
+#include "common/language.h"
+
+namespace Glk {
+namespace Level9 {
+
+// TODO: The list of Level 9 games and detection entries needs to be filled out
+const PlainGameDescriptor LEVEL9_GAME_LIST[] = {
+	{ "level9", "Level 9 IF Game" },
+
+	{ nullptr, nullptr }
+};
+
+const GlkDetectionEntry LEVEL9_GAMES[] = {
+	DT_END_MARKER
+};
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
new file mode 100644
index 0000000..5cae3dc
--- /dev/null
+++ b/engines/glk/level9/level9.cpp
@@ -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.
+ *
+ */
+
+#include "glk/level9/level9.h"
+
+namespace Glk {
+namespace Level9 {
+
+Level9 *g_vm = nullptr;
+
+Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
+	g_vm = this;
+}
+
+void Level9::runGame() {
+	// TODO
+}
+
+Common::Error Level9::readSaveData(Common::SeekableReadStream *rs) {
+	// TODO
+	return Common::kNoError;
+}
+
+Common::Error Level9::writeGameData(Common::WriteStream *ws) {
+	// TODO
+	return Common::kNoError;
+}
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/level9.h b/engines/glk/level9/level9.h
new file mode 100644
index 0000000..90c441e
--- /dev/null
+++ b/engines/glk/level9/level9.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_LEVEL9_LEVEL9
+#define GLK_LEVEL9_LEVEL9
+
+#include "common/scummsys.h"
+#include "common/serializer.h"
+#include "common/stack.h"
+#include "glk/glk_api.h"
+
+namespace Glk {
+namespace Level9 {
+
+/**
+ * Level 9 game interpreter
+ */
+class Level9 : public GlkAPI {
+private:
+	/**
+	 * Initialization
+	 */
+	bool initialize();
+
+	/**
+	 * Deinitialization
+	 */
+	void deinitialize();
+public:
+	/**
+	 * Constructor
+	 */
+	Level9(OSystem *syst, const GlkGameDescription &gameDesc);
+
+	/**
+	 * Run the game
+	 */
+	void runGame();
+
+	/**
+	 * Returns the running interpreter type
+	 */
+	virtual InterpreterType getInterpreterType() const override {
+		return INTERPRETER_LEVEL9;
+	}
+
+	/**
+	 * 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 Level9 *g_vm;
+
+} // End of namespace Alan2
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 4da1002..3e474cc 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -226,6 +226,12 @@ MODULE_OBJS += \
 	jacl/utils.o
 endif
 
+ifdef ENABLE_GLK_LEVEL9
+MODULE_OBJS += \
+	level9/detection.o \
+	level9/level9.o
+endif
+
 ifdef ENABLE_GLK_MAGNETIC
 MODULE_OBJS += \
 	magnetic/detection.o \


Commit: d8048720fc6c1104368c995022285462cf9f32f9
    https://github.com/scummvm/scummvm/commit/d8048720fc6c1104368c995022285462cf9f32f9
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Added subengine files

Changed paths:
  A engines/glk/level9/bitmap.cpp
  A engines/glk/level9/level9_main.cpp
  A engines/glk/level9/level9_main.h
  A engines/glk/level9/os_glk.cpp
    engines/glk/module.mk


diff --git a/engines/glk/level9/bitmap.cpp b/engines/glk/level9/bitmap.cpp
new file mode 100644
index 0000000..976c8a1
--- /dev/null
+++ b/engines/glk/level9/bitmap.cpp
@@ -0,0 +1,1460 @@
+/* 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/level9/level9_main.h"
+#include "common/file.h"
+
+namespace Glk {
+namespace Level9 {
+
+extern Bitmap *bitmap;
+
+void L9Allocate(L9BYTE **ptr, L9UINT32 Size);
+
+L9BOOL bitmap_exists(char *file) {
+	return Common::File::exists(file);
+}
+
+L9BYTE *bitmap_load(char *file, L9UINT32 *size) {
+	L9BYTE *data = NULL;
+
+	Common::File f;
+	if (f.open(file)) {
+		*size = f.size();
+		L9Allocate(&data, *size);
+		f.read(data, *size);
+
+		f.close();
+	}
+	return data;
+}
+
+Bitmap *bitmap_alloc(int x, int y) {
+	Bitmap *b = NULL;
+	L9Allocate((L9BYTE **)&b, sizeof(Bitmap) + (x * y));
+
+	b->width = x;
+	b->height = y;
+	b->bitmap = ((L9BYTE *)b) + sizeof(Bitmap);
+	b->npalette = 0;
+	return b;
+}
+
+/*
+    A PC or ST palette colour is a sixteen bit value in which the low three nybbles
+    hold the rgb colour values. The lowest nybble holds the blue value, the
+    second nybble the blue value and the third nybble the red value. (The high
+    nybble is ignored). Within each nybble, only the low three bits are used
+    IE the value can only be 0-7 not the full possible 0-15 and so the MSbit in
+    each nybble is always 0.
+*/
+Colour bitmap_pcst_colour(int big, int small) {
+	Colour col;
+	L9UINT32 r = big & 0xF;
+	L9UINT32 g = (small >> 4) & 0xF;
+	L9UINT32 b = small & 0xF;
+
+	r *= 0x49;
+	r >>= 1;
+	g *= 0x49;
+	g >>= 1;
+	b *= 0x49;
+	b >>= 1;
+
+	col.red = (L9BYTE)(r & 0xFF);
+	col.green = (L9BYTE)(g & 0xFF);
+	col.blue = (L9BYTE)(b & 0xFF);
+	return col;
+}
+
+/*
+    ST Bitmaps
+
+    On the ST different graphics file formats were used for the early V4
+    games (Knight Orc, Gnome Ranger) and the later V4 games (Lancelot,
+    Ingrid's Back, Time & Magik and Scapeghost).
+*/
+
+/*
+    Extracts the number of pixels requested from an eight-byte data block (4 bit-
+    planes) passed to it.
+
+    Note:   On entry each one of four pointers is set to point to the start of each
+            bit-plane in the block. The function then indexes through each byte in
+            each bit plane. and uses shift and mask operations to extract each four
+            bit pixel into an L9PIXEL.
+
+            The bit belonging to the pixel in the current byte of the current bit-
+            plane is moved to its position in an eight-bit pixel. The byte is then
+            masked by a value to select only that bit and added to the final pixel
+            value.
+*/
+L9UINT32 bitmap_st1_decode_pixels(L9BYTE *pic, L9BYTE *data, L9UINT32 count, L9UINT32 pixels) {
+	L9UINT32 bitplane_length = count / 4; /* length of each bitplane */
+	L9BYTE *bitplane0 = data; /* address of bit0 bitplane */
+	L9BYTE *bitplane1 = data + (bitplane_length); /* address of bit1 bitplane */
+	L9BYTE *bitplane2 = data + (bitplane_length * 2); /* address of bit2 bitplane */
+	L9BYTE *bitplane3 = data + (bitplane_length * 3); /* address of bit3 bitplane */
+	L9UINT32 bitplane_index, pixel_index = 0; /* index variables */
+
+	for (bitplane_index = 0; bitplane_index < bitplane_length; bitplane_index++) {
+		/* build the eight pixels from the current bitplane bytes, high bit to low */
+
+		/* bit7 byte */
+		pic[pixel_index] = ((bitplane3[bitplane_index] >> 4) & 0x08)
+		                   + ((bitplane2[bitplane_index] >> 5) & 0x04)
+		                   + ((bitplane1[bitplane_index] >> 6) & 0x02)
+		                   + ((bitplane0[bitplane_index] >> 7) & 0x01);
+		if (pixels == ++pixel_index)
+			break;
+
+		/* bit6 byte */
+		pic[pixel_index] = ((bitplane3[bitplane_index] >> 3) & 0x08)
+		                   + ((bitplane2[bitplane_index] >> 4) & 0x04)
+		                   + ((bitplane1[bitplane_index] >> 5) & 0x02)
+		                   + ((bitplane0[bitplane_index] >> 6) & 0x01);
+		if (pixels == ++pixel_index)
+			break;
+
+		/* bit5 byte */
+		pic[pixel_index] = ((bitplane3[bitplane_index] >> 2) & 0x08)
+		                   + ((bitplane2[bitplane_index] >> 3) & 0x04)
+		                   + ((bitplane1[bitplane_index] >> 4) & 0x02)
+		                   + ((bitplane0[bitplane_index] >> 5) & 0x01);
+		if (pixels == ++pixel_index)
+			break;
+
+		/* bit4 byte */
+		pic[pixel_index] = ((bitplane3[bitplane_index] >> 1) & 0x08)
+		                   + ((bitplane2[bitplane_index] >> 2) & 0x04)
+		                   + ((bitplane1[bitplane_index] >> 3) & 0x02)
+		                   + ((bitplane0[bitplane_index] >> 4) & 0x01);
+		if (pixels == ++pixel_index)
+			break;
+
+		/* bit3 byte */
+		pic[pixel_index] = ((bitplane3[bitplane_index]) & 0x08)
+		                   + ((bitplane2[bitplane_index] >> 1) & 0x04)
+		                   + ((bitplane1[bitplane_index] >> 2) & 0x02)
+		                   + ((bitplane0[bitplane_index] >> 3) & 0x01);
+		if (pixels == ++pixel_index)
+			break;
+
+		/* bit2 byte */
+		pic[pixel_index] = ((bitplane3[bitplane_index] << 1) & 0x08)
+		                   + ((bitplane2[bitplane_index]) & 0x04)
+		                   + ((bitplane1[bitplane_index] >> 1) & 0x02)
+		                   + ((bitplane0[bitplane_index] >> 2) & 0x01);
+		if (pixels == ++pixel_index)
+			break;
+
+		/* bit1 byte */
+		pic[pixel_index] = ((bitplane3[bitplane_index] << 2) & 0x08)
+		                   + ((bitplane2[bitplane_index] << 1) & 0x04)
+		                   + ((bitplane1[bitplane_index]) & 0x02)
+		                   + ((bitplane0[bitplane_index] >> 1) & 0x01);
+		if (pixels == ++pixel_index)
+			break;
+
+		/* bit0 byte */
+		pic[pixel_index] = ((bitplane3[bitplane_index] << 3) & 0x08)
+		                   + ((bitplane2[bitplane_index] << 2) & 0x04)
+		                   + ((bitplane1[bitplane_index] << 1) & 0x02)
+		                   + ((bitplane0[bitplane_index]) & 0x01);
+		if (pixels == ++pixel_index)
+			break;
+	}
+
+	return pixel_index;
+}
+
+/*
+    The ST image file has the following format. It consists of a 44 byte header
+    followed by the image data.
+
+    The header has the following format:
+    Bytes 0-31:     sixteen entry ST palette
+    Bytes 32-33:    padding
+    Bytes 34-35:    big-endian word holding number of bitplanes needed to make
+                up a row of pixels*
+    Bytes 36-37:    padding
+    Bytes 38-39:    big-endian word holding number of rows in the image*
+    Bytes 40-41:    padding**
+    Bytes 42-43:    mask for pixels to show in last 16 pixel block. Again, this
+                is big endian
+
+    [*]     these are probably big-endian unsigned longs but I have designated
+            the upper two bytes as padding because (a) Level 9 does not need
+            them as longs and (b) using unsigned shorts reduces byte sex induced
+            byte order juggling.
+    [**]    not certain what this is for but I suspect that, like bytes 42-43
+            it is a mask to indicate which pixels to show, in this case in the
+            first 16 pixel block
+
+    The image data is essentially a memory dump of the video RAM representing
+    the image in lo-res mode. In lo-res mode each row is 320 pixels wide
+    and each pixel can be any one of sixteen colours - needs 4 bits to store.
+
+    In the ST video memory (in lo-res mode which we are dealing with here)
+    is organised as follows. The lowest point in memory in the frame buffer
+    represents the top-left of the screen, the highest the bottom-right.
+    Each row of pixels is stored in sequence.
+
+    Within each pixel row the pixels are stored as follows. Each row is
+    divided into groups of 16 pixels. Each sixteen pixel group is stored
+    in 8 bytes, logically four groups of two. Each two byte pair
+    is a bit-plane for that sixteen pixel group - that is it stores the
+    same bit of each pixel in that group. The first two bytes store the
+    lowest bit, the second pair the second bit &c.
+
+    The word at bytes 34-35 of the header stores the number of bitplanes
+    that make up each pixel row in the image. Multplying this number by
+    four gives the number of pixels in the row***. For title and frame
+    images that will be 320, for sub-images it will be less.
+
+    [***]   Not always exactly. For GnomeRanger sub-images this value is 60
+            - implying there are 240 pixels per row. In fact there are only
+            225 pixels in each row. To identify this situation look at the
+            big-endian word in bytes 42-43 of the header. This is a mask
+            telling you the pixels to use. Each bit represents one pixel in
+            the block, with the MSBit representing the first pixel and the
+            LSbit the last.
+
+            In this situation, the file does contain the entire sixteen
+            pixel block (it has to with the bitplane arrangement) but
+            the pixels which are not part of the image are just noise. When
+            decoding the image, the L9BITMAP produced has the actual pixel
+            dimensions - the surplus pixels are discarded.
+
+            I suspect, though I have not found an instance, that in theory
+            the same situation could apply at the start of a pixel row and that
+            in this case the big-endian word at bytes 40-41 is the mask.
+
+    Having obtained the pixel dimensions of the image the function uses
+    them to allocate memory for the bitmap and then extracts the pixel
+    information from the bitmap row by row. For each row eight byte blocks
+    are read from the image data and passed to UnpackSTv1Pixels along with
+    the number of pixels to extract (usually 16, possibly less for the last
+    block in a row.)
+*/
+L9BOOL bitmap_st1_decode(char *file, int x, int y) {
+	L9BYTE *data = NULL;
+	int i, xi, yi, max_x, max_y, last_block;
+	int bitplanes_row, bitmaps_row, pixel_count, get_pixels;
+
+	L9UINT32 size;
+	data = bitmap_load(file, &size);
+	if (data == NULL)
+		return FALSE;
+
+	bitplanes_row = data[35] + data[34] * 256;
+	bitmaps_row = bitplanes_row / 4;
+	max_x = bitplanes_row * 4;
+	max_y = data[39] + data[38] * 256;
+	last_block = data[43] + data[42] * 256;
+
+	/* Check if sub-image with rows shorter than max_x */
+	if (last_block != 0xFFFF) {
+		/* use last_block to adjust max_x */
+		i = 0;
+		while ((0x0001 & last_block) == 0) { /* test for ls bit set */
+			last_block >>= 1; /* if not, shift right one bit */
+			i++;
+		}
+		max_x = max_x - i;
+	}
+
+	if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+		free(data);
+		return FALSE;
+	}
+
+	if ((x == 0) && (y == 0)) {
+		if (bitmap)
+			free(bitmap);
+		bitmap = bitmap_alloc(max_x, max_y);
+	}
+	if (bitmap == NULL) {
+		free(data);
+		return FALSE;
+	}
+
+	if (x + max_x > bitmap->width)
+		max_x = bitmap->width - x;
+	if (y + max_y > bitmap->height)
+		max_y = bitmap->height - y;
+
+	for (yi = 0; yi < max_y; yi++) {
+		pixel_count = 0;
+		for (xi = 0; xi < bitmaps_row; xi++) {
+			if ((max_x - pixel_count) < 16)
+				get_pixels = max_x - pixel_count;
+			else
+				get_pixels = 16;
+
+			pixel_count += bitmap_st1_decode_pixels(
+			                   bitmap->bitmap + ((y + yi) * bitmap->width) + x + (xi * 16),
+			                   data + 44 + (yi * bitplanes_row * 2) + (xi * 8), 8, get_pixels);
+		}
+	}
+
+	bitmap->npalette = 16;
+	for (i = 0; i < 16; i++)
+		bitmap->palette[i] = bitmap_pcst_colour(data[(i * 2)], data[1 + (i * 2)]);
+
+	free(data);
+	return TRUE;
+}
+
+void bitmap_st2_name(int num, char *dir, char *out) {
+	/* title picture is #30 */
+	if (num == 0)
+		num = 30;
+	sprintf(out, "%s%d.squ", dir, num);
+}
+
+/*
+    PC Bitmaps
+
+    On the PC different graphics file formats were used for the early V4
+    games (Knight Orc, Gnome Ranger) and the later V4 games (Lancelot,
+    Ingrid's Back, Time & Magik and Scapeghost).
+
+    The ST and the PC both use the same image file format for the later
+    V4 games (Lancelot, Ingrid's Back, Time & Magik and Scapeghost.)
+*/
+
+void bitmap_pc_name(int num, char *dir, char *out) {
+	/* title picture is #30 */
+	if (num == 0)
+		num = 30;
+	sprintf(out, "%s%d.pic", dir, num);
+}
+
+/*
+    The EGA standard for the IBM PCs and compatibles defines 64 colors, any
+    16 of which can be mapped to the usable palette at any given time. If
+    you display these 64 colors in numerical order, 16 at a time, you get a
+    hodgepodge of colors in no logical order. The 64 EGA color numbers are
+    assigned in a way that the numbers can easily be converted to a relative
+    intensity of each of the three phosphor colors R,G,B. If the number is
+    converted to six bit binary, the most significant three bits represent
+    the 25% level of R,G,B in that order and the least significant three
+    bits represent the 75% level of R,G,B in that order. Take EGA color 53
+    for example. In binary, 53 is 110101. Since both R bits are on, R = 1.0.
+    Of the G bits only the 25% bit is on so G = 0.25. Of the B bits only the
+    75% bit is on so B = 0.75.
+*/
+Colour bitmap_pc1_colour(int i) {
+	Colour col;
+	col.red = (((i & 4) >> 1) | ((i & 0x20) >> 5)) * 0x55;
+	col.green = ((i & 2) | ((i & 0x10) >> 4)) * 0x55;
+	col.blue = (((i & 1) << 1) | ((i & 8) >> 3)) * 0x55;
+	return col;
+}
+
+/*
+    The PC (v1) image file has the following format. It consists of a 22
+    byte header organised like this:
+
+    Byte 0:     probably a file type flag
+    Byte 1:     the MSB of the file's length as a word
+    Bytes 2-3:  little-endian word with picture width in pixels
+    Bytes 4-5:  little-endian word with picture height in pixel rows
+    Bytes 6-21: the image colour table. One EGA colour in each byte
+
+    The image data is extremely simple. The entire block is packed array
+    of 4-bit pixels - IE each byte holds two pixels - the first in the high
+    nybble, the second in the low. The pixel value is an index into the
+    image colour table. The pixels are organised with the top left first and
+    bottom left last, each row in turn.
+*/
+L9BOOL bitmap_pc1_decode(char *file, int x, int y) {
+	L9BYTE *data = NULL;
+	int i, xi, yi, max_x, max_y;
+
+	L9UINT32 size;
+	data = bitmap_load(file, &size);
+	if (data == NULL)
+		return FALSE;
+
+	max_x = data[2] + data[3] * 256;
+	max_y = data[4] + data[5] * 256;
+	if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+		free(data);
+		return FALSE;
+	}
+
+	if ((x == 0) && (y == 0)) {
+		if (bitmap)
+			free(bitmap);
+		bitmap = bitmap_alloc(max_x, max_y);
+	}
+	if (bitmap == NULL) {
+		free(data);
+		return FALSE;
+	}
+
+	if (x + max_x > bitmap->width)
+		max_x = bitmap->width - x;
+	if (y + max_y > bitmap->height)
+		max_y = bitmap->height - y;
+
+	for (yi = 0; yi < max_y; yi++) {
+		for (xi = 0; xi < max_x; xi++) {
+			bitmap->bitmap[(bitmap->width * (y + yi)) + (x + xi)] =
+			    (data[23 + ((yi * max_x) / 2) + (xi / 2)] >> ((1 - (xi & 1)) * 4)) & 0x0f;
+		}
+	}
+
+	bitmap->npalette = 16;
+	for (i = 0; i < 16; i++)
+		bitmap->palette[i] = bitmap_pc1_colour(data[6 + i]);
+
+	free(data);
+	return TRUE;
+}
+
+/*
+    The PC (v2) image file has the following format. It consists of a 44
+    byte header followed by the image data.
+
+    The header has the following format:
+    Bytes 0-1: "datalen":   length of file -1 as a big-endian word*
+    Bytes 2-3: "flagbyte1 & flagbyte2": unknown, possibly type identifiers.
+        Usually 0xFF or 0xFE followed by 0x84, 0x72, 0xFF, 0xFE or
+        some other (of a fairly small range of possibles) byte.
+    Bytes 4-35: "colour_index[]": sixteen entry palette. Basically an ST
+        palette (even if in a PC image file. Each entry is a sixteen
+        bit value in which the low three nybbles hold the rgb colour
+        values. The lowest nybble holds the blue value, the second
+        nybble the blue value and the third nybble the red value. (The
+        high nybble is ignored). Within each nybble, only the low
+        three bits are used IE the value can only be 0-7 not the full
+        possible 0-15 and so the MSbit in each nybble is always 0.**,
+    Bytes 36-37: "width": image width in pixels as a big-endian word
+    Bytes 38-39: "numrows": image height in pixel rows as a big-endian word
+    Byte 40: "seedByte": seed byte to start picture decoding.
+    Byte 41: "padByte": unknown. Possibly padding to word align the next
+        element?
+    Bytes 42-297: "pixelTable": an array of 0x100 bytes used as a lookup table
+        for pixel values
+    Bytes 298-313: "bitStripTable": an array of 0x10 bytes used as a lookup table
+        for the number of bytes to strip from the bit stream for the pixel being
+        decoded
+    Bytes 314-569:  "indexByteTable": an array of 0x100 bytes used as a lookup
+        table to index into bitStripTable and pixelTable****
+
+    The encoded image data then follows ending in a 0x00 at the file length stored
+    in the first two bytes of the file. there is then one extra byte holding a
+    checksum produced by the addition of all the bytes in the file (except the first
+    two and itself)*
+
+    [*] in some PC games the file is padded out beyond this length to the
+    nearest 0x80/0x00 boundary with the byte 0x1A. The valid data in the
+    file still finishes where this word says with the checkbyte following it.
+    [**] I imagine when a game was running on a PC this standard palette
+    was algorithimcally changed to suit the graphics mode being used
+    (Hercules, MDA, CGA, EGA, MCGA, VGA &c.)
+    [***]   Note also, in image 1 of PC Time & Magik I think one palette entry
+    is bad as what should be white in the image is actually set to
+    a very pale yellow. This is corrected with the display of the next
+    sub-picture and I am pretty sure it is note a decoding problem
+    here as when run on the PC the same image has the same pale yellow
+    cast.
+    [****] for detail of how all this works see below
+
+    As this file format is intended for two very different platforms the decoded
+    imaged data is in a neutral, intermediate form. Each pixel is extracted as a
+    byte with only the low four bits significant. The pixel value is an index into
+    the sixteen entry palette.
+
+    The pixel data is compressed, presumably to allow a greater number of images
+    to be distributed on the (rather small) default ST & PC floppy disks (in both
+    cases about 370 Kbytes.)*****
+
+    Here's how to decode the data. The image data is actually a contiguous bit
+    stream with the byte structure on disk having almost no relevance to the
+    encoding. We access the bit stream via a two-byte buffer arranged as a word.
+
+    Preparation:
+
+    Initially, move the first byte from the image data into the low byte of
+    theBitStreamBuffer and move the second byte of the image data into the
+    high byte of theBitStreamBuffer.
+
+    Set a counter (theBufferBitCounter) to 8 which you will use to keep track
+    of when it is necesary to refill the buffer.
+
+    Set a L9BYTE variable (theNewPixel) to byte 40 (seedByte) of the header.
+    We need to do this because as part of identifying the pixel being
+    extracted we need to know the value of the previous pixel extracted. Since
+    none exists at this point we must prime this variable with the correct
+    value.
+
+    Extraction:
+
+    Set up a loop which you will execute once for each pixel to be extracted
+    and within that loop do as follows.
+
+    Copy the low byte of theBitStreamBuffer to an L9BYTE
+    (theNewPixelIndexSelector). Examine theNewPixelIndexSelector. If this
+    is 0xFF this flags that the index to the new pixel is present as a
+    literal in the bit stream; if it is NOT 0xFF then the new pixel index
+    value has to be decoded.
+
+    If theNewPixelIndexSelector is NOT 0xFF do as follows:
+
+    Set the variable theNewPixelIndex to the byte in the
+    indexByteTable array of the header indexed by
+    theNewPixelIndexSelector.
+
+    Set the variable theBufferBitStripCount to the value in the
+    bitStripTable array of the header indexed by theNewPixelIndex.
+
+    One-by-one use right bit shift (>>) to remove
+    theBufferBitStripCount bits from theBitStreamBuffer. After each
+    shift decrement theBufferBitCounter and check whether it has
+    reached 0. If it has, get the next byte from the image data and
+    insert it in the high byte of theBitStreamBuffer and reset
+    theBufferBitCounter to 8. What is happening here is as we remove
+    each bit from the bottom of the bit stream buffer we check to see
+    if there are any bits left in the high byte of the buffer. As soon
+    as we know there are none, we refill it with the next eight bits
+    from the image data.
+
+    When this 'bit-stripping' is finished, other than actually identifying
+    the new pixel we are nearly done. I will leave that for the moment and
+    look at what happens if the low byte of theBitStreamBuffer we put in
+    theNewPixelIndexSelector was actually 0xFF:
+
+    In this case, instead of the above routine we begin by removing
+    the low eight bits from the theBitStreamBuffer. We use the same
+    ono-by-one bit shift right process described above to do this,
+    again checking after each shift if it is necesary to refill the
+    buffer's high byte.
+
+    When the eight bits have been removed we set theNewPixelIndex to
+    the value of the low four bits of theBitStreamBuffer. Having done
+    that we again one-by-one strip off those low four bits from the
+    theBitStreamBuffer, again checking if we need to refill the buffer
+    high byte.
+
+    Irrespective of whether we initially had 0xFF in
+    theNewPixelIndexSelector we now have a new value in theNewPixelIndex.
+    This value is used as follows to obtain the new pixel value.
+
+    The variable theNewPixel contains either the seedByte or the value of
+    the previously extracted pixel. In either case this is a 4-bit value
+    in the lower 4 bits. Use the left bit shift operator (or multiply by
+    16) to shift those four bits into the high four bits of theNewPixel.
+
+    Add the value in theNewPixelIndex (it is a 4-bit value) to
+    theNewPixel. The resulting value is used as an index into the
+    pixelTable array of the header to get the actual new pixel value so
+    theNewPixel = header.pixelTable[theNewPixel] gets us our new pixel and
+    primes theNewPixel for the same process next time around the loop.
+
+    Having got our new pixel it is stored in the next empty space in the
+    bitmap and we loop back and start again.
+
+    [*****] I am not sure how the compression was done - someone with a better
+    understanding of this area may be able to work out the method from the above.
+    I worked out how to decode it by spending many, many hours tracing through the
+    code in a debugger - thanks to the now defunct HiSoft for their DevPac ST and
+    Gerin Philippe for NoSTalgia <http://users.skynet.be/sky39147/>.
+*/
+L9BOOL bitmap_pc2_decode(char *file, int x, int y) {
+	L9BYTE *data = NULL;
+	int i, xi, yi, max_x, max_y;
+
+	L9BYTE theNewPixel, theNewPixelIndex;
+	L9BYTE theBufferBitCounter, theNewPixelIndexSelector, theBufferBitStripCount;
+	L9UINT16 theBitStreamBuffer, theImageDataIndex;
+	L9BYTE *theImageFileData;
+
+	L9UINT32 size;
+	data = bitmap_load(file, &size);
+	if (data == NULL)
+		return FALSE;
+
+	max_x = data[37] + data[36] * 256;
+	max_y = data[39] + data[38] * 256;
+	if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+		free(data);
+		return FALSE;
+	}
+
+	if ((x == 0) && (y == 0)) {
+		if (bitmap)
+			free(bitmap);
+		bitmap = bitmap_alloc(max_x, max_y);
+	}
+	if (bitmap == NULL) {
+		free(data);
+		return FALSE;
+	}
+
+	if (x + max_x > bitmap->width)
+		max_x = bitmap->width - x;
+	if (y + max_y > bitmap->height)
+		max_y = bitmap->height - y;
+
+	/* prime the new pixel variable with the seed byte */
+	theNewPixel = data[40];
+	/* initialise the index to the image data */
+	theImageDataIndex = 0;
+	/* prime the bit stream buffer */
+	theImageFileData = data + 570;
+	theBitStreamBuffer = theImageFileData[theImageDataIndex++];
+	theBitStreamBuffer = theBitStreamBuffer +
+	                     (0x100 * theImageFileData[theImageDataIndex++]);
+	/* initialise the bit stream buffer bit counter */
+	theBufferBitCounter = 8;
+
+	for (yi = 0; yi < max_y; yi++) {
+		for (xi = 0; xi < max_x; xi++) {
+			theNewPixelIndexSelector = (theBitStreamBuffer & 0x00FF);
+			if (theNewPixelIndexSelector != 0xFF) {
+				/* get index for new pixel and bit strip count */
+				theNewPixelIndex = (data + 314)[theNewPixelIndexSelector];
+				/* get the bit strip count */
+				theBufferBitStripCount = (data + 298)[theNewPixelIndex];
+				/* strip theBufferBitStripCount bits from theBitStreamBuffer */
+				while (theBufferBitStripCount > 0) {
+					theBitStreamBuffer = theBitStreamBuffer >> 1;
+					theBufferBitStripCount--;
+					theBufferBitCounter--;
+					if (theBufferBitCounter == 0) {
+						/* need to refill the theBitStreamBuffer high byte */
+						theBitStreamBuffer = theBitStreamBuffer +
+						                     (0x100 * theImageFileData[theImageDataIndex++]);
+						/* re-initialise the bit stream buffer bit counter */
+						theBufferBitCounter = 8;
+					}
+				}
+			} else {
+				/* strip the 8 bits holding 0xFF from theBitStreamBuffer */
+				theBufferBitStripCount = 8;
+				while (theBufferBitStripCount > 0) {
+					theBitStreamBuffer = theBitStreamBuffer >> 1;
+					theBufferBitStripCount--;
+					theBufferBitCounter--;
+					if (theBufferBitCounter == 0) {
+						/* need to refill the theBitStreamBuffer high byte */
+						theBitStreamBuffer = theBitStreamBuffer +
+						                     (0x100 * theImageFileData[theImageDataIndex++]);
+						/* re-initialise the bit stream buffer bit counter */
+						theBufferBitCounter = 8;
+					}
+				}
+				/* get the literal pixel index value from the bit stream */
+				theNewPixelIndex = (0x000F & theBitStreamBuffer);
+				theBufferBitStripCount = 4;
+				/* strip 4 bits from theBitStreamBuffer */
+				while (theBufferBitStripCount > 0) {
+					theBitStreamBuffer = theBitStreamBuffer >> 1;
+					theBufferBitStripCount--;
+					theBufferBitCounter--;
+					if (theBufferBitCounter == 0) {
+						/* need to refill the theBitStreamBuffer high byte */
+						theBitStreamBuffer = theBitStreamBuffer +
+						                     (0x100 * theImageFileData[theImageDataIndex++]);
+						/* re-initialise the bit stream buffer bit counter */
+						theBufferBitCounter = 8;
+					}
+				}
+			}
+
+			/* shift the previous pixel into the high four bits of theNewPixel */
+			theNewPixel = (0xF0 & (theNewPixel << 4));
+			/* add the index to the new pixel to theNewPixel */
+			theNewPixel = theNewPixel + theNewPixelIndex;
+			/* extract the nex pixel from the table */
+			theNewPixel = (data + 42)[theNewPixel];
+			/* store new pixel in the bitmap */
+			bitmap->bitmap[(bitmap->width * (y + yi)) + (x + xi)] = theNewPixel;
+		}
+	}
+
+	bitmap->npalette = 16;
+	for (i = 0; i < 16; i++)
+		bitmap->palette[i] = bitmap_pcst_colour(data[4 + (i * 2)], data[5 + (i * 2)]);
+
+	free(data);
+	return TRUE;
+}
+
+BitmapType bitmap_pc_type(char *file) {
+	BitmapType type = PC2_BITMAPS;
+
+	Common::File f;
+	if (f.open(file)) {
+		L9BYTE data[6];
+		int x, y;
+
+		if (f.read(data, sizeof(data)) != sizeof(data) && !f.eos())
+			return NO_BITMAPS;
+		f.close();
+
+		x = data[2] + data[3] * 256;
+		y = data[4] + data[5] * 256;
+
+		if ((x == 0x0140) && (y == 0x0087))
+			type = PC1_BITMAPS;
+		if ((x == 0x00E0) && (y == 0x0074))
+			type = PC1_BITMAPS;
+		if ((x == 0x0140) && (y == 0x0087))
+			type = PC1_BITMAPS;
+		if ((x == 0x00E1) && (y == 0x0076))
+			type = PC1_BITMAPS;
+	}
+
+	return type;
+}
+
+/*
+    Amiga Bitmaps
+*/
+
+void bitmap_noext_name(int num, char *dir, char *out) {
+	if (num == 0) {
+		sprintf(out, "%stitle", dir);
+		if (Common::File::exists(out))
+			return;
+
+		num = 30;
+	}
+
+	sprintf(out, "%s%d", dir, num);
+}
+
+int bitmap_amiga_intensity(int col) {
+	return (int)(pow((double)col / 15, 1.0 / 0.8) * 0xff);
+}
+
+/*
+    Amiga palette colours are word length structures with the red, green and blue
+    values stored in the second, third and lowest nybles respectively. The high
+    nybble is always zero.
+*/
+Colour bitmap_amiga_colour(int i1, int i2) {
+	Colour col;
+	col.red = bitmap_amiga_intensity(i1 & 0xf);
+	col.green = bitmap_amiga_intensity(i2 >> 4);
+	col.blue = bitmap_amiga_intensity(i2 & 0xf);
+	return col;
+}
+
+/*
+    The Amiga image file has the following format. It consists of a 44 byte
+    header followed by the image data.
+
+    The header has the following format:
+    Bytes 0-63:  thirty-two entry Amiga palette
+    Bytes 64-65: padding
+    Bytes 66-67: big-endian word holding picture width in pixels*
+    Bytes 68-69: padding
+    Bytes 70-71: big-endian word holding number of pixel rows in the image*
+
+    [*] these are probably big-endian unsigned longs but I have designated
+    the upper two bytes as padding because (a) Level 9 does not need
+    them as longs and (b) using unsigned shorts reduces byte sex induced
+    byte order juggling.
+
+    The images are designed for an Amiga low-res mode screen - that is they
+    assume a 320*256 (or 320 * 200 if NSTC display) screen with a palette of
+    32 colours from the possible 4096.
+
+    The image data is organised the same way that Amiga video memory is. The
+    entire data block is divided into five equal length bit planes with the
+    first bit plane holding the low bit of each 5-bit pixel, the second bitplane
+    the second bit of the pixel and so on up to the fifth bit plane holding the
+    high bit of the f5-bit pixel.
+*/
+L9BOOL bitmap_amiga_decode(char *file, int x, int y) {
+	L9BYTE *data = NULL;
+	int i, xi, yi, max_x, max_y, p, b;
+
+	L9UINT32 size;
+	data = bitmap_load(file, &size);
+	if (data == NULL)
+		return FALSE;
+
+	max_x = (((((data[64] << 8) | data[65]) << 8) | data[66]) << 8) | data[67];
+	max_y = (((((data[68] << 8) | data[69]) << 8) | data[70]) << 8) | data[71];
+	if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+		free(data);
+		return FALSE;
+	}
+
+	if ((x == 0) && (y == 0)) {
+		if (bitmap)
+			free(bitmap);
+		bitmap = bitmap_alloc(max_x, max_y);
+	}
+	if (bitmap == NULL) {
+		free(data);
+		return FALSE;
+	}
+
+	if (x + max_x > bitmap->width)
+		max_x = bitmap->width - x;
+	if (y + max_y > bitmap->height)
+		max_y = bitmap->height - y;
+
+	for (yi = 0; yi < max_y; yi++) {
+		for (xi = 0; xi < max_x; xi++) {
+			p = 0;
+			for (b = 0; b < 5; b++)
+				p |= ((data[72 + (max_x / 8) * (max_y * b + yi) + xi / 8] >> (7 - (xi % 8))) & 1) << b;
+			bitmap->bitmap[(bitmap->width * (y + yi)) + (x + xi)] = p;
+		}
+	}
+
+	bitmap->npalette = 32;
+	for (i = 0; i < 32; i++)
+		bitmap->palette[i] = bitmap_amiga_colour(data[i * 2], data[i * 2 + 1]);
+
+	free(data);
+	return TRUE;
+}
+
+BitmapType bitmap_noext_type(char *file) {
+	Common::File f;
+	if (f.open(file)) {
+		L9BYTE data[72];
+		int x, y;
+
+		if (f.read(data, sizeof(data)) != sizeof(data) && !f.eos())
+			return NO_BITMAPS;
+		f.close();
+
+		x = data[67] + data[66] * 256;
+		y = data[71] + data[70] * 256;
+
+		if ((x == 0x0140) && (y == 0x0088))
+			return AMIGA_BITMAPS;
+		if ((x == 0x0140) && (y == 0x0087))
+			return AMIGA_BITMAPS;
+		if ((x == 0x00E0) && (y == 0x0075))
+			return AMIGA_BITMAPS;
+		if ((x == 0x00E4) && (y == 0x0075))
+			return AMIGA_BITMAPS;
+		if ((x == 0x00E0) && (y == 0x0076))
+			return AMIGA_BITMAPS;
+		if ((x == 0x00DB) && (y == 0x0076))
+			return AMIGA_BITMAPS;
+
+		x = data[3] + data[2] * 256;
+		y = data[7] + data[6] * 256;
+
+		if ((x == 0x0200) && (y == 0x00D8))
+			return MAC_BITMAPS;
+		if ((x == 0x0168) && (y == 0x00BA))
+			return MAC_BITMAPS;
+		if ((x == 0x0168) && (y == 0x00BC))
+			return MAC_BITMAPS;
+		if ((x == 0x0200) && (y == 0x00DA))
+			return MAC_BITMAPS;
+		if ((x == 0x0168) && (y == 0x00DA))
+			return MAC_BITMAPS;
+
+		x = data[35] + data[34] * 256;
+		y = data[39] + data[38] * 256;
+
+		if ((x == 0x0050) && (y == 0x0087))
+			return ST1_BITMAPS;
+		if ((x == 0x0038) && (y == 0x0074))
+			return ST1_BITMAPS;
+	}
+
+	return NO_BITMAPS;
+}
+
+/*
+    Macintosh Bitmaps
+*/
+
+/*
+    The Mac image file format is very simple. The header is ten bytes
+    with the width of the image in pixels in the first long and the
+    height (in pixel rows) in the second long - both are big-endian.
+    (In both cases I treat these as unsigned shorts to minimise byte
+    twiddling when working around byte sex issues). There follow two
+    unidentified bytes - possibly image type identifiers or maybe
+    valid pixel masks for the beginning and end of pixel rows in
+    sub-images.
+
+    The image data is extremely simple. The entire block is a packed array
+    of 1-bit pixels - I.E. each byte holds eight pixels - with 1 representing
+    white and 0 representing black. The pixels are organised with the top
+    left first and bottom left last, each row in turn.
+
+    The image sizes are 512 * 216 pixels for main images and 360 * 186 pixels
+    for sub-images.
+*/
+L9BOOL bitmap_mac_decode(char *file, int x, int y) {
+	L9BYTE *data = NULL;
+	int xi, yi, max_x, max_y;
+
+	L9UINT32 size;
+	data = bitmap_load(file, &size);
+	if (data == NULL)
+		return FALSE;
+
+	max_x = data[3] + data[2] * 256;
+	max_y = data[7] + data[6] * 256;
+	if (max_x > MAX_BITMAP_WIDTH || max_y > MAX_BITMAP_HEIGHT) {
+		free(data);
+		return FALSE;
+	}
+
+	if (x > 0)  /* Mac bug, apparently */
+		x = 78;
+
+	if ((x == 0) && (y == 0)) {
+		if (bitmap)
+			free(bitmap);
+		bitmap = bitmap_alloc(max_x, max_y);
+	}
+	if (bitmap == NULL) {
+		free(data);
+		return FALSE;
+	}
+
+	if (x + max_x > bitmap->width)
+		max_x = bitmap->width - x;
+	if (y + max_y > bitmap->height)
+		max_y = bitmap->height - y;
+
+	for (yi = 0; yi < max_y; yi++) {
+		for (xi = 0; xi < max_x; xi++) {
+			bitmap->bitmap[(bitmap->width * (y + yi)) + (x + xi)] =
+			    (data[10 + (max_x / 8) * yi + xi / 8] >> (7 - (xi % 8))) & 1;
+		}
+	}
+
+	bitmap->npalette = 2;
+	bitmap->palette[0].red = 0;
+	bitmap->palette[0].green = 0;
+	bitmap->palette[0].blue = 0;
+	bitmap->palette[1].red = 0xff;
+	bitmap->palette[1].green = 0xff;
+	bitmap->palette[1].blue = 0xff;
+
+	free(data);
+	return TRUE;
+}
+
+/*
+    C64 Bitmaps, also related formats (BBC B, Amstrad CPC and Spectrum +3)
+*/
+
+/* Commodore 64 palette from Vice */
+const Colour bitmap_c64_colours[] = {
+	{0x00, 0x00, 0x00 },
+	{0xff, 0xff, 0xff },
+	{0x89, 0x40, 0x36 },
+	{0x7a, 0xbf, 0xc7 },
+	{0x8a, 0x46, 0xae },
+	{0x68, 0xa9, 0x41 },
+	{0x3e, 0x31, 0xa2 },
+	{0xd0, 0xdc, 0x71 },
+	{0x90, 0x5f, 0x25 },
+	{0x5c, 0x47, 0x00 },
+	{0xbb, 0x77, 0x6d },
+	{0x55, 0x55, 0x55 },
+	{0x80, 0x80, 0x80 },
+	{0xac, 0xea, 0x88 },
+	{0x7c, 0x70, 0xda },
+	{0xab, 0xab, 0xab }
+};
+
+const Colour bitmap_bbc_colours[] = {
+	{0x00, 0x00, 0x00 },
+	{0xff, 0x00, 0x00 },
+	{0x00, 0xff, 0x00 },
+	{0xff, 0xff, 0x00 },
+	{0x00, 0x00, 0xff },
+	{0xff, 0x00, 0xff },
+	{0x00, 0xff, 0xff },
+	{0xff, 0xff, 0xff }
+};
+
+void bitmap_c64_name(int num, char *dir, char *out) {
+	if (num == 0)
+		sprintf(out, "%stitle mpic", dir);
+	else
+		sprintf(out, "%spic%d", dir, num);
+}
+
+void bitmap_bbc_name(int num, char *dir, char *out) {
+	if (num == 0) {
+		sprintf(out, "%sP.Title", dir);
+		if (Common::File::exists(out))
+			return;
+
+		sprintf(out, "%stitle", dir);
+	} else {
+		sprintf(out, "%sP.Pic%d", dir, num);
+		if (Common::File::exists(out))
+			return;
+
+		sprintf(out, "%spic%d", dir, num);
+	}
+}
+
+void bitmap_cpc_name(int num, char *dir, char *out) {
+	if (num == 0)
+		sprintf(out, "%stitle.pic", dir);
+	else if (num == 1)
+		sprintf(out, "%s1.pic", dir);
+	else
+		sprintf(out, "%sallpics.pic", dir);
+}
+
+BitmapType bitmap_c64_type(char *file) {
+	BitmapType type = C64_BITMAPS;
+
+	Common::File f;
+	if (f.open(file)) {
+		L9UINT32 size = f.size();
+		f.close();
+
+		if (size == 10048)
+			type = BBC_BITMAPS;
+		if (size == 6494)
+			type = BBC_BITMAPS;
+	}
+
+	return type;
+}
+
+/*
+    The C64 graphics file format is (loosely) based on the layout of
+    C64 graphics memory. There are in fact two formats (i) the
+    standard game images and (ii) title pictures. For both formats
+    the file begins within the 2-byte pair 0x00 and 0x20.
+
+    The images are "multi-color bitmap mode" images which means they
+    have rows of 160 double width pixels and can be up to 200 rows
+    long. (The title images are 200 lines long, the game images are
+    136 lines long.) Unlike Amiga, Mac, ST and PC graphics there are
+    no "main" and "sub" images. All game graphics have the same
+    dimensions and each completely replaces its predecessor.
+
+    The graphics files used on the Amstrad CPC and Spectrum +3 are also
+    virtually identical to C64 graphics files. This choice was presumably
+    made because although the CPC screen was more capable than the c64 it
+    was (in low resolution) the same size (160*200) and presumably
+    algorothmic conversion conversion of the colours was trivial for
+    the interpreter. In addition (a) the artwork already existed so no
+    extra expense would be incurred and (b) by accepting the C64's
+    limitation of only four colours in each 4*8 pixel block (but still
+    with sixteen colours on screen) they got a compressed file format
+    allowing more pictures on each disk.
+
+    The file organisation is rather different though. Only picture
+    one and the title picture are separate files. All the other
+    pictures (2-29) are stored in one large file "allpics.pic".
+
+    On these platforms the picture 1 file and title picture file have
+    an AMSDOS header (a 128 byte block of metadata) which contains a
+    checksum of the first 66 bytes of the header in a little-endian
+    word at bytes 67 & 68. On the original C64 platform there was a
+    simple two byte header. Following the header the data is organised
+    exactly as in the C64 game and title image files. The
+    'allpics.pic" file has no header and consists of 0x139E blocks
+    each forming a picture, in the C64 game file format (minus the two
+    byte header).
+*/
+L9BOOL bitmap_c64_decode(char *file, BitmapType type, int num) {
+	L9BYTE *data = NULL;
+	int i = 0, xi, yi, max_x = 0, max_y = 0, cx, cy, px, py, p;
+	int off = 0, off_scr = 0, off_col = 0, off_bg = 0, col_comp = 0;
+
+	L9UINT32 size;
+	data = bitmap_load(file, &size);
+	if (data == NULL)
+		return FALSE;
+
+	if (type == C64_BITMAPS) {
+		if (size == 10018) { /* C64 title picture */
+			max_x = 320;
+			max_y = 200;
+			off = 2;
+			off_scr = 8002;
+			off_bg = 9003;
+			off_col = 9018;
+			col_comp = 0;
+		} else if (size == 6464) { /* C64 picture */
+			max_x = 320;
+			max_y = 136;
+			off = 2;
+			off_scr = 5442;
+			off_col = 6122;
+			off_bg = 6463;
+			col_comp = 1;
+		} else
+			return FALSE;
+	} else if (type == BBC_BITMAPS) {
+		if (size == 10058) { /* BBC title picture */
+			max_x = 320;
+			max_y = 200;
+			off = 10;
+			off_scr = 8010;
+			off_bg = 9011;
+			off_col = 9026;
+			col_comp = 0;
+		} else if (size == 10048) { /* BBC title picture */
+			max_x = 320;
+			max_y = 200;
+			off = 0;
+			off_scr = 8000;
+			off_bg = 9001;
+			off_col = 9016;
+			col_comp = 0;
+		} else if (size == 6504) { /* BBC picture */
+			max_x = 320;
+			max_y = 136;
+			off = 10;
+			off_scr = 5450;
+			off_col = 6130;
+			off_bg = 6471;
+			col_comp = 1;
+		} else if (size == 6494) { /* BBC picture */
+			max_x = 320;
+			max_y = 136;
+			off = 0;
+			off_scr = 5440;
+			off_col = 6120;
+			off_bg = 6461;
+			col_comp = 1;
+		} else
+			return FALSE;
+	} else if (type == CPC_BITMAPS) {
+		if (num == 0) { /* CPC/+3 title picture */
+			max_x = 320;
+			max_y = 200;
+			off = 128;
+			off_scr = 8128;
+			off_bg = 9128;
+			off_col = 9144;
+			col_comp = 0;
+		} else if (num == 1) { /* First CPC/+3 picture */
+			max_x = 320;
+			max_y = 136;
+			off = 128;
+			off_scr = 5568;
+			off_col = 6248;
+			off_bg = 6588;
+			col_comp = 1;
+		} else if (num >= 2 && num <= 29) { /* Subsequent CPC/+3 pictures */
+			max_x = 320;
+			max_y = 136;
+			off = ((num - 2) * 6462);
+			off_scr = 5440 + ((num - 2) * 6462);
+			off_col = 6120 + ((num - 2) * 6462);
+			off_bg = 6460 + ((num - 2) * 6462);
+			col_comp = 1;
+		} else
+			return FALSE;
+	}
+
+	if (bitmap)
+		free(bitmap);
+	bitmap = bitmap_alloc(max_x, max_y);
+	if (bitmap == NULL) {
+		free(data);
+		return FALSE;
+	}
+
+	for (yi = 0; yi < max_y; yi++) {
+		for (xi = 0; xi < max_x / 2; xi++) {
+			cx = xi / 4;
+			px = xi % 4;
+			cy = yi / 8;
+			py = yi % 8;
+
+			p = data[off + (cy * 40 + cx) * 8 + py];
+			p = (p >> ((3 - px) * 2)) & 3;
+
+			switch (p) {
+			case 0:
+				i = data[off_bg] & 0x0f;
+				break;
+			case 1:
+				i = data[off_scr + cy * 40 + cx] >> 4;
+				break;
+			case 2:
+				i = data[off_scr + cy * 40 + cx] & 0x0f;
+				break;
+			case 3:
+				if (col_comp)
+					i = (data[off_col + (cy * 40 + cx) / 2] >> ((1 - (cx % 2)) * 4)) & 0x0f;
+				else
+					i = data[off_col + (cy * 40 + cx)] & 0x0f;
+				break;
+			}
+
+			bitmap->bitmap[(bitmap->width * yi) + (xi * 2)] = i;
+			bitmap->bitmap[(bitmap->width * yi) + (xi * 2) + 1] = i;
+		}
+	}
+
+	bitmap->npalette = 16;
+	for (i = 0; i < 16; i++)
+		bitmap->palette[i] = bitmap_c64_colours[i];
+
+	free(data);
+	return TRUE;
+}
+
+/*
+    The graphics files used by the BBC B are virtually identical
+    to C64 graphics files. I assume that (as with the CPC and
+    Spectrum+3) this choice was made because the BBC mode 2 screen,
+    was nearly the same size (160*256) and had roughly the same capability
+    as the C64 screen (displays 16 colours, although eight of those ar
+    just the first eight flashing).
+
+    In addition (a) the artwork already existed so no extra expense would
+    be incurred and (b) by accepting the C64's limitation of only four
+    colours in each 4*8 pixel block (but still with sixteen colours on
+    screen) they got a compressed file format allowing more pictures
+    on each disk.
+
+    The file organisation is very close to the C64. The naming system
+    can be the same eg "PIC12", but another form is also used :
+    "P.Pic12". Unlike the C64 the BBC has well defined title images,
+    called "TITLE" or P.Title. All pictures are in separate files.
+
+    The only difference seems to be:
+
+    * There is either *no* header before the image data or a simple
+    10 byte header which I think *may* be a file system header
+    left in place by the extractor system.
+
+    * There is an extra 32 bytes following the data at the end of
+    each file. These bytes encode a table to convert between the 16
+    C64 colours and 16, four-pixel pix-patterns used to let the BBC
+    (with only 8 colours) represent the sixteen possible C64 colours.
+
+    A pix-pattern looks like this:
+
+             |   Even |   Odd   |
+             | Column | Column  |
+        -----------------------------
+        Even Row |Pixel 1 | Pixel 2 |
+        ---------|--------|---------|
+        Odd Row  |Pixel 3 | Pixel 4 |
+        -----------------------------
+
+    Each of the four pixel *can* be any of the eight BBC Mode 2
+    steady colours. In practice they seem either to be all the
+    same or a simple check of two colours - the pixels in the
+    odd row being in the reverse order to those in the even row.
+
+    When converting a C64 pixel to a BBC pixel the game uses the
+    value of the C64 pixel as an index into the array of sixteen
+    BBC pix-patterns. The game looks at the selected pattern and
+    chooses the BBC pixel colour thus: if the pixel is in an even
+    numbered row and an even numbered column, it uses Pixel 1 from
+    the pattern, if in an even row but an odd column, it uses Pixel 3
+    and so on.
+
+    The pix-pattern data is encoded thus: the first sixteen bytes
+    encode the even row pixels for the patterns, one byte per
+    pattern, and in the same way the second sixteen bytes encode
+    the odd row pixels for each pattern. For example for the
+    pattern representing C64 colour 0 the even row pixels are encoded
+    in the first byte and the odd row pixels in the sixteenth byte.
+
+    Within each byte the pixels are encoded in this way:
+
+    Bit     7   6   5   4   3   2   1   0
+    -------------------------------------
+            0   0   1   0   0   1   1   1
+            |   |   |   |   |   |   |   |
+            +---|---+---|---+---|---+---|----- Even Pixel 0101 (5)
+                |       |       |       |
+                +-------+-------+-------+----- Odd Pixel 0011 (3)
+
+    This function calls the C64 decoding routines to do the actual
+    loading. See the comments to that function for details of how the
+    image is encoded and stored.
+*/
+L9BOOL bitmap_bbc_decode(char *file, BitmapType type, int num) {
+	unsigned char   patRowData[32];
+	unsigned char patArray[16][2][2];
+	int i, j, k, isOddColumn, isOddRow;
+	L9BYTE pixel;
+
+	if (bitmap_c64_decode(file, type, num) == FALSE)
+		return FALSE;
+
+	Common::File f;
+	if (!f.open(file))
+		return FALSE;
+
+	/* Seek to the offset of the pixPat data and read in the data */
+	f.seek(f.size() - 32, SEEK_SET);
+	if (f.read(patRowData, 32) != 32 && !f.eos())
+		return FALSE;
+	f.close();
+
+	/* Extract the patterns */
+	i = 0;
+	for (k = 0; k < 2; k++) {
+		for (j = 0; j < 16; j++) {
+			/* Extract the even col pixel for this pattern row */
+			patArray[j][k][0] =
+			    ((patRowData[i] >> 4) & 0x8) + ((patRowData[i] >> 3) & 0x4) +
+			    ((patRowData[i] >> 2) & 0x2) + ((patRowData[i] >> 1) & 0x1);
+			/* Extract the odd col pixel for this pattern row */
+			patArray[j][k][1] =
+			    ((patRowData[i] >> 3) & 0x8) + ((patRowData[i] >> 2) & 0x4) +
+			    ((patRowData[i] >> 1) & 0x2) + (patRowData[i] & 0x1);
+			i++;
+		}
+	}
+
+	/* Convert the image. Each BBC pixel is represented by two pixels here */
+	i = 0;
+	isOddRow = 0;
+	for (j = 0; j < bitmap->height; j++) {
+		isOddColumn = 0;
+		for (k = 0; k < bitmap->width / 2; k++) {
+			pixel = bitmap->bitmap[i];
+			bitmap->bitmap[i] = patArray[pixel][isOddColumn][isOddRow];
+			bitmap->bitmap[i + 1] = patArray[pixel][isOddColumn][isOddRow];
+			isOddColumn ^= 1;
+			i += 2;
+		}
+		isOddRow ^= 1;
+	}
+
+	bitmap->npalette = 8;
+	for (i = 0; i < 8; i++)
+		bitmap->palette[i] = bitmap_bbc_colours[i];
+
+	return TRUE;
+}
+
+BitmapType DetectBitmaps(char *dir) {
+	char file[MAX_PATH];
+
+	bitmap_noext_name(2, dir, file);
+	if (bitmap_exists(file))
+		return bitmap_noext_type(file);
+
+	bitmap_pc_name(2, dir, file);
+	if (bitmap_exists(file))
+		return bitmap_pc_type(file);
+
+	bitmap_c64_name(2, dir, file);
+	if (bitmap_exists(file))
+		return bitmap_c64_type(file);
+
+	bitmap_bbc_name(2, dir, file);
+	if (bitmap_exists(file))
+		return BBC_BITMAPS;
+
+	bitmap_cpc_name(2, dir, file);
+	if (bitmap_exists(file))
+		return CPC_BITMAPS;
+
+	bitmap_st2_name(2, dir, file);
+	if (bitmap_exists(file))
+		return ST2_BITMAPS;
+
+	return NO_BITMAPS;
+}
+
+Bitmap *DecodeBitmap(char *dir, BitmapType type, int num, int x, int y) {
+	char file[MAX_PATH];
+
+	switch (type) {
+	case PC1_BITMAPS:
+		bitmap_pc_name(num, dir, file);
+		if (bitmap_pc1_decode(file, x, y))
+			return bitmap;
+		break;
+
+	case PC2_BITMAPS:
+		bitmap_pc_name(num, dir, file);
+		if (bitmap_pc2_decode(file, x, y))
+			return bitmap;
+		break;
+
+	case AMIGA_BITMAPS:
+		bitmap_noext_name(num, dir, file);
+		if (bitmap_amiga_decode(file, x, y))
+			return bitmap;
+		break;
+
+	case C64_BITMAPS:
+		bitmap_c64_name(num, dir, file);
+		if (bitmap_c64_decode(file, type, num))
+			return bitmap;
+		break;
+
+	case BBC_BITMAPS:
+		bitmap_bbc_name(num, dir, file);
+		if (bitmap_bbc_decode(file, type, num))
+			return bitmap;
+		break;
+
+	case CPC_BITMAPS:
+		bitmap_cpc_name(num, dir, file);
+		if (bitmap_c64_decode(file, type, num)) /* Nearly identical to C64 */
+			return bitmap;
+		break;
+
+	case MAC_BITMAPS:
+		bitmap_noext_name(num, dir, file);
+		if (bitmap_mac_decode(file, x, y))
+			return bitmap;
+		break;
+
+	case ST1_BITMAPS:
+		bitmap_noext_name(num, dir, file);
+		if (bitmap_st1_decode(file, x, y))
+			return bitmap;
+		break;
+
+	case ST2_BITMAPS:
+		bitmap_st2_name(num, dir, file);
+		if (bitmap_pc2_decode(file, x, y))
+			return bitmap;
+		break;
+
+	default:
+		break;
+	}
+
+	return NULL;
+}
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
new file mode 100644
index 0000000..719d05b
--- /dev/null
+++ b/engines/glk/level9/level9_main.cpp
@@ -0,0 +1,3599 @@
+/* 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.
+ *
+ */
+
+/*
+ * The input routine will repond to the following 'hash' commands
+ *  #save         saves position file directly (bypasses any
+ *                disk change prompts)
+ *  #restore      restores position file directly (bypasses any
+ *                protection code)
+ *  #quit         terminates current game, RunGame() will return FALSE
+ *  #cheat        tries to bypass restore protection on v3,4 games
+ *                (can be slow)
+ *  #dictionary   lists game dictionary (press a key to interrupt)
+ *  #picture <n>  show picture <n>
+ *  #seed <n>     set the random number seed to the value <n>
+ *  #play         plays back a file as the input to the game
+ *
+\***********************************************************************/
+
+#include "glk/level9/level9_main.h"
+#include "glk/level9/level9.h"
+#include "common/file.h"
+#include "common/system.h"
+
+namespace Glk {
+namespace Level9 {
+
+/* #define L9DEBUG */
+/* #define CODEFOLLOW */
+/* #define FULLSCAN */
+
+/* "L901" */
+#define L9_ID 0x4c393031
+
+#define IBUFFSIZE 500
+#define RAMSAVESLOTS 10
+#define GFXSTACKSIZE 100
+#define FIRSTLINESIZE 96
+
+/* Typedefs */
+typedef struct {
+	L9UINT16 vartable[256];
+	L9BYTE listarea[LISTAREASIZE];
+} SaveStruct;
+
+/* Enumerations */
+enum L9GameTypes { L9_V1, L9_V2, L9_V3, L9_V4 };
+enum L9MsgTypes { MSGT_V1, MSGT_V2 };
+/*
+    Graphics type    Resolution     Scale stack reset
+    -------------------------------------------------
+    GFX_V2           160 x 128            yes
+    GFX_V3A          160 x 96             yes
+    GFX_V3B          160 x 96             no
+    GFX_V3C          320 x 96             no
+*/
+enum L9GfxTypes { GFX_V2, GFX_V3A, GFX_V3B, GFX_V3C };
+
+/* Global Variables */
+L9BYTE *startfile = NULL, *pictureaddress = NULL, *picturedata = NULL;
+L9BYTE *startdata;
+L9UINT32 FileSize, picturesize;
+
+L9BYTE *L9Pointers[12];
+L9BYTE *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
+L9BYTE *startmd, *endmd, *endwdp5, *wordtable, *dictdata, *defdict;
+L9UINT16 dictdatalen;
+L9BYTE *startmdV2;
+
+int wordcase;
+int unpackcount;
+char unpackbuf[8];
+L9BYTE *dictptr;
+char threechars[34];
+int L9GameType;
+int L9MsgType;
+char LastGame[MAX_PATH];
+char FirstLine[FIRSTLINESIZE];
+int FirstLinePos = 0;
+int FirstPicture = -1;
+
+#if defined(AMIGA) && defined(_DCC)
+__far SaveStruct ramsavearea[RAMSAVESLOTS];
+#else
+SaveStruct ramsavearea[RAMSAVESLOTS];
+#endif
+
+GameState workspace;
+
+L9UINT16 randomseed;
+L9UINT16 constseed = 0;
+L9BOOL Running;
+
+char ibuff[IBUFFSIZE];
+L9BYTE *ibuffptr;
+char obuff[34];
+Common::SeekableReadStream *scriptfile = NULL;
+
+L9BOOL Cheating = FALSE;
+int CheatWord;
+GameState CheatWorkspace;
+
+int reflectflag, scale, gintcolour, option;
+int l9textmode = 0, drawx = 0, drawy = 0, screencalled = 0, showtitle = 1;
+L9BYTE *gfxa5 = NULL;
+Bitmap *bitmap = NULL;
+int gfx_mode = GFX_V2;
+
+L9BYTE *GfxA5Stack[GFXSTACKSIZE];
+int GfxA5StackPos = 0;
+int GfxScaleStack[GFXSTACKSIZE];
+int GfxScaleStackPos = 0;
+
+char lastchar = '.';
+char lastactualchar = 0;
+int d5;
+
+L9BYTE *codeptr; /* instruction codes */
+L9BYTE code;
+
+L9BYTE *list9ptr;
+
+int unpackd3;
+
+L9BYTE exitreversaltable[16] = {0x00, 0x04, 0x06, 0x07, 0x01, 0x08, 0x02, 0x03, 0x05, 0x0a, 0x09, 0x0c, 0x0b, 0xff, 0xff, 0x0f};
+
+L9UINT16 gnostack[128];
+L9BYTE gnoscratch[32];
+int object, gnosp, numobjectfound, searchdepth, inithisearchpos;
+
+
+struct L9V1GameInfo {
+	L9BYTE dictVal1, dictVal2;
+	int dictStart, L9Ptrs[5], absData, msgStart, msgLen;
+};
+struct L9V1GameInfo L9V1Games[] = {
+	0x1a, 0x24, 301, 0x0000, -0x004b, 0x0080, -0x002b, 0x00d0, 0x03b0, 0x0f80, 0x4857, /* Colossal Adventure */
+	0x20, 0x3b, 283, -0x0583, 0x0000, -0x0508, -0x04e0, 0x0000, 0x0800, 0x1000, 0x39d1, /* Adventure Quest */
+	0x14, 0xff, 153, -0x00d6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0a20, 0x16bf, 0x420d, /* Dungeon Adventure */
+	0x15, 0x5d, 252, -0x3e70, 0x0000, -0x3d30, -0x3ca0, 0x0100, 0x4120, -0x3b9d, 0x3988, /* Lords of Time */
+	0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
+};
+int L9V1Game = -1;
+
+
+/* Prototypes */
+L9BOOL LoadGame2(char *filename, char *picname);
+int getlongcode(void);
+L9BOOL GetWordV2(char *buff, int Word);
+L9BOOL GetWordV3(char *buff, int Word);
+void show_picture(int pic);
+
+
+#ifdef CODEFOLLOW
+#define CODEFOLLOWFILE "c:\\temp\\level9.txt"
+FILE *f;
+L9UINT16 *cfvar, *cfvar2;
+char *codes[] = {
+	"Goto",
+	"intgosub",
+	"intreturn",
+	"printnumber",
+	"messagev",
+	"messagec",
+	"function",
+	"input",
+	"varcon",
+	"varvar",
+	"_add",
+	"_sub",
+	"ilins",
+	"ilins",
+	"jump",
+	"Exit",
+	"ifeqvt",
+	"ifnevt",
+	"ifltvt",
+	"ifgtvt",
+	"screen",
+	"cleartg",
+	"picture",
+	"getnextobject",
+	"ifeqct",
+	"ifnect",
+	"ifltct",
+	"ifgtct",
+	"printinput",
+	"ilins",
+	"ilins",
+	"ilins",
+};
+char *functions[] = {
+	"calldriver",
+	"L9Random",
+	"save",
+	"restore",
+	"clearworkspace",
+	"clearstack"
+};
+char *drivercalls[] = {
+	"init",
+	"drivercalcchecksum",
+	"driveroswrch",
+	"driverosrdch",
+	"driverinputline",
+	"driversavefile",
+	"driverloadfile",
+	"settext",
+	"resettask",
+	"returntogem",
+	"10 *",
+	"loadgamedatafile",
+	"randomnumber",
+	"13 *",
+	"driver14",
+	"15 *",
+	"driverclg",
+	"line",
+	"fill",
+	"driverchgcol",
+	"20 *",
+	"21 *",
+	"ramsave",
+	"ramload",
+	"24 *",
+	"lensdisplay",
+	"26 *",
+	"27 *",
+	"28 *",
+	"29 *",
+	"allocspace",
+	"31 *",
+	"showbitmap",
+	"33 *",
+	"checkfordisc"
+};
+#endif
+
+void initdict(L9BYTE *ptr) {
+	dictptr = ptr;
+	unpackcount = 8;
+}
+
+char getdictionarycode(void) {
+	if (unpackcount != 8) return unpackbuf[unpackcount++];
+	else {
+		/* unpackbytes */
+		L9BYTE d1 = *dictptr++, d2;
+		unpackbuf[0] = d1 >> 3;
+		d2 = *dictptr++;
+		unpackbuf[1] = ((d2 >> 6) + (d1 << 2)) & 0x1f;
+		d1 = *dictptr++;
+		unpackbuf[2] = (d2 >> 1) & 0x1f;
+		unpackbuf[3] = ((d1 >> 4) + (d2 << 4)) & 0x1f;
+		d2 = *dictptr++;
+		unpackbuf[4] = ((d1 << 1) + (d2 >> 7)) & 0x1f;
+		d1 = *dictptr++;
+		unpackbuf[5] = (d2 >> 2) & 0x1f;
+		unpackbuf[6] = ((d2 << 3) + (d1 >> 5)) & 0x1f;
+		unpackbuf[7] = d1 & 0x1f;
+		unpackcount = 1;
+		return unpackbuf[0];
+	}
+}
+
+int getdictionary(int d0) {
+	if (d0 >= 0x1a) return getlongcode();
+	else return d0 + 0x61;
+}
+
+int getlongcode(void) {
+	int d0, d1;
+	d0 = getdictionarycode();
+	if (d0 == 0x10) {
+		wordcase = 1;
+		d0 = getdictionarycode();
+		return getdictionary(d0); /* reentrant? */
+	}
+	d1 = getdictionarycode();
+	return 0x80 | ((d0 << 5) & 0xe0) | (d1 & 0x1f);
+}
+
+void printchar(char c) {
+	if (Cheating) return;
+
+	if (c & 128)
+		lastchar = (c &= 0x7f);
+	else if (c != 0x20 && c != 0x0d && (c < '\"' || c >= '.')) {
+		if (lastchar == '!' || lastchar == '?' || lastchar == '.') c = toupper(c);
+		lastchar = c;
+	}
+	/* eat multiple CRs */
+	if (c != 0x0d || lastactualchar != 0x0d) {
+		os_printchar(c);
+		if (FirstLinePos < FIRSTLINESIZE - 1)
+			FirstLine[FirstLinePos++] = tolower(c);
+	}
+	lastactualchar = c;
+}
+
+void printstring(const char *buf) {
+	int i;
+	for (i = 0; i < (int) strlen(buf); i++) printchar(buf[i]);
+}
+
+void printdecimald0(int d0) {
+	char temp[12];
+	sprintf(temp, "%d", d0);
+	printstring(temp);
+}
+
+void printautocase(int d0) {
+	if (d0 & 128) printchar((char) d0);
+	else {
+		if (wordcase) printchar((char) toupper(d0));
+		else if (d5 < 6) printchar((char) d0);
+		else {
+			wordcase = 0;
+			printchar((char) toupper(d0));
+		}
+	}
+}
+
+void displaywordref(L9UINT16 Off) {
+	static int mdtmode = 0;
+
+	wordcase = 0;
+	d5 = (Off >> 12) & 7;
+	Off &= 0xfff;
+	if (Off < 0xf80) {
+		/* dwr01 */
+		L9BYTE *a0, *oPtr, *a3;
+		int d0, d2, i;
+
+		if (mdtmode == 1) printchar(0x20);
+		mdtmode = 1;
+
+		/* setindex */
+		a0 = dictdata;
+		d2 = dictdatalen;
+
+		/* dwr02 */
+		oPtr = a0;
+		while (d2 && Off >= L9WORD(a0 + 2)) {
+			a0 += 4;
+			d2--;
+		}
+		/* dwr04 */
+		if (a0 == oPtr) {
+			a0 = defdict;
+		} else {
+			a0 -= 4;
+			Off -= L9WORD(a0 + 2);
+			a0 = startdata + L9WORD(a0);
+		}
+		/* dwr04b */
+		Off++;
+		initdict(a0);
+		a3 = (L9BYTE *) threechars; /* a3 not set in original, prevent possible spam */
+
+		/* dwr05 */
+		while (TRUE) {
+			d0 = getdictionarycode();
+			if (d0 < 0x1c) {
+				/* dwr06 */
+				if (d0 >= 0x1a) d0 = getlongcode();
+				else d0 += 0x61;
+				*a3++ = d0;
+			} else {
+				d0 &= 3;
+				a3 = (L9BYTE *) threechars + d0;
+				if (--Off == 0) break;
+			}
+		}
+		for (i = 0; i < d0; i++) printautocase(threechars[i]);
+
+		/* dwr10 */
+		while (TRUE) {
+			d0 = getdictionarycode();
+			if (d0 >= 0x1b) return;
+			printautocase(getdictionary(d0));
+		}
+	}
+
+	else {
+		if (d5 & 2) printchar(0x20); /* prespace */
+		mdtmode = 2;
+		Off &= 0x7f;
+		if (Off != 0x7e) printchar((char)Off);
+		if (d5 & 1) printchar(0x20); /* postspace */
+	}
+}
+
+int getmdlength(L9BYTE **Ptr) {
+	int tot = 0, len;
+	do {
+		len = (*(*Ptr)++ -1) & 0x3f;
+		tot += len;
+	} while (len == 0x3f);
+	return tot;
+}
+
+void printmessage(int Msg) {
+	L9BYTE *Msgptr = startmd;
+	L9BYTE Data;
+
+	int len;
+	L9UINT16 Off;
+
+	while (Msg > 0 && Msgptr - endmd <= 0) {
+		Data = *Msgptr;
+		if (Data & 128) {
+			Msgptr++;
+			Msg -= Data & 0x7f;
+		} else {
+			len = getmdlength(&Msgptr);
+			Msgptr += len;
+		}
+		Msg--;
+	}
+	if (Msg < 0 || *Msgptr & 128) return;
+
+	len = getmdlength(&Msgptr);
+	if (len == 0) return;
+
+	while (len) {
+		Data = *Msgptr++;
+		len--;
+		if (Data & 128) {
+			/* long form (reverse word) */
+			Off = (Data << 8) + *Msgptr++;
+			len--;
+		} else {
+			Off = (wordtable[Data * 2] << 8) + wordtable[Data * 2 + 1];
+		}
+		if (Off == 0x8f80) break;
+		displaywordref(Off);
+	}
+}
+
+/* v2 message stuff */
+
+int msglenV2(L9BYTE **ptr) {
+	int i = 0;
+	L9BYTE a;
+
+	/* catch berzerking code */
+	if (*ptr >= startdata + FileSize) return 0;
+
+	while ((a = **ptr) == 0) {
+		(*ptr)++;
+
+		if (*ptr >= startdata + FileSize) return 0;
+
+		i += 255;
+	}
+	i += a;
+	return i;
+}
+
+void printcharV2(char c) {
+	if (c == 0x25) c = 0xd;
+	else if (c == 0x5f) c = 0x20;
+	printautocase(c);
+}
+
+void displaywordV2(L9BYTE *ptr, int msg) {
+	int n;
+	L9BYTE a;
+	if (msg == 0) return;
+	while (--msg) {
+		ptr += msglenV2(&ptr);
+	}
+	n = msglenV2(&ptr);
+
+	while (--n > 0) {
+		a = *++ptr;
+		if (a < 3) return;
+
+		if (a >= 0x5e) displaywordV2(startmdV2 - 1, a - 0x5d);
+		else printcharV2((char)(a + 0x1d));
+	}
+}
+
+int msglenV1(L9BYTE **ptr) {
+	L9BYTE *ptr2 = *ptr;
+	while (ptr2 < startdata + FileSize && *ptr2++ != 1);
+	return ptr2 - *ptr;
+}
+
+void displaywordV1(L9BYTE *ptr, int msg) {
+	int n;
+	L9BYTE a;
+	while (msg--) {
+		ptr += msglenV1(&ptr);
+	}
+	n = msglenV1(&ptr);
+
+	while (--n > 0) {
+		a = *ptr++;
+		if (a < 3) return;
+
+		if (a >= 0x5e) displaywordV1(startmdV2, a - 0x5e);
+		else printcharV2((char)(a + 0x1d));
+	}
+}
+
+L9BOOL amessageV2(L9BYTE *ptr, int msg, long *w, long *c) {
+	int n;
+	L9BYTE a;
+	static int depth = 0;
+	if (msg == 0) return FALSE;
+	while (--msg) {
+		ptr += msglenV2(&ptr);
+	}
+	if (ptr >= startdata + FileSize) return FALSE;
+	n = msglenV2(&ptr);
+
+	while (--n > 0) {
+		a = *++ptr;
+		if (a < 3) return TRUE;
+
+		if (a >= 0x5e) {
+			if (++depth > 10 || !amessageV2(startmdV2 - 1, a - 0x5d, w, c)) {
+				depth--;
+				return FALSE;
+			}
+			depth--;
+		} else {
+			char ch = a + 0x1d;
+			if (ch == 0x5f || ch == ' ')(*w)++;
+			else (*c)++;
+		}
+	}
+	return TRUE;
+}
+
+L9BOOL amessageV1(L9BYTE *ptr, int msg, long *w, long *c) {
+	int n;
+	L9BYTE a;
+	static int depth = 0;
+
+	while (msg--) {
+		ptr += msglenV1(&ptr);
+	}
+	if (ptr >= startdata + FileSize) return FALSE;
+	n = msglenV1(&ptr);
+
+	while (--n > 0) {
+		a = *ptr++;
+		if (a < 3) return TRUE;
+
+		if (a >= 0x5e) {
+			if (++depth > 10 || !amessageV1(startmdV2, a - 0x5e, w, c)) {
+				depth--;
+				return FALSE;
+			}
+			depth--;
+		} else {
+			char ch = a + 0x1d;
+			if (ch == 0x5f || ch == ' ')(*w)++;
+			else (*c)++;
+		}
+	}
+	return TRUE;
+}
+
+L9BOOL analyseV2(double *wl) {
+	long words = 0, chars = 0;
+	int i;
+	for (i = 1; i < 256; i++) {
+		long w = 0, c = 0;
+		if (amessageV2(startmd, i, &w, &c)) {
+			words += w;
+			chars += c;
+		} else return FALSE;
+	}
+	*wl = words ? (double) chars / words : 0.0;
+	return TRUE;
+}
+
+L9BOOL analyseV1(double *wl) {
+	long words = 0, chars = 0;
+	int i;
+	for (i = 0; i < 256; i++) {
+		long w = 0, c = 0;
+		if (amessageV1(startmd, i, &w, &c)) {
+			words += w;
+			chars += c;
+		} else return FALSE;
+	}
+
+	*wl = words ? (double) chars / words : 0.0;
+	return TRUE;
+}
+
+void printmessageV2(int Msg) {
+	if (L9MsgType == MSGT_V2) displaywordV2(startmd, Msg);
+	else displaywordV1(startmd, Msg);
+}
+
+void L9Allocate(L9BYTE **ptr, L9UINT32 Size) {
+	if (*ptr) free(*ptr);
+	*ptr = (L9BYTE *)malloc(Size);
+	if (*ptr == NULL) {
+		error("Unable to allocate memory for the game! Exiting...");
+	}
+}
+
+void FreeMemory(void) {
+	if (startfile) {
+		free(startfile);
+		startfile = NULL;
+	}
+	if (pictureaddress) {
+		free(pictureaddress);
+		pictureaddress = NULL;
+	}
+	if (bitmap) {
+		free(bitmap);
+		bitmap = NULL;
+	}
+	if (scriptfile) {
+		delete scriptfile;
+		scriptfile = NULL;
+	}
+	picturedata = NULL;
+	picturesize = 0;
+	gfxa5 = NULL;
+}
+
+L9BOOL load(const char *filename) {
+	Common::File f;
+	if (!f.open(filename))
+		return FALSE;
+
+	if ((FileSize = f.size()) < 256) {
+		f.close();
+		error("File is too small to contain a Level 9 game");
+		return FALSE;
+	}
+
+	L9Allocate(&startfile, FileSize);
+	if (f.read(startfile, FileSize) != FileSize) {
+		f.close();
+		return FALSE;
+	}
+
+	f.close();
+	return TRUE;
+}
+
+L9UINT16 scanmovewa5d0(L9BYTE *Base, L9UINT32 *Pos) {
+	L9UINT16 ret = L9WORD(Base + *Pos);
+	(*Pos) += 2;
+	return ret;
+}
+
+L9UINT32 scangetaddr(int Code, L9BYTE *Base, L9UINT32 *Pos, L9UINT32 acode, int *Mask) {
+	(*Mask) |= 0x20;
+	if (Code & 0x20) {
+		/* getaddrshort */
+		signed char diff = Base[*Pos];
+		(*Pos)++;
+		return (*Pos) + diff - 1;
+	} else {
+		return acode + scanmovewa5d0(Base, Pos);
+	}
+}
+
+void scangetcon(int Code, L9UINT32 *Pos, int *Mask) {
+	(*Pos)++;
+	if (!(Code & 64))(*Pos)++;
+	(*Mask) |= 0x40;
+}
+
+L9BOOL CheckCallDriverV4(L9BYTE *Base, L9UINT32 Pos) {
+	int i, j;
+
+	/* Look back for an assignment from a variable
+	 * to list9[0], which is used to specify the
+	 * driver call.
+	 */
+	for (i = 0; i < 2; i++) {
+		int x = Pos - ((i + 1) * 3);
+		if ((Base[x] == 0x89) && (Base[x + 1] == 0x00)) {
+			/* Get the variable being copied to list9[0] */
+			int var = Base[x + 2];
+
+			/* Look back for an assignment to the variable. */
+			for (j = 0; j < 2; j++) {
+				int y = x - ((j + 1) * 3);
+				if ((Base[y] == 0x48) && (Base[y + 2] == var)) {
+					/* If this a V4 driver call? */
+					switch (Base[y + 1]) {
+					case 0x0E:
+					case 0x20:
+					case 0x22:
+						return TRUE;
+					}
+					return FALSE;
+				}
+			}
+		}
+	}
+	return FALSE;
+}
+
+L9BOOL ValidateSequence(L9BYTE *Base, L9BYTE *Image, L9UINT32 iPos, L9UINT32 acode, L9UINT32 *Size, L9UINT32 size, L9UINT32 *Min, L9UINT32 *Max, L9BOOL Rts, L9BOOL *JumpKill, L9BOOL *DriverV4) {
+	L9UINT32 Pos;
+	L9BOOL Finished = FALSE, Valid;
+	L9UINT32 Strange = 0;
+	int ScanCodeMask;
+	int Code;
+	*JumpKill = FALSE;
+
+	if (iPos >= size)
+		return FALSE;
+	Pos = iPos;
+	if (Pos < *Min) *Min = Pos;
+
+	if (Image[Pos]) return TRUE; /* hit valid code */
+
+	do {
+		Code = Base[Pos];
+		Valid = TRUE;
+		if (Image[Pos]) break; /* converged to found code */
+		Image[Pos++] = 2;
+		if (Pos > *Max) *Max = Pos;
+
+		ScanCodeMask = 0x9f;
+		if (Code & 0x80) {
+			ScanCodeMask = 0xff;
+			if ((Code & 0x1f) > 0xa)
+				Valid = FALSE;
+			Pos += 2;
+		} else switch (Code & 0x1f) {
+			case 0: { /* goto */
+				L9UINT32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+				Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, TRUE/*Rts*/, JumpKill, DriverV4);
+				Finished = TRUE;
+				break;
+			}
+			case 1: { /* intgosub */
+				L9UINT32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+				Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, TRUE, JumpKill, DriverV4);
+				break;
+			}
+			case 2: /* intreturn */
+				Valid = Rts;
+				Finished = TRUE;
+				break;
+			case 3: /* printnumber */
+				Pos++;
+				break;
+			case 4: /* messagev */
+				Pos++;
+				break;
+			case 5: /* messagec */
+				scangetcon(Code, &Pos, &ScanCodeMask);
+				break;
+			case 6: /* function */
+				switch (Base[Pos++]) {
+				case 2:/* random */
+					Pos++;
+					break;
+				case 1:/* calldriver */
+					if (DriverV4) {
+						if (CheckCallDriverV4(Base, Pos - 2))
+							*DriverV4 = TRUE;
+					}
+					break;
+				case 3:/* save */
+				case 4:/* restore */
+				case 5:/* clearworkspace */
+				case 6:/* clear stack */
+					break;
+				case 250: /* printstr */
+					while (Base[Pos++]);
+					break;
+
+				default:
+#ifdef L9DEBUG
+					/* printf("scan: illegal function call: %d",Base[Pos-1]); */
+#endif
+					Valid = FALSE;
+					break;
+				}
+				break;
+			case 7: /* input */
+				Pos += 4;
+				break;
+			case 8: /* varcon */
+				scangetcon(Code, &Pos, &ScanCodeMask);
+				Pos++;
+				break;
+			case 9: /* varvar */
+				Pos += 2;
+				break;
+			case 10: /* _add */
+				Pos += 2;
+				break;
+			case 11: /* _sub */
+				Pos += 2;
+				break;
+			case 14: /* jump */
+#ifdef L9DEBUG
+				/* printf("jmp at codestart: %ld",acode); */
+#endif
+				*JumpKill = TRUE;
+				Finished = TRUE;
+				break;
+			case 15: /* exit */
+				Pos += 4;
+				break;
+			case 16: /* ifeqvt */
+			case 17: /* ifnevt */
+			case 18: /* ifltvt */
+			case 19: { /* ifgtvt */
+				L9UINT32 Val;
+				Pos += 2;
+				Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+				Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
+				break;
+			}
+			case 20: /* screen */
+				if (Base[Pos++]) Pos++;
+				break;
+			case 21: /* cleartg */
+				Pos++;
+				break;
+			case 22: /* picture */
+				Pos++;
+				break;
+			case 23: /* getnextobject */
+				Pos += 6;
+				break;
+			case 24: /* ifeqct */
+			case 25: /* ifnect */
+			case 26: /* ifltct */
+			case 27: { /* ifgtct */
+				L9UINT32 Val;
+				Pos++;
+				scangetcon(Code, &Pos, &ScanCodeMask);
+				Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+				Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
+				break;
+			}
+			case 28: /* printinput */
+				break;
+			case 12: /* ilins */
+			case 13: /* ilins */
+			case 29: /* ilins */
+			case 30: /* ilins */
+			case 31: /* ilins */
+#ifdef L9DEBUG
+				/* printf("scan: illegal instruction"); */
+#endif
+				Valid = FALSE;
+				break;
+			}
+		if (Valid && (Code & ~ScanCodeMask))
+			Strange++;
+	} while (Valid && !Finished && Pos < size); /* && Strange==0); */
+	(*Size) += Pos - iPos;
+	return Valid; /* && Strange==0; */
+}
+
+L9BYTE calcchecksum(L9BYTE *ptr, L9UINT32 num) {
+	L9BYTE d1 = 0;
+	while (num-- != 0) d1 += *ptr++;
+	return d1;
+}
+
+/*
+L9BOOL Check(L9BYTE* StartFile,L9UINT32 FileSize,L9UINT32 Offset)
+{
+    L9UINT16 d0,num;
+    int i;
+    L9BYTE* Image;
+    L9UINT32 Size=0,Min,Max;
+    L9BOOL ret,JumpKill;
+
+    for (i=0;i<12;i++)
+    {
+        d0=L9WORD (StartFile+Offset+0x12 + i*2);
+        if (d0>=0x8000+LISTAREASIZE) return FALSE;
+    }
+
+    num=L9WORD(StartFile+Offset)+1;
+    if (Offset+num>FileSize) return FALSE;
+    if (calcchecksum(StartFile+Offset,num)) return FALSE;
+
+    Image=calloc(FileSize,1);
+
+    Min=Max=Offset+d0;
+    ret=ValidateSequence(StartFile,Image,Offset+d0,Offset+d0,&Size,FileSize,&Min,&Max,FALSE,&JumpKill,NULL);
+    free(Image);
+    return ret;
+}
+*/
+
+long Scan(L9BYTE *StartFile, L9UINT32 size) {
+	L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
+	L9BYTE *Image = (L9BYTE *)calloc(size, 1);
+	L9UINT32 i, num, Size, MaxSize = 0;
+	int j;
+	L9UINT16 d0 = 0, l9, md, ml, dd, dl;
+	L9UINT32 Min, Max;
+	long Offset = -1;
+	L9BOOL JumpKill, DriverV4;
+
+	if ((Chk == NULL) || (Image == NULL)) {
+		error("Unable to allocate memory for game scan! Exiting...");
+	}
+
+	Chk[0] = 0;
+	for (i = 1; i <= size; i++)
+		Chk[i] = Chk[i - 1] + StartFile[i - 1];
+
+	for (i = 0; i < size - 33; i++) {
+		num = L9WORD(StartFile + i) + 1;
+		/*
+		        Chk[i] = 0 +...+ i-1
+		        Chk[i+n] = 0 +...+ i+n-1
+		        Chk[i+n] - Chk[i] = i + ... + i+n
+		*/
+		if (num > 0x2000 && i + num <= size && Chk[i + num] == Chk[i]) {
+			md = L9WORD(StartFile + i + 0x2);
+			ml = L9WORD(StartFile + i + 0x4);
+			dd = L9WORD(StartFile + i + 0xa);
+			dl = L9WORD(StartFile + i + 0xc);
+
+			if (ml > 0 && md > 0 && i + md + ml <= size && dd > 0 && dl > 0 && i + dd + dl * 4 <= size) {
+				/* v4 files may have acodeptr in 8000-9000, need to fix */
+				for (j = 0; j < 12; j++) {
+					d0 = L9WORD(StartFile + i + 0x12 + j * 2);
+					if (j != 11 && d0 >= 0x8000 && d0 < 0x9000) {
+						if (d0 >= 0x8000 + LISTAREASIZE) break;
+					} else if (i + d0 > size) break;
+				}
+				/* list9 ptr must be in listarea, acode ptr in data */
+				if (j < 12 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
+
+				l9 = L9WORD(StartFile + i + 0x12 + 10 * 2);
+				if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
+
+				Size = 0;
+				Min = Max = i + d0;
+				DriverV4 = 0;
+				if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, &DriverV4)) {
+#ifdef L9DEBUG
+					printf("Found valid header at %ld, code size %ld", i, Size);
+#endif
+					if (Size > MaxSize && Size > 100) {
+						Offset = i;
+						MaxSize = Size;
+						L9GameType = DriverV4 ? L9_V4 : L9_V3;
+					}
+				}
+			}
+		}
+	}
+	free(Chk);
+	free(Image);
+	return Offset;
+}
+
+long ScanV2(L9BYTE *StartFile, L9UINT32 size) {
+	L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
+	L9BYTE *Image = (L9BYTE *)calloc(size, 1);
+	L9UINT32 i, Size, MaxSize = 0, num;
+	int j;
+	L9UINT16 d0 = 0, l9;
+	L9UINT32 Min, Max;
+	long Offset = -1;
+	L9BOOL JumpKill;
+
+	if ((Chk == NULL) || (Image == NULL)) {
+		error("Unable to allocate memory for game scan! Exiting...");
+	}
+
+	Chk[0] = 0;
+	for (i = 1; i <= size; i++)
+		Chk[i] = Chk[i - 1] + StartFile[i - 1];
+
+	for (i = 0; i < size - 28; i++) {
+		num = L9WORD(StartFile + i + 28) + 1;
+		if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == StartFile[i + 0x1e]) {
+			for (j = 0; j < 14; j++) {
+				d0 = L9WORD(StartFile + i + j * 2);
+				if (j != 13 && d0 >= 0x8000 && d0 < 0x9000) {
+					if (d0 >= 0x8000 + LISTAREASIZE) break;
+				} else if (i + d0 > size) break;
+			}
+			/* list9 ptr must be in listarea, acode ptr in data */
+			if (j < 14 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
+
+			l9 = L9WORD(StartFile + i + 6 + 9 * 2);
+			if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
+
+			Size = 0;
+			Min = Max = i + d0;
+			if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+#ifdef L9DEBUG
+				printf("Found valid V2 header at %ld, code size %ld", i, Size);
+#endif
+				if (Size > MaxSize && Size > 100) {
+					Offset = i;
+					MaxSize = Size;
+				}
+			}
+		}
+	}
+	free(Chk);
+	free(Image);
+	return Offset;
+}
+
+long ScanV1(L9BYTE *StartFile, L9UINT32 size) {
+	L9BYTE *Image = (L9BYTE *)calloc(size, 1);
+	L9UINT32 i, Size;
+	int Replace;
+	L9BYTE *ImagePtr;
+	long MaxPos = -1;
+	L9UINT32 MaxCount = 0;
+	L9UINT32 Min, Max; //, MaxMax, MaxMin;
+	L9BOOL JumpKill; // , MaxJK;
+
+	int dictOff1 = 0, dictOff2 = 0;
+	L9BYTE dictVal1 = 0xff, dictVal2 = 0xff;
+
+	if (Image == NULL) {
+		error("Unable to allocate memory for game scan! Exiting...");
+	}
+
+	for (i = 0; i < size; i++) {
+		if ((StartFile[i] == 0 && StartFile[i + 1] == 6) || (StartFile[i] == 32 && StartFile[i + 1] == 4)) {
+			Size = 0;
+			Min = Max = i;
+			Replace = 0;
+			if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+				if (Size > MaxCount && Size > 100 && Size < 10000) {
+					MaxCount = Size;
+					//MaxMin = Min;
+					//MaxMax = Max;
+
+					MaxPos = i;
+					//MaxJK = JumpKill;
+				}
+				Replace = 0;
+			}
+			for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
+				if (*ImagePtr == 2)
+					*ImagePtr = Replace;
+			}
+		}
+	}
+#ifdef L9DEBUG
+	printf("V1scan found code at %ld size %ld", MaxPos, MaxCount);
+#endif
+
+	/* V1 dictionary detection from L9Cut by Paul David Doherty */
+	for (i = 0; i < size - 20; i++) {
+		if (StartFile[i] == 'A') {
+			if (StartFile[i + 1] == 'T' && StartFile[i + 2] == 'T' && StartFile[i + 3] == 'A' && StartFile[i + 4] == 'C' && StartFile[i + 5] == 0xcb) {
+				dictOff1 = i;
+				dictVal1 = StartFile[dictOff1 + 6];
+				break;
+			}
+		}
+	}
+	for (i = dictOff1; i < size - 20; i++) {
+		if (StartFile[i] == 'B') {
+			if (StartFile[i + 1] == 'U' && StartFile[i + 2] == 'N' && StartFile[i + 3] == 'C' && StartFile[i + 4] == 0xc8) {
+				dictOff2 = i;
+				dictVal2 = StartFile[dictOff2 + 5];
+				break;
+			}
+		}
+	}
+	L9V1Game = -1;
+	if (dictVal1 != 0xff || dictVal2 != 0xff) {
+		for (i = 0; i < sizeof L9V1Games / sizeof L9V1Games[0]; i++) {
+			if ((L9V1Games[i].dictVal1 == dictVal1) && (L9V1Games[i].dictVal2 == dictVal2)) {
+				L9V1Game = i;
+				dictdata = StartFile + dictOff1 - L9V1Games[i].dictStart;
+			}
+		}
+	}
+
+#ifdef L9DEBUG
+	if (L9V1Game >= 0)
+		printf("V1scan found known dictionary: %d", L9V1Game);
+#endif
+
+	free(Image);
+
+	if (MaxPos > 0) {
+		acodeptr = StartFile + MaxPos;
+		return 0;
+	}
+	return -1;
+}
+
+#ifdef FULLSCAN
+void FullScan(L9BYTE *StartFile, L9UINT32 size) {
+	L9BYTE *Image = (L9BYTE *)calloc(size, 1);
+	L9UINT32 i, Size;
+	int Replace;
+	L9BYTE *ImagePtr;
+	L9UINT32 MaxPos = 0;
+	L9UINT32 MaxCount = 0;
+	L9UINT32 Min, Max, MaxMin, MaxMax;
+	int Offset;
+	L9BOOL JumpKill, MaxJK;
+	for (i = 0; i < size; i++) {
+		Size = 0;
+		Min = Max = i;
+		Replace = 0;
+		if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+			if (Size > MaxCount) {
+				MaxCount = Size;
+				MaxMin = Min;
+				MaxMax = Max;
+
+				MaxPos = i;
+				MaxJK = JumpKill;
+			}
+			Replace = 0;
+		}
+		for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
+			if (*ImagePtr == 2)
+				*ImagePtr = Replace;
+		}
+	}
+	printf("%ld %ld %ld %ld %s", MaxPos, MaxCount, MaxMin, MaxMax, MaxJK ? "jmp killed" : "");
+	/* search for reference to MaxPos */
+	Offset = 0x12 + 11 * 2;
+	for (i = 0; i < size - Offset - 1; i++) {
+		if ((L9WORD(StartFile + i + Offset)) + i == MaxPos) {
+			printf("possible v3,4 Code reference at : %ld", i);
+			/* startdata=StartFile+i; */
+		}
+	}
+	Offset = 13 * 2;
+	for (i = 0; i < size - Offset - 1; i++) {
+		if ((L9WORD(StartFile + i + Offset)) + i == MaxPos)
+			printf("possible v2 Code reference at : %ld", i);
+	}
+	free(Image);
+}
+#endif
+
+L9BOOL findsubs(L9BYTE *testptr, L9UINT32 testsize, L9BYTE **picdata, L9UINT32 *picsize) {
+	int i, j, length, count;
+	L9BYTE *picptr, *startptr, *tmpptr;
+
+	if (testsize < 16) return FALSE;
+
+	/*
+	    Try to traverse the graphics subroutines.
+
+	    Each subroutine starts with a header: nn | nl | ll
+	    nnn : the subroutine number ( 0x000 - 0x7ff )
+	    lll : the subroutine length ( 0x004 - 0x3ff )
+
+	    The first subroutine usually has the number 0x000.
+	    Each subroutine ends with 0xff.
+
+	    findsubs() searches for the header of the second subroutine
+	    (pattern: 0xff | nn | nl | ll) and then tries to find the
+	    first and next subroutines by evaluating the length fields
+	    of the subroutine headers.
+	*/
+	for (i = 4; i < (int)(testsize - 4); i++) {
+		picptr = testptr + i;
+		if (*(picptr - 1) != 0xff || (*picptr & 0x80) || (*(picptr + 1) & 0x0c) || (*(picptr + 2) < 4))
+			continue;
+
+		count = 0;
+		startptr = picptr;
+
+		while (TRUE) {
+			length = ((*(picptr + 1) & 0x0f) << 8) + *(picptr + 2);
+			if (length > 0x3ff || picptr + length + 4 > testptr + testsize)
+				break;
+
+			picptr += length;
+			if (*(picptr - 1) != 0xff) {
+				picptr -= length;
+				break;
+			}
+			if ((*picptr & 0x80) || (*(picptr + 1) & 0x0c) || (*(picptr + 2) < 4))
+				break;
+
+			count++;
+		}
+
+		if (count > 10) {
+			/* Search for the start of the first subroutine */
+			for (j = 4; j < 0x3ff; j++) {
+				tmpptr = startptr - j;
+				if (*tmpptr == 0xff || tmpptr < testptr)
+					break;
+
+				length = ((*(tmpptr + 1) & 0x0f) << 8) + *(tmpptr + 2);
+				if (tmpptr + length == startptr) {
+					startptr = tmpptr;
+					break;
+				}
+			}
+
+			if (*tmpptr != 0xff) {
+				*picdata = startptr;
+				*picsize = picptr - startptr;
+				return TRUE;
+			}
+		}
+	}
+	return FALSE;
+}
+
+L9BOOL intinitialise(const char *filename, char *picname) {
+	/* init */
+	/* driverclg */
+
+	int i;
+	int hdoffset;
+	long Offset;
+	Common::File f;
+
+	if (pictureaddress) {
+		free(pictureaddress);
+		pictureaddress = NULL;
+	}
+	picturedata = NULL;
+	picturesize = 0;
+	gfxa5 = NULL;
+
+	if (!load(filename)) {
+		error("\rUnable to load: %s\r", filename);
+		return FALSE;
+	}
+
+	/* try to load graphics */
+	if (picname) {
+		if (f.open(picname)) {
+			picturesize = f.size();
+			L9Allocate(&pictureaddress, picturesize);
+			if (f.read(pictureaddress, picturesize) != picturesize) {
+				free(pictureaddress);
+				pictureaddress = NULL;
+				picturesize = 0;
+			}
+			f.close();
+		}
+	}
+	screencalled = 0;
+	l9textmode = 0;
+
+#ifdef FULLSCAN
+	FullScan(startfile, FileSize);
+#endif
+
+	Offset = Scan(startfile, FileSize);
+	if (Offset < 0) {
+		Offset = ScanV2(startfile, FileSize);
+		L9GameType = L9_V2;
+		if (Offset < 0) {
+			Offset = ScanV1(startfile, FileSize);
+			L9GameType = L9_V1;
+			if (Offset < 0) {
+				error("\rUnable to locate valid Level 9 game in file: %s\r", filename);
+				return FALSE;
+			}
+		}
+	}
+
+	startdata = startfile + Offset;
+	FileSize -= Offset;
+
+	/* setup pointers */
+	if (L9GameType == L9_V1) {
+		if (L9V1Game < 0) {
+			error("\rWhat appears to be V1 game data was found, but the game was not recognised.\rEither this is an unknown V1 game file or, more likely, it is corrupted.\r");
+			return FALSE;
+		}
+		for (i = 0; i < 6; i++) {
+			int off = L9V1Games[L9V1Game].L9Ptrs[i];
+			if (off < 0)
+				L9Pointers[i + 2] = acodeptr + off;
+			else
+				L9Pointers[i + 2] = workspace.listarea + off;
+		}
+		absdatablock = acodeptr - L9V1Games[L9V1Game].absData;
+	} else {
+		/* V2,V3,V4 */
+		hdoffset = L9GameType == L9_V2 ? 4 : 0x12;
+		for (i = 0; i < 12; i++) {
+			L9UINT16 d0 = L9WORD(startdata + hdoffset + i * 2);
+			L9Pointers[i] = (i != 11 && d0 >= 0x8000 && d0 <= 0x9000) ? workspace.listarea + d0 - 0x8000 : startdata + d0;
+		}
+		absdatablock = L9Pointers[0];
+		dictdata = L9Pointers[1];
+		list2ptr = L9Pointers[3];
+		list3ptr = L9Pointers[4];
+		/*list9startptr */
+		list9startptr = L9Pointers[10];
+		acodeptr = L9Pointers[11];
+	}
+
+	switch (L9GameType) {
+	case L9_V1: {
+		double a1;
+		startmd = acodeptr + L9V1Games[L9V1Game].msgStart;
+		startmdV2 = startmd + L9V1Games[L9V1Game].msgLen;
+
+		if (analyseV1(&a1) && a1 > 2 && a1 < 10) {
+			L9MsgType = MSGT_V1;
+#ifdef L9DEBUG
+			printf("V1 msg table: wordlen=%.2lf", a1);
+#endif
+		} else {
+			error("\rUnable to identify V1 message table in file: %s\r", filename);
+			return FALSE;
+		}
+		break;
+	}
+	case L9_V2: {
+		double a2, a1;
+		startmd = startdata + L9WORD(startdata + 0x0);
+		startmdV2 = startdata + L9WORD(startdata + 0x2);
+
+		/* determine message type */
+		if (analyseV2(&a2) && a2 > 2 && a2 < 10) {
+			L9MsgType = MSGT_V2;
+#ifdef L9DEBUG
+			printf("V2 msg table: wordlen=%.2lf", a2);
+#endif
+		} else if (analyseV1(&a1) && a1 > 2 && a1 < 10) {
+			L9MsgType = MSGT_V1;
+#ifdef L9DEBUG
+			printf("V1 msg table: wordlen=%.2lf", a1);
+#endif
+		} else {
+			error("\rUnable to identify V2 message table in file: %s\r", filename);
+			return FALSE;
+		}
+		break;
+	}
+	case L9_V3:
+	case L9_V4:
+		startmd = startdata + L9WORD(startdata + 0x2);
+		endmd = startmd + L9WORD(startdata + 0x4);
+		defdict = startdata + L9WORD(startdata + 6);
+		endwdp5 = defdict + 5 + L9WORD(startdata + 0x8);
+		dictdata = startdata + L9WORD(startdata + 0x0a);
+		dictdatalen = L9WORD(startdata + 0x0c);
+		wordtable = startdata + L9WORD(startdata + 0xe);
+		break;
+	}
+
+#ifndef NO_SCAN_GRAPHICS
+	/* If there was no graphics file, look in the game data */
+	if (pictureaddress) {
+		if (!findsubs(pictureaddress, picturesize, &picturedata, &picturesize)) {
+			picturedata = NULL;
+			picturesize = 0;
+		}
+	} else {
+		if (!findsubs(startdata, FileSize, &picturedata, &picturesize)
+		        && !findsubs(startfile, startdata - startfile, &picturedata, &picturesize)) {
+			picturedata = NULL;
+			picturesize = 0;
+		}
+	}
+#endif
+
+	memset(FirstLine, 0, FIRSTLINESIZE);
+	FirstLinePos = 0;
+
+	return TRUE;
+}
+
+L9BOOL checksumgamedata(void) {
+	return calcchecksum(startdata, L9WORD(startdata) + 1) == 0;
+}
+
+L9UINT16 movewa5d0(void) {
+	L9UINT16 ret = L9WORD(codeptr);
+	codeptr += 2;
+	return ret;
+}
+
+L9UINT16 getcon(void) {
+	if (code & 64) {
+		/* getconsmall */
+		return *codeptr++;
+	} else return movewa5d0();
+}
+
+L9BYTE *getaddr(void) {
+	if (code & 0x20) {
+		/* getaddrshort */
+		signed char diff = *codeptr++;
+		return codeptr + diff - 1;
+	} else {
+		return acodeptr + movewa5d0();
+	}
+}
+
+L9UINT16 *getvar(void) {
+#ifndef CODEFOLLOW
+	return workspace.vartable + *codeptr++;
+#else
+	cfvar2 = cfvar;
+	return cfvar = workspace.vartable + *codeptr++;
+#endif
+}
+
+void Goto(void) {
+	L9BYTE *target = getaddr();
+	if (target == codeptr - 2)
+		Running = FALSE; /* Endless loop! */
+	else
+		codeptr = target;
+}
+
+void intgosub(void) {
+	L9BYTE *newcodeptr = getaddr();
+	if (workspace.stackptr == STACKSIZE) {
+		error("\rStack overflow error\r");
+		Running = FALSE;
+		return;
+	}
+	workspace.stack[workspace.stackptr++] = (L9UINT16)(codeptr - acodeptr);
+	codeptr = newcodeptr;
+}
+
+void intreturn(void) {
+	if (workspace.stackptr == 0) {
+		error("\rStack underflow error\r");
+		Running = FALSE;
+		return;
+	}
+	codeptr = acodeptr + workspace.stack[--workspace.stackptr];
+}
+
+void printnumber(void) {
+	printdecimald0(*getvar());
+}
+
+void messagec(void) {
+	if (L9GameType <= L9_V2)
+		printmessageV2(getcon());
+	else
+		printmessage(getcon());
+}
+
+void messagev(void) {
+	if (L9GameType <= L9_V2)
+		printmessageV2(*getvar());
+	else
+		printmessage(*getvar());
+}
+
+void init(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - init");
+#endif
+}
+
+void randomnumber(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - randomnumber");
+#endif
+	L9SETWORD(a6, g_vm->getRandomNumber(0xffff));
+}
+
+void driverclg(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - driverclg");
+#endif
+}
+
+void _line(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - line");
+#endif
+}
+
+void fill(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - fill");
+#endif
+}
+
+void driverchgcol(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - driverchgcol");
+#endif
+}
+
+void drivercalcchecksum(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - calcchecksum");
+#endif
+}
+
+void driveroswrch(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - driveroswrch");
+#endif
+}
+
+void driverosrdch(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - driverosrdch");
+#endif
+
+	os_flush();
+	if (Cheating) {
+		*a6 = '\r';
+	} else {
+		/* max delay of 1/50 sec */
+		*a6 = os_readchar(20);
+	}
+}
+
+void driversavefile(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - driversavefile");
+#endif
+}
+
+void driverloadfile(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - driverloadfile");
+#endif
+}
+
+void settext(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - settext");
+#endif
+}
+
+void resettask(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - resettask");
+#endif
+}
+
+void driverinputline(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - driverinputline");
+#endif
+}
+
+void returntogem(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - returntogem");
+#endif
+}
+
+void lensdisplay(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - lensdisplay");
+#endif
+
+	printstring("\rLenslok code is ");
+	printchar(*a6);
+	printchar(*(a6 + 1));
+	printchar('\r');
+}
+
+void allocspace(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - allocspace");
+#endif
+}
+
+void driver14(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - call 14");
+#endif
+
+	*a6 = 0;
+}
+
+void showbitmap(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - showbitmap");
+#endif
+
+	os_show_bitmap(a6[1], a6[3], a6[5]);
+}
+
+void checkfordisc(L9BYTE *a6) {
+#ifdef L9DEBUG
+	printf("driver - checkfordisc");
+#endif
+
+	*a6 = 0;
+	list9startptr[2] = 0;
+}
+
+void driver(int d0, L9BYTE *a6) {
+	switch (d0) {
+	case 0:
+		init(a6);
+		break;
+	case 0x0c:
+		randomnumber(a6);
+		break;
+	case 0x10:
+		driverclg(a6);
+		break;
+	case 0x11:
+		_line(a6);
+		break;
+	case 0x12:
+		fill(a6);
+		break;
+	case 0x13:
+		driverchgcol(a6);
+		break;
+	case 0x01:
+		drivercalcchecksum(a6);
+		break;
+	case 0x02:
+		driveroswrch(a6);
+		break;
+	case 0x03:
+		driverosrdch(a6);
+		break;
+	case 0x05:
+		driversavefile(a6);
+		break;
+	case 0x06:
+		driverloadfile(a6);
+		break;
+	case 0x07:
+		settext(a6);
+		break;
+	case 0x08:
+		resettask(a6);
+		break;
+	case 0x04:
+		driverinputline(a6);
+		break;
+	case 0x09:
+		returntogem(a6);
+		break;
+	/*
+	        case 0x16: ramsave(a6); break;
+	        case 0x17: ramload(a6); break;
+	*/
+	case 0x19:
+		lensdisplay(a6);
+		break;
+	case 0x1e:
+		allocspace(a6);
+		break;
+	/* v4 */
+	case 0x0e:
+		driver14(a6);
+		break;
+	case 0x20:
+		showbitmap(a6);
+		break;
+	case 0x22:
+		checkfordisc(a6);
+		break;
+	}
+}
+
+void ramsave(int i) {
+#ifdef L9DEBUG
+	printf("driver - ramsave %d", i);
+#endif
+
+	memmove(ramsavearea + i, workspace.vartable, sizeof(SaveStruct));
+}
+
+void ramload(int i) {
+#ifdef L9DEBUG
+	printf("driver - ramload %d", i);
+#endif
+
+	memmove(workspace.vartable, ramsavearea + i, sizeof(SaveStruct));
+}
+
+void calldriver(void) {
+	L9BYTE *a6 = list9startptr;
+	int d0 = *a6++;
+#ifdef CODEFOLLOW
+	fprintf(f, " %s", drivercalls[d0]);
+#endif
+
+	if (d0 == 0x16 || d0 == 0x17) {
+		int d1 = *a6;
+		if (d1 > 0xfa) *a6 = 1;
+		else if (d1 + 1 >= RAMSAVESLOTS) *a6 = 0xff;
+		else {
+			*a6 = 0;
+			if (d0 == 0x16) ramsave(d1 + 1);
+			else ramload(d1 + 1);
+		}
+		*list9startptr = *a6;
+	} else if (d0 == 0x0b) {
+		char NewName[MAX_PATH];
+		strcpy(NewName, LastGame);
+		if (*a6 == 0) {
+			printstring("\rSearching for next sub-game file.\r");
+			if (!os_get_game_file(NewName, MAX_PATH)) {
+				printstring("\rFailed to load game.\r");
+				return;
+			}
+		} else {
+			os_set_filenumber(NewName, MAX_PATH, *a6);
+		}
+		LoadGame2(NewName, NULL);
+	} else driver(d0, a6);
+}
+
+void L9Random(void) {
+#ifdef CODEFOLLOW
+	fprintf(f, " %d", randomseed);
+#endif
+	randomseed = (((randomseed << 8) + 0x0a - randomseed) << 2) + randomseed + 1;
+	*getvar() = randomseed & 0xff;
+#ifdef CODEFOLLOW
+	fprintf(f, " %d", randomseed);
+#endif
+}
+
+void save(void) {
+	L9UINT16 checksum;
+	int i;
+#ifdef L9DEBUG
+	printf("function - save");
+#endif
+	/* does a full save, workpace, stack, codeptr, stackptr, game name, checksum */
+
+	workspace.Id = L9_ID;
+	workspace.codeptr = codeptr - acodeptr;
+	workspace.listsize = LISTAREASIZE;
+	workspace.stacksize = STACKSIZE;
+	workspace.filenamesize = MAX_PATH;
+	workspace.checksum = 0;
+	strcpy(workspace.filename, LastGame);
+
+	checksum = 0;
+	for (i = 0; i < (int)sizeof(GameState); i++) checksum += ((L9BYTE *) &workspace)[i];
+	workspace.checksum = checksum;
+
+	if (os_save_file((L9BYTE *) &workspace, sizeof(workspace))) printstring("\rGame saved.\r");
+	else printstring("\rUnable to save game.\r");
+}
+
+L9BOOL CheckFile(GameState *gs) {
+	L9UINT16 checksum;
+	int i;
+	char c = 'Y';
+
+	if (gs->Id != L9_ID) return FALSE;
+	checksum = gs->checksum;
+	gs->checksum = 0;
+	for (i = 0; i < (int)sizeof(GameState); i++)
+		checksum -= *((L9BYTE *) gs + i);
+	if (checksum) return FALSE;
+	if (scumm_stricmp(gs->filename, LastGame)) {
+		printstring("\rWarning: game path name does not match, you may be about to load this position file into the wrong story file.\r");
+		printstring("Are you sure you want to restore? (Y/N)");
+		os_flush();
+
+		c = '\0';
+		while ((c != 'y') && (c != 'Y') && (c != 'n') && (c != 'N'))
+			c = os_readchar(20);
+	}
+	if ((c == 'y') || (c == 'Y'))
+		return TRUE;
+	return FALSE;
+}
+
+void NormalRestore(void) {
+	GameState temp;
+	int Bytes;
+#ifdef L9DEBUG
+	printf("function - restore");
+#endif
+	if (Cheating) {
+		/* not really an error */
+		Cheating = FALSE;
+		error("\rWord is: %s\r", ibuff);
+	}
+
+	if (os_load_file((L9BYTE *) &temp, &Bytes, sizeof(GameState))) {
+		if (Bytes == V1FILESIZE) {
+			printstring("\rGame restored.\r");
+			memset(workspace.listarea, 0, LISTAREASIZE);
+			memmove(workspace.vartable, &temp, V1FILESIZE);
+		} else if (CheckFile(&temp)) {
+			printstring("\rGame restored.\r");
+			/* only copy in workspace */
+			memmove(workspace.vartable, temp.vartable, sizeof(SaveStruct));
+		} else {
+			printstring("\rSorry, unrecognised format. Unable to restore\r");
+		}
+	} else printstring("\rUnable to restore game.\r");
+}
+
+void restore(void) {
+	int Bytes;
+	GameState temp;
+	if (os_load_file((L9BYTE *) &temp, &Bytes, sizeof(GameState))) {
+		if (Bytes == V1FILESIZE) {
+			printstring("\rGame restored.\r");
+			/* only copy in workspace */
+			memset(workspace.listarea, 0, LISTAREASIZE);
+			memmove(workspace.vartable, &temp, V1FILESIZE);
+		} else if (CheckFile(&temp)) {
+			printstring("\rGame restored.\r");
+			/* full restore */
+			memmove(&workspace, &temp, sizeof(GameState));
+			codeptr = acodeptr + workspace.codeptr;
+		} else {
+			printstring("\rSorry, unrecognised format. Unable to restore\r");
+		}
+	} else printstring("\rUnable to restore game.\r");
+}
+
+void playback(void) {
+	if (scriptfile)
+		delete scriptfile;
+	scriptfile = os_open_script_file();
+
+	if (scriptfile)
+		printstring("\rPlaying back input from script file.\r");
+	else
+		printstring("\rUnable to play back script file.\r");
+}
+
+void l9_fgets(char *s, int n, Common::SeekableReadStream *f) {
+	int c = '\0';
+	int count = 0;
+
+	while ((c != '\n') && (c != '\r') && (c != EOF) && (count < n - 1)) {
+		c = f->readByte();
+		*s++ = c;
+		count++;
+	}
+	*s = '\0';
+
+	if (c == EOF) {
+		s--;
+		*s = '\n';
+	} else if (c == '\r') {
+		s--;
+		*s = '\n';
+
+		c = f->readByte();
+		if ((c != '\r') && (c != EOF))
+			f->seek(-1, SEEK_CUR);
+	}
+}
+
+L9BOOL scriptinput(char *buffer, int size) {
+	while (scriptfile != NULL) {
+		if (scriptfile->eos()) {
+			delete scriptfile;
+			scriptfile = NULL;
+		} else {
+			char *p = buffer;
+			*p = '\0';
+			l9_fgets(buffer, size, scriptfile);
+			while (*p != '\0') {
+				switch (*p) {
+				case '\n':
+				case '\r':
+				case '[':
+				case ';':
+					*p = '\0';
+					break;
+				case '#':
+					if ((p == buffer) && (scumm_strnicmp(p, "#seed ", 6) == 0))
+						p++;
+					else
+						*p = '\0';
+					break;
+				default:
+					p++;
+					break;
+				}
+			}
+			if (*buffer != '\0') {
+				printstring(buffer);
+				lastchar = lastactualchar = '.';
+				return TRUE;
+			}
+		}
+	}
+	return FALSE;
+}
+
+void clearworkspace(void) {
+	memset(workspace.vartable, 0, sizeof(workspace.vartable));
+}
+
+void ilins(int d0) {
+	error("\rIllegal instruction: %d\r", d0);
+	Running = FALSE;
+}
+
+void function(void) {
+	int d0 = *codeptr++;
+#ifdef CODEFOLLOW
+	fprintf(f, " %s", d0 == 250 ? "printstr" : functions[d0 - 1]);
+#endif
+
+	switch (d0) {
+	case 1:
+		if (L9GameType == L9_V1)
+			StopGame();
+		else
+			calldriver();
+		break;
+	case 2:
+		L9Random();
+		break;
+	case 3:
+		save();
+		break;
+	case 4:
+		NormalRestore();
+		break;
+	case 5:
+		clearworkspace();
+		break;
+	case 6:
+		workspace.stackptr = 0;
+		break;
+	case 250:
+		printstring((char *) codeptr);
+		while (*codeptr++);
+		break;
+
+	default:
+		ilins(d0);
+	}
+}
+
+void findmsgequiv(int d7) {
+	int d4 = -1, d0;
+	L9BYTE *a2 = startmd;
+
+	do {
+		d4++;
+		if (a2 > endmd) return;
+		d0 = *a2;
+		if (d0 & 0x80) {
+			a2++;
+			d4 += d0 & 0x7f;
+		} else if (d0 & 0x40) {
+			int d6 = getmdlength(&a2);
+			do {
+				int d1;
+				if (d6 == 0) break;
+
+				d1 = *a2++;
+				d6--;
+				if (d1 & 0x80) {
+					if (d1 < 0x90) {
+						a2++;
+						d6--;
+					} else {
+						d0 = (d1 << 8) + *a2++;
+						d6--;
+						if (d7 == (d0 & 0xfff)) {
+							d0 = ((d0 << 1) & 0xe000) | d4;
+							list9ptr[1] = d0;
+							list9ptr[0] = d0 >> 8;
+							list9ptr += 2;
+							if (list9ptr >= list9startptr + 0x20) return;
+						}
+					}
+				}
+			} while (TRUE);
+		} else {
+			int len = getmdlength(&a2);
+			a2 += len;
+		}
+	} while (TRUE);
+}
+
+L9BOOL unpackword(void) {
+	L9BYTE *a3;
+
+	if (unpackd3 == 0x1b) return TRUE;
+
+	a3 = (L9BYTE *) threechars + (unpackd3 & 3);
+
+	/*uw01 */
+	while (TRUE) {
+		L9BYTE d0 = getdictionarycode();
+		if (dictptr >= endwdp5) return TRUE;
+		if (d0 >= 0x1b) {
+			*a3 = 0;
+			unpackd3 = d0;
+			return FALSE;
+		}
+		*a3++ = getdictionary(d0);
+	}
+}
+
+L9BOOL initunpack(L9BYTE *ptr) {
+	initdict(ptr);
+	unpackd3 = 0x1c;
+	return unpackword();
+}
+
+int partword(char c) {
+	c = tolower(c);
+
+	if (c == 0x27 || c == 0x2d) return 0;
+	if (c < 0x30) return 1;
+	if (c < 0x3a) return 0;
+	if (c < 0x61) return 1;
+	if (c < 0x7b) return 0;
+	return 1;
+}
+
+L9UINT32 readdecimal(char *buff) {
+	return atol(buff);
+}
+
+void checknumber(void) {
+	if (*obuff >= 0x30 && *obuff < 0x3a) {
+		if (L9GameType == L9_V4) {
+			*list9ptr = 1;
+			L9SETWORD(list9ptr + 1, readdecimal(obuff));
+			L9SETWORD(list9ptr + 3, 0);
+		} else {
+			L9SETDWORD(list9ptr, readdecimal(obuff));
+			L9SETWORD(list9ptr + 4, 0);
+		}
+	} else {
+		L9SETWORD(list9ptr, 0x8000);
+		L9SETWORD(list9ptr + 2, 0);
+	}
+}
+
+void NextCheat(void) {
+	/* restore game status */
+	memmove(&workspace, &CheatWorkspace, sizeof(GameState));
+	codeptr = acodeptr + workspace.codeptr;
+
+	if (!((L9GameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++))) {
+		Cheating = FALSE;
+		printstring("\rCheat failed.\r");
+		*ibuff = 0;
+	}
+}
+
+void StartCheat(void) {
+	Cheating = TRUE;
+	CheatWord = 0;
+
+	/* save current game status */
+	memmove(&CheatWorkspace, &workspace, sizeof(GameState));
+	CheatWorkspace.codeptr = codeptr - acodeptr;
+
+	NextCheat();
+}
+
+/* v3,4 input routine */
+
+L9BOOL GetWordV3(char *buff, int Word) {
+	int i;
+	int subdict = 0;
+	/* 26*4-1=103 */
+
+	initunpack(startdata + L9WORD(dictdata));
+	unpackword();
+
+	while (Word--) {
+		if (unpackword()) {
+			if (++subdict == dictdatalen) return FALSE;
+			initunpack(startdata + L9WORD(dictdata + (subdict << 2)));
+			Word++; /* force unpack again */
+		}
+	}
+	strcpy(buff, threechars);
+	for (i = 0; i < (int)strlen(buff); i++) buff[i] &= 0x7f;
+	return TRUE;
+}
+
+L9BOOL CheckHash(void) {
+	if (scumm_stricmp(ibuff, "#cheat") == 0) StartCheat();
+	else if (scumm_stricmp(ibuff, "#save") == 0) {
+		save();
+		return TRUE;
+	} else if (scumm_stricmp(ibuff, "#restore") == 0) {
+		restore();
+		return TRUE;
+	} else if (scumm_stricmp(ibuff, "#quit") == 0) {
+		StopGame();
+		printstring("\rGame Terminated\r");
+		return TRUE;
+	} else if (scumm_stricmp(ibuff, "#dictionary") == 0) {
+		CheatWord = 0;
+		printstring("\r");
+		while ((L9GameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++)) {
+			error("%s ", ibuff);
+			if (os_stoplist() || !Running) break;
+		}
+		printstring("\r");
+		return TRUE;
+	} else if (scumm_strnicmp(ibuff, "#picture ", 9) == 0) {
+		int pic = 0;
+		if (sscanf(ibuff + 9, "%d", &pic) == 1) {
+			if (L9GameType == L9_V4)
+				os_show_bitmap(pic, 0, 0);
+			else
+				show_picture(pic);
+		}
+
+		lastactualchar = 0;
+		printchar('\r');
+		return TRUE;
+	} else if (scumm_strnicmp(ibuff, "#seed ", 6) == 0) {
+		int seed = 0;
+		if (sscanf(ibuff + 6, "%d", &seed) == 1)
+			randomseed = constseed = seed;
+		lastactualchar = 0;
+		printchar('\r');
+		return TRUE;
+	} else if (scumm_stricmp(ibuff, "#play") == 0) {
+		playback();
+		return TRUE;
+	}
+	return FALSE;
+}
+
+L9BOOL IsInputChar(char c) {
+	if (c == '-' || c == '\'')
+		return TRUE;
+	if ((L9GameType >= L9_V3) && (c == '.' || c == ','))
+		return TRUE;
+	return Common::isAlnum(c);
+}
+
+L9BOOL corruptinginput(void) {
+	L9BYTE *a0, *a2, *a6;
+	int d0, d1, d2, keywordnumber, abrevword;
+	char *iptr;
+
+	list9ptr = list9startptr;
+
+	if (ibuffptr == NULL) {
+		if (Cheating) NextCheat();
+		else {
+			/* flush */
+			os_flush();
+			lastchar = lastactualchar = '.';
+			/* get input */
+			if (!scriptinput(ibuff, IBUFFSIZE)) {
+				if (!os_input(ibuff, IBUFFSIZE))
+					return FALSE; /* fall through */
+			}
+			if (CheckHash())
+				return FALSE;
+
+			/* check for invalid chars */
+			for (iptr = ibuff; *iptr != 0; iptr++) {
+				if (!IsInputChar(*iptr))
+					*iptr = ' ';
+			}
+
+			/* force CR but prevent others */
+			os_printchar(lastactualchar = '\r');
+		}
+		ibuffptr = (L9BYTE *) ibuff;
+	}
+
+	a2 = (L9BYTE *) obuff;
+	a6 = ibuffptr;
+
+	/*ip05 */
+	while (TRUE) {
+		d0 = *a6++;
+		if (d0 == 0) {
+			ibuffptr = NULL;
+			L9SETWORD(list9ptr, 0);
+			return TRUE;
+		}
+		if (partword((char)d0) == 0) break;
+		if (d0 != 0x20) {
+			ibuffptr = a6;
+			L9SETWORD(list9ptr, 0);
+			L9SETWORD(list9ptr + 2, 0);
+			list9ptr[1] = d0;
+			*a2 = 0x20;
+			keywordnumber = -1;
+			return TRUE;
+		}
+	}
+
+	a6--;
+	/*ip06loop */
+	do {
+		d0 = *a6++;
+		if (partword((char)d0) == 1) break;
+		d0 = tolower(d0);
+		*a2++ = d0;
+	} while (a2 < (L9BYTE *) obuff + 0x1f);
+	/*ip06a */
+	*a2 = 0x20;
+	a6--;
+	ibuffptr = a6;
+	abrevword = -1;
+	keywordnumber = -1;
+	list9ptr = list9startptr;
+	/* setindex */
+	a0 = dictdata;
+	d2 = dictdatalen;
+	d0 = *obuff - 0x61;
+	if (d0 < 0) {
+		a6 = defdict;
+		d1 = 0;
+	} else {
+		/*ip10 */
+		d1 = 0x67;
+		if (d0 < 0x1a) {
+			d1 = d0 << 2;
+			d0 = obuff[1];
+			if (d0 != 0x20) d1 += ((d0 - 0x61) >> 3) & 3;
+		}
+		/*ip13 */
+		if (d1 >= d2) {
+			checknumber();
+			return TRUE;
+		}
+		a0 += d1 << 2;
+		a6 = startdata + L9WORD(a0);
+		d1 = L9WORD(a0 + 2);
+	}
+	/*ip13gotwordnumber */
+
+	initunpack(a6);
+	/*ip14 */
+	d1--;
+	do {
+		d1++;
+		if (unpackword()) {
+			/* ip21b */
+			if (abrevword == -1) break; /* goto ip22 */
+			else d0 = abrevword; /* goto ip18b */
+		} else {
+			L9BYTE *a1 = (L9BYTE *) threechars;
+			int d6 = -1;
+
+			a0 = (L9BYTE *) obuff;
+			/*ip15 */
+			do {
+				d6++;
+				d0 = tolower(*a1++ & 0x7f);
+				d2 = *a0++;
+			} while (d0 == d2);
+
+			if (d2 != 0x20) {
+				/* ip17 */
+				if (abrevword == -1) continue;
+				else d0 = -1;
+			} else if (d0 == 0) d0 = d1;
+			else if (abrevword != -1) break;
+			else if (d6 >= 4) d0 = d1;
+			else {
+				abrevword = d1;
+				continue;
+			}
+		}
+		/*ip18b */
+		findmsgequiv(d1);
+
+		abrevword = -1;
+		if (list9ptr != list9startptr) {
+			L9SETWORD(list9ptr, 0);
+			return TRUE;
+		}
+	} while (TRUE);
+	/* ip22 */
+	checknumber();
+	return TRUE;
+}
+
+/* version 2 stuff hacked from bbc v2 files */
+
+L9BOOL IsDictionaryChar(char c) {
+	switch (c) {
+	case '?':
+	case '-':
+	case '\'':
+	case '/':
+		return TRUE;
+	case '!':
+	case '.':
+	case ',':
+		return TRUE;
+	}
+	return Common::isUpper(c) || Common::isDigit(c);
+}
+
+L9BOOL GetWordV2(char *buff, int Word) {
+	L9BYTE *ptr = dictdata, x;
+
+	while (Word--) {
+		do {
+			x = *ptr++;
+		} while (x > 0 && x < 0x7f);
+		if (x == 0) return FALSE; /* no more words */
+		ptr++;
+	}
+	do {
+		x = *ptr++;
+		if (!IsDictionaryChar(x & 0x7f)) return FALSE;
+		*buff++ = x & 0x7f;
+	} while (x > 0 && x < 0x7f);
+	*buff = 0;
+	return TRUE;
+}
+
+L9BOOL inputV2(int *wordcount) {
+	L9BYTE a, x;
+	L9BYTE *ibuffp, *obuffptr, *ptr, *list0ptr;
+	char *iptr;
+
+	if (Cheating) NextCheat();
+	else {
+		os_flush();
+		lastchar = lastactualchar = '.';
+		/* get input */
+		if (!scriptinput(ibuff, IBUFFSIZE)) {
+			if (!os_input(ibuff, IBUFFSIZE))
+				return FALSE; /* fall through */
+		}
+		if (CheckHash())
+			return FALSE;
+
+		/* check for invalid chars */
+		for (iptr = ibuff; *iptr != 0; iptr++) {
+			if (!IsInputChar(*iptr))
+				*iptr = ' ';
+		}
+
+		/* force CR but prevent others */
+		os_printchar(lastactualchar = '\r');
+	}
+	/* add space onto end */
+	ibuffp = (L9BYTE *) strchr(ibuff, 0);
+	*ibuffp++ = 32;
+	*ibuffp = 0;
+
+	*wordcount = 0;
+	ibuffp = (L9BYTE *) ibuff;
+	obuffptr = (L9BYTE *) obuff;
+	/* ibuffp=76,77 */
+	/* obuffptr=84,85 */
+	/* list0ptr=7c,7d */
+	list0ptr = dictdata;
+
+	while (*ibuffp == 32) ++ibuffp;
+
+	ptr = ibuffp;
+	do {
+		while (*ptr == 32) ++ptr;
+		if (*ptr == 0) break;
+		(*wordcount)++;
+		do {
+			a = *++ptr;
+		} while (a != 32 && a != 0);
+	} while (*ptr > 0);
+
+	while (TRUE) {
+		ptr = ibuffp; /* 7a,7b */
+		while (*ibuffp == 32) ++ibuffp;
+
+		while (TRUE) {
+			a = *ibuffp;
+			x = *list0ptr++;
+
+			if (a == 32) break;
+			if (a == 0) {
+				*obuffptr++ = 0;
+				return TRUE;
+			}
+
+			++ibuffp;
+			if (!IsDictionaryChar(x & 0x7f)) x = 0;
+			if (tolower(x & 0x7f) != tolower(a)) {
+				while (x > 0 && x < 0x7f) x = *list0ptr++;
+				if (x == 0) {
+					do {
+						a = *ibuffp++;
+						if (a == 0) {
+							*obuffptr = 0;
+							return TRUE;
+						}
+					} while (a != 32);
+					while (*ibuffp == 32) ++ibuffp;
+					list0ptr = dictdata;
+					ptr = ibuffp;
+				} else {
+					list0ptr++;
+					ibuffp = ptr;
+				}
+			} else if (x >= 0x7f) break;
+		}
+
+		a = *ibuffp;
+		if (a != 32) {
+			ibuffp = ptr;
+			list0ptr += 2;
+			continue;
+		}
+		--list0ptr;
+		while (*list0ptr++ < 0x7e);
+		*obuffptr++ = *list0ptr;
+		while (*ibuffp == 32) ++ibuffp;
+		list0ptr = dictdata;
+	}
+}
+
+void input(void) {
+	if (L9GameType == L9_V3 && FirstPicture >= 0) {
+		show_picture(FirstPicture);
+		FirstPicture = -1;
+	}
+
+	/* if corruptinginput() returns false then, input will be called again
+	   next time around instructionloop, this is used when save() and restore()
+	   are called out of line */
+
+	codeptr--;
+	if (L9GameType <= L9_V2) {
+		int wordcount;
+		if (inputV2(&wordcount)) {
+			L9BYTE *obuffptr = (L9BYTE *) obuff;
+			codeptr++;
+			*getvar() = *obuffptr++;
+			*getvar() = *obuffptr++;
+			*getvar() = *obuffptr;
+			*getvar() = wordcount;
+		}
+	} else if (corruptinginput()) codeptr += 5;
+}
+
+void varcon(void) {
+	L9UINT16 d6 = getcon();
+	*getvar() = d6;
+
+#ifdef CODEFOLLOW
+	fprintf(f, " Var[%d]=%d)", cfvar - workspace.vartable, *cfvar);
+#endif
+}
+
+void varvar(void) {
+	L9UINT16 d6 = *getvar();
+	*getvar() = d6;
+
+#ifdef CODEFOLLOW
+	fprintf(f, " Var[%d]=Var[%d] (=%d)", cfvar - workspace.vartable, cfvar2 - workspace.vartable, d6);
+#endif
+}
+
+void _add(void) {
+	L9UINT16 d0 = *getvar();
+	*getvar() += d0;
+
+#ifdef CODEFOLLOW
+	fprintf(f, " Var[%d]+=Var[%d] (+=%d)", cfvar - workspace.vartable, cfvar2 - workspace.vartable, d0);
+#endif
+}
+
+void _sub(void) {
+	L9UINT16 d0 = *getvar();
+	*getvar() -= d0;
+
+#ifdef CODEFOLLOW
+	fprintf(f, " Var[%d]-=Var[%d] (-=%d)", cfvar - workspace.vartable, cfvar2 - workspace.vartable, d0);
+#endif
+}
+
+void jump(void) {
+	L9UINT16 d0 = L9WORD(codeptr);
+	L9BYTE *a0;
+	codeptr += 2;
+
+	a0 = acodeptr + ((d0 + ((*getvar()) << 1)) & 0xffff);
+	codeptr = acodeptr + L9WORD(a0);
+}
+
+/* bug */
+void exit1(L9BYTE *d4, L9BYTE *d5p, L9BYTE d6, L9BYTE d7) {
+	L9BYTE *a0 = absdatablock;
+	L9BYTE d1 = d7, d0;
+	if (--d1) {
+		do {
+			d0 = *a0;
+			if (L9GameType == L9_V4) {
+				if ((d0 == 0) && (*(a0 + 1) == 0))
+					goto notfn4;
+			}
+			a0 += 2;
+		} while ((d0 & 0x80) == 0 || --d1);
+	}
+
+	do {
+		*d4 = *a0++;
+		if (((*d4) & 0xf) == d6) {
+			*d5p = *a0;
+			return;
+		}
+		a0++;
+	} while (((*d4) & 0x80) == 0);
+
+	/* notfn4 */
+notfn4:
+	d6 = exitreversaltable[d6];
+	a0 = absdatablock;
+	*d5p = 1;
+
+	do {
+		*d4 = *a0++;
+		if (((*d4) & 0x10) == 0 || ((*d4) & 0xf) != d6) a0++;
+		else if (*a0++ == d7) return;
+		/* exit6noinc */
+		if ((*d4) & 0x80)(*d5p)++;
+	} while (*d4);
+	*d5p = 0;
+}
+
+void Exit(void) {
+	L9BYTE d4, d5v;
+	L9BYTE d7 = (L9BYTE) * getvar();
+	L9BYTE d6 = (L9BYTE) * getvar();
+#ifdef CODEFOLLOW
+	fprintf(f, " d7=%d d6=%d", d7, d6);
+#endif
+	exit1(&d4, &d5v, d6, d7);
+
+	*getvar() = (d4 & 0x70) >> 4;
+	*getvar() = d5v;
+#ifdef CODEFOLLOW
+	fprintf(f, " Var[%d]=%d(d4=%d) Var[%d]=%d",
+	        cfvar2 - workspace.vartable, (d4 & 0x70) >> 4, d4, cfvar - workspace.vartable, d5v);
+#endif
+}
+
+void ifeqvt(void) {
+	L9UINT16 d0 = *getvar();
+	L9UINT16 d1 = *getvar();
+	L9BYTE *a0 = getaddr();
+	if (d0 == d1) codeptr = a0;
+
+#ifdef CODEFOLLOW
+	fprintf(f, " if Var[%d]=Var[%d] goto %d (%s)", cfvar2 - workspace.vartable, cfvar - workspace.vartable, (L9UINT32)(a0 - acodeptr), d0 == d1 ? "Yes" : "No");
+#endif
+}
+
+void ifnevt(void) {
+	L9UINT16 d0 = *getvar();
+	L9UINT16 d1 = *getvar();
+	L9BYTE *a0 = getaddr();
+	if (d0 != d1) codeptr = a0;
+
+#ifdef CODEFOLLOW
+	fprintf(f, " if Var[%d]!=Var[%d] goto %d (%s)", cfvar2 - workspace.vartable, cfvar - workspace.vartable, (L9UINT32)(a0 - acodeptr), d0 != d1 ? "Yes" : "No");
+#endif
+}
+
+void ifltvt(void) {
+	L9UINT16 d0 = *getvar();
+	L9UINT16 d1 = *getvar();
+	L9BYTE *a0 = getaddr();
+	if (d0 < d1) codeptr = a0;
+
+#ifdef CODEFOLLOW
+	fprintf(f, " if Var[%d]<Var[%d] goto %d (%s)", cfvar2 - workspace.vartable, cfvar - workspace.vartable, (L9UINT32)(a0 - acodeptr), d0 < d1 ? "Yes" : "No");
+#endif
+}
+
+void ifgtvt(void) {
+	L9UINT16 d0 = *getvar();
+	L9UINT16 d1 = *getvar();
+	L9BYTE *a0 = getaddr();
+	if (d0 > d1) codeptr = a0;
+
+#ifdef CODEFOLLOW
+	fprintf(f, " if Var[%d]>Var[%d] goto %d (%s)", cfvar2 - workspace.vartable, cfvar - workspace.vartable, (L9UINT32)(a0 - acodeptr), d0 > d1 ? "Yes" : "No");
+#endif
+}
+
+int scalex(int x) {
+	return (gfx_mode != GFX_V3C) ? (x >> 6) : (x >> 5);
+}
+
+int scaley(int y) {
+	return (gfx_mode == GFX_V2) ? 127 - (y >> 7) : 95 - (((y >> 5) + (y >> 6)) >> 3);
+}
+
+void detect_gfx_mode(void) {
+	if (L9GameType == L9_V3) {
+		/* These V3 games use graphics logic similar to the V2 games */
+		if (strstr(FirstLine, "price of magik") != 0)
+			gfx_mode = GFX_V3A;
+		else if (strstr(FirstLine, "the archers") != 0)
+			gfx_mode = GFX_V3A;
+		else if (strstr(FirstLine, "secret diary of adrian mole") != 0)
+			gfx_mode = GFX_V3A;
+		else if ((strstr(FirstLine, "worm in paradise") != 0)
+		         && (strstr(FirstLine, "silicon dreams") == 0))
+			gfx_mode = GFX_V3A;
+		else if (strstr(FirstLine, "growing pains of adrian mole") != 0)
+			gfx_mode = GFX_V3B;
+		else if (strstr(FirstLine, "jewels of darkness") != 0 && picturesize < 11000)
+			gfx_mode = GFX_V3B;
+		else if (strstr(FirstLine, "silicon dreams") != 0) {
+			if (picturesize > 11000
+			        || (startdata[0] == 0x14 && startdata[1] == 0x7d)  /* Return to Eden /SD (PC) */
+			        || (startdata[0] == 0xd7 && startdata[1] == 0x7c)) /* Worm in Paradise /SD (PC) */
+				gfx_mode = GFX_V3C;
+			else
+				gfx_mode = GFX_V3B;
+		} else
+			gfx_mode = GFX_V3C;
+	} else
+		gfx_mode = GFX_V2;
+}
+
+void _screen(void) {
+	int mode = 0;
+
+	if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
+		if (*codeptr++)
+			codeptr++;
+		return;
+	}
+
+	detect_gfx_mode();
+	l9textmode = *codeptr++;
+	if (l9textmode) {
+		if (L9GameType == L9_V4)
+			mode = 2;
+		else if (picturedata)
+			mode = 1;
+	}
+	os_graphics(mode);
+
+	screencalled = 1;
+
+#ifdef L9DEBUG
+	printf("screen %s", l9textmode ? "graphics" : "text");
+#endif
+
+	if (l9textmode) {
+		codeptr++;
+		/* clearg */
+		/* gintclearg */
+		os_cleargraphics();
+
+		/* title pic */
+		if (showtitle == 1 && mode == 2) {
+			showtitle = 0;
+			os_show_bitmap(0, 0, 0);
+		}
+	}
+	/* screent */
+}
+
+void cleartg(void) {
+	int d0 = *codeptr++;
+#ifdef L9DEBUG
+	printf("cleartg %s", d0 ? "graphics" : "text");
+#endif
+
+	if (d0) {
+		/* clearg */
+		if (l9textmode)
+			/* gintclearg */
+			os_cleargraphics();
+	}
+	/* cleart */
+	/* oswrch(0x0c) */
+}
+
+L9BOOL validgfxptr(L9BYTE *a5) {
+	return ((a5 >= picturedata) && (a5 < picturedata + picturesize));
+}
+
+L9BOOL findsub(int d0, L9BYTE **a5) {
+	int d1, d2, d3, d4;
+
+	d1 = d0 << 4;
+	d2 = d1 >> 8;
+	*a5 = picturedata;
+	/* findsubloop */
+	while (TRUE) {
+		d3 = *(*a5)++;
+		if (!validgfxptr(*a5))
+			return FALSE;
+		if (d3 & 0x80)
+			return FALSE;
+		if (d2 == d3) {
+			if ((d1 & 0xff) == (*(*a5) & 0xf0)) {
+				(*a5) += 2;
+				return TRUE;
+			}
+		}
+
+		d3 = *(*a5)++ & 0x0f;
+		if (!validgfxptr(*a5))
+			return FALSE;
+
+		d4 = **a5;
+		if ((d3 | d4) == 0)
+			return FALSE;
+
+		(*a5) += (d3 << 8) + d4 - 2;
+		if (!validgfxptr(*a5))
+			return FALSE;
+	}
+}
+
+void gosubd0(int d0, L9BYTE **a5) {
+	if (GfxA5StackPos < GFXSTACKSIZE) {
+		GfxA5Stack[GfxA5StackPos] = *a5;
+		GfxA5StackPos++;
+		GfxScaleStack[GfxScaleStackPos] = scale;
+		GfxScaleStackPos++;
+
+		if (findsub(d0, a5) == FALSE) {
+			GfxA5StackPos--;
+			*a5 = GfxA5Stack[GfxA5StackPos];
+			GfxScaleStackPos--;
+			scale = GfxScaleStack[GfxScaleStackPos];
+		}
+	}
+}
+
+void newxy(int x, int y) {
+	drawx += (x * scale) & ~7;
+	drawy += (y * scale) & ~7;
+}
+
+/* sdraw instruction plus arguments are stored in an 8 bit word.
+       76543210
+       iixxxyyy
+   where i is instruction code
+         x is x argument, high bit is sign
+         y is y argument, high bit is sign
+*/
+void sdraw(int d7) {
+	int x, y, x1, y1;
+
+	/* getxy1 */
+	x = (d7 & 0x18) >> 3;
+	if (d7 & 0x20)
+		x = (x | 0xfc) - 0x100;
+	y = (d7 & 0x3) << 2;
+	if (d7 & 0x4)
+		y = (y | 0xf0) - 0x100;
+
+	if (reflectflag & 2)
+		x = -x;
+	if (reflectflag & 1)
+		y = -y;
+
+	/* gintline */
+	x1 = drawx;
+	y1 = drawy;
+	newxy(x, y);
+
+#ifdef L9DEBUG
+	printf("gfx - sdraw (%d,%d) (%d,%d) colours %d,%d",
+	       x1, y1, drawx, drawy, gintcolour & 3, option & 3);
+#endif
+
+	os_drawline(scalex(x1), scaley(y1), scalex(drawx), scaley(drawy),
+	            gintcolour & 3, option & 3);
+}
+
+/* smove instruction plus arguments are stored in an 8 bit word.
+       76543210
+       iixxxyyy
+   where i is instruction code
+         x is x argument, high bit is sign
+         y is y argument, high bit is sign
+*/
+void smove(int d7) {
+	int x, y;
+
+	/* getxy1 */
+	x = (d7 & 0x18) >> 3;
+	if (d7 & 0x20)
+		x = (x | 0xfc) - 0x100;
+	y = (d7 & 0x3) << 2;
+	if (d7 & 0x4)
+		y = (y | 0xf0) - 0x100;
+
+	if (reflectflag & 2)
+		x = -x;
+	if (reflectflag & 1)
+		y = -y;
+	newxy(x, y);
+}
+
+void sgosub(int d7, L9BYTE **a5) {
+	int d0 = d7 & 0x3f;
+#ifdef L9DEBUG
+	printf("gfx - sgosub 0x%.2x", d0);
+#endif
+	gosubd0(d0, a5);
+}
+
+/* draw instruction plus arguments are stored in a 16 bit word.
+       FEDCBA9876543210
+       iiiiixxxxxxyyyyy
+   where i is instruction code
+         x is x argument, high bit is sign
+         y is y argument, high bit is sign
+*/
+void draw(int d7, L9BYTE **a5) {
+	int xy, x, y, x1, y1;
+
+	/* getxy2 */
+	xy = (d7 << 8) + (*(*a5)++);
+	x = (xy & 0x3e0) >> 5;
+	if (xy & 0x400)
+		x = (x | 0xe0) - 0x100;
+	y = (xy & 0xf) << 2;
+	if (xy & 0x10)
+		y = (y | 0xc0) - 0x100;
+
+	if (reflectflag & 2)
+		x = -x;
+	if (reflectflag & 1)
+		y = -y;
+
+	/* gintline */
+	x1 = drawx;
+	y1 = drawy;
+	newxy(x, y);
+
+#ifdef L9DEBUG
+	printf("gfx - draw (%d,%d) (%d,%d) colours %d,%d",
+	       x1, y1, drawx, drawy, gintcolour & 3, option & 3);
+#endif
+
+	os_drawline(scalex(x1), scaley(y1), scalex(drawx), scaley(drawy),
+	            gintcolour & 3, option & 3);
+}
+
+/* move instruction plus arguments are stored in a 16 bit word.
+       FEDCBA9876543210
+       iiiiixxxxxxyyyyy
+   where i is instruction code
+         x is x argument, high bit is sign
+         y is y argument, high bit is sign
+*/
+void _move(int d7, L9BYTE **a5) {
+	int xy, x, y;
+
+	/* getxy2 */
+	xy = (d7 << 8) + (*(*a5)++);
+	x = (xy & 0x3e0) >> 5;
+	if (xy & 0x400)
+		x = (x | 0xe0) - 0x100;
+	y = (xy & 0xf) << 2;
+	if (xy & 0x10)
+		y = (y | 0xc0) - 0x100;
+
+	if (reflectflag & 2)
+		x = -x;
+	if (reflectflag & 1)
+		y = -y;
+	newxy(x, y);
+}
+
+void icolour(int d7) {
+	gintcolour = d7 & 3;
+#ifdef L9DEBUG
+	printf("gfx - icolour 0x%.2x", gintcolour);
+#endif
+}
+
+void size(int d7) {
+	static int sizetable[7] = { 0x02, 0x04, 0x06, 0x07, 0x09, 0x0c, 0x10 };
+
+	d7 &= 7;
+	if (d7) {
+		int d0 = (scale * sizetable[d7 - 1]) >> 3;
+		scale = (d0 < 0x100) ? d0 : 0xff;
+	} else {
+		/* sizereset */
+		scale = 0x80;
+		if (gfx_mode == GFX_V2 || gfx_mode == GFX_V3A)
+			GfxScaleStackPos = 0;
+	}
+
+#ifdef L9DEBUG
+	printf("gfx - size 0x%.2x", scale);
+#endif
+}
+
+void gintfill(int d7) {
+	if ((d7 & 7) == 0)
+		/* filla */
+		d7 = gintcolour;
+	else
+		d7 &= 3;
+	/* fillb */
+
+#ifdef L9DEBUG
+	printf("gfx - gintfill (%d,%d) colours %d,%d", drawx, drawy, d7 & 3, option & 3);
+#endif
+
+	os_fill(scalex(drawx), scaley(drawy), d7 & 3, option & 3);
+}
+
+void gosub(int d7, L9BYTE **a5) {
+	int d0 = ((d7 & 7) << 8) + (*(*a5)++);
+#ifdef L9DEBUG
+	printf("gfx - gosub 0x%.2x", d0);
+#endif
+	gosubd0(d0, a5);
+}
+
+void reflect(int d7) {
+#ifdef L9DEBUG
+	printf("gfx - reflect 0x%.2x", d7);
+#endif
+
+	if (d7 & 4) {
+		d7 &= 3;
+		d7 ^= reflectflag;
+	}
+	/* reflect1 */
+	reflectflag = d7;
+}
+
+void notimp(void) {
+#ifdef L9DEBUG
+	printf("gfx - notimp");
+#endif
+}
+
+void gintchgcol(L9BYTE **a5) {
+	int d0 = *(*a5)++;
+
+#ifdef L9DEBUG
+	printf("gfx - gintchgcol %d %d", (d0 >> 3) & 3, d0 & 7);
+#endif
+
+	os_setcolour((d0 >> 3) & 3, d0 & 7);
+}
+
+void amove(L9BYTE **a5) {
+	drawx = 0x40 * (*(*a5)++);
+	drawy = 0x40 * (*(*a5)++);
+#ifdef L9DEBUG
+	printf("gfx - amove (%d,%d)", drawx, drawy);
+#endif
+}
+
+void opt(L9BYTE **a5) {
+	int d0 = *(*a5)++;
+#ifdef L9DEBUG
+	printf("gfx - opt 0x%.2x", d0);
+#endif
+
+	if (d0)
+		d0 = (d0 & 3) | 0x80;
+	/* optend */
+	option = d0;
+}
+
+void restorescale(void) {
+#ifdef L9DEBUG
+	printf("gfx - restorescale");
+#endif
+	if (GfxScaleStackPos > 0)
+		scale = GfxScaleStack[GfxScaleStackPos - 1];
+}
+
+L9BOOL rts(L9BYTE **a5) {
+	if (GfxA5StackPos > 0) {
+		GfxA5StackPos--;
+		*a5 = GfxA5Stack[GfxA5StackPos];
+		if (GfxScaleStackPos > 0) {
+			GfxScaleStackPos--;
+			scale = GfxScaleStack[GfxScaleStackPos];
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+L9BOOL getinstruction(L9BYTE **a5) {
+	int d7 = *(*a5)++;
+	if ((d7 & 0xc0) != 0xc0) {
+		switch ((d7 >> 6) & 3) {
+		case 0:
+			sdraw(d7);
+			break;
+		case 1:
+			smove(d7);
+			break;
+		case 2:
+			sgosub(d7, a5);
+			break;
+		}
+	} else if ((d7 & 0x38) != 0x38) {
+		switch ((d7 >> 3) & 7) {
+		case 0:
+			draw(d7, a5);
+			break;
+		case 1:
+			_move(d7, a5);
+			break;
+		case 2:
+			icolour(d7);
+			break;
+		case 3:
+			size(d7);
+			break;
+		case 4:
+			gintfill(d7);
+			break;
+		case 5:
+			gosub(d7, a5);
+			break;
+		case 6:
+			reflect(d7);
+			break;
+		}
+	} else {
+		switch (d7 & 7) {
+		case 0:
+			notimp();
+			break;
+		case 1:
+			gintchgcol(a5);
+			break;
+		case 2:
+			notimp();
+			break;
+		case 3:
+			amove(a5);
+			break;
+		case 4:
+			opt(a5);
+			break;
+		case 5:
+			restorescale();
+			break;
+		case 6:
+			notimp();
+			break;
+		case 7:
+			return rts(a5);
+		}
+	}
+	return TRUE;
+}
+
+void absrunsub(int d0) {
+	L9BYTE *a5;
+	if (!findsub(d0, &a5))
+		return;
+	while (getinstruction(&a5));
+}
+
+void show_picture(int pic) {
+	if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
+		FirstPicture = pic;
+		return;
+	}
+
+	if (picturedata) {
+		/* Some games don't call the screen() opcode before drawing
+		   graphics, so here graphics are enabled if necessary. */
+		if ((screencalled == 0) && (l9textmode == 0)) {
+			detect_gfx_mode();
+			l9textmode = 1;
+			os_graphics(1);
+		}
+
+#ifdef L9DEBUG
+		printf("picture %d", pic);
+#endif
+
+		os_cleargraphics();
+		/* gintinit */
+		gintcolour = 3;
+		option = 0x80;
+		reflectflag = 0;
+		drawx = 0x1400;
+		drawy = 0x1400;
+		/* sizereset */
+		scale = 0x80;
+
+		GfxA5StackPos = 0;
+		GfxScaleStackPos = 0;
+		absrunsub(0);
+		if (!findsub(pic, &gfxa5))
+			gfxa5 = NULL;
+	}
+}
+
+void picture(void) {
+	show_picture(*getvar());
+}
+
+void GetPictureSize(int *width, int *height) {
+	if (L9GameType == L9_V4) {
+		if (width != NULL)
+			*width = 0;
+		if (height != NULL)
+			*height = 0;
+	} else {
+		if (width != NULL)
+			*width = (gfx_mode != GFX_V3C) ? 160 : 320;
+		if (height != NULL)
+			*height = (gfx_mode == GFX_V2) ? 128 : 96;
+	}
+}
+
+L9BOOL RunGraphics(void) {
+	if (gfxa5) {
+		if (!getinstruction(&gfxa5))
+			gfxa5 = NULL;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+void initgetobj(void) {
+	int i;
+	numobjectfound = 0;
+	object = 0;
+	for (i = 0; i < 32; i++) gnoscratch[i] = 0;
+}
+
+void getnextobject(void) {
+	int d2, d3, d4;
+	L9UINT16 *hisearchposvar, *searchposvar;
+
+#ifdef L9DEBUG
+	printf("getnextobject");
+#endif
+
+	d2 = *getvar();
+	hisearchposvar = getvar();
+	searchposvar = getvar();
+	d3 = *hisearchposvar;
+	d4 = *searchposvar;
+
+	/* gnoabs */
+	do {
+		if ((d3 | d4) == 0) {
+			/* initgetobjsp */
+			gnosp = 128;
+			searchdepth = 0;
+			initgetobj();
+			break;
+		}
+
+		if (numobjectfound == 0) inithisearchpos = d3;
+
+		/* gnonext */
+		do {
+			if (d4 == list2ptr[++object]) {
+				/* gnomaybefound */
+				int d6 = list3ptr[object] & 0x1f;
+				if (d6 != d3) {
+					if (d6 == 0 || d3 == 0) continue;
+					if (d3 != 0x1f) {
+						gnoscratch[d6] = d6;
+						continue;
+					}
+					d3 = d6;
+				}
+				/* gnofound */
+				numobjectfound++;
+				gnostack[--gnosp] = object;
+				gnostack[--gnosp] = 0x1f;
+
+				*hisearchposvar = d3;
+				*searchposvar = d4;
+				*getvar() = object;
+				*getvar() = numobjectfound;
+				*getvar() = searchdepth;
+				return;
+			}
+		} while (object <= d2);
+
+		if (inithisearchpos == 0x1f) {
+			gnoscratch[d3] = 0;
+			d3 = 0;
+
+			/* gnoloop */
+			do {
+				if (gnoscratch[d3]) {
+					gnostack[--gnosp] = d4;
+					gnostack[--gnosp] = d3;
+				}
+			} while (++d3 < 0x1f);
+		}
+		/* gnonewlevel */
+		if (gnosp != 128) {
+			d3 = gnostack[gnosp++];
+			d4 = gnostack[gnosp++];
+		} else d3 = d4 = 0;
+
+		numobjectfound = 0;
+		if (d3 == 0x1f) searchdepth++;
+
+		initgetobj();
+	} while (d4);
+
+	/* gnofinish */
+	/* gnoreturnargs */
+	*hisearchposvar = 0;
+	*searchposvar = 0;
+	*getvar() = object = 0;
+	*getvar() = numobjectfound;
+	*getvar() = searchdepth;
+}
+
+void ifeqct(void) {
+	L9UINT16 d0 = *getvar();
+	L9UINT16 d1 = getcon();
+	L9BYTE *a0 = getaddr();
+	if (d0 == d1) codeptr = a0;
+#ifdef CODEFOLLOW
+	fprintf(f, " if Var[%d]=%d goto %d (%s)", cfvar - workspace.vartable, d1, (L9UINT32)(a0 - acodeptr), d0 == d1 ? "Yes" : "No");
+#endif
+}
+
+void ifnect(void) {
+	L9UINT16 d0 = *getvar();
+	L9UINT16 d1 = getcon();
+	L9BYTE *a0 = getaddr();
+	if (d0 != d1) codeptr = a0;
+#ifdef CODEFOLLOW
+	fprintf(f, " if Var[%d]!=%d goto %d (%s)", cfvar - workspace.vartable, d1, (L9UINT32)(a0 - acodeptr), d0 != d1 ? "Yes" : "No");
+#endif
+}
+
+void ifltct(void) {
+	L9UINT16 d0 = *getvar();
+	L9UINT16 d1 = getcon();
+	L9BYTE *a0 = getaddr();
+	if (d0 < d1) codeptr = a0;
+#ifdef CODEFOLLOW
+	fprintf(f, " if Var[%d]<%d goto %d (%s)", cfvar - workspace.vartable, d1, (L9UINT32)(a0 - acodeptr), d0 < d1 ? "Yes" : "No");
+#endif
+}
+
+void ifgtct(void) {
+	L9UINT16 d0 = *getvar();
+	L9UINT16 d1 = getcon();
+	L9BYTE *a0 = getaddr();
+	if (d0 > d1) codeptr = a0;
+#ifdef CODEFOLLOW
+	fprintf(f, " if Var[%d]>%d goto %d (%s)", cfvar - workspace.vartable, d1, (L9UINT32)(a0 - acodeptr), d0 > d1 ? "Yes" : "No");
+#endif
+}
+
+void printinput(void) {
+	L9BYTE *ptr = (L9BYTE *) obuff;
+	char c;
+	while ((c = *ptr++) != ' ') printchar(c);
+
+#ifdef L9DEBUG
+	printf("printinput");
+#endif
+}
+
+void listhandler(void) {
+	L9BYTE *a4, *MinAccess, *MaxAccess;
+	L9UINT16 val;
+	L9UINT16 *var;
+#ifdef CODEFOLLOW
+	int offset;
+#endif
+
+	if ((code & 0x1f) > 0xa) {
+		error("\rillegal list access %d\r", code & 0x1f);
+		Running = FALSE;
+		return;
+	}
+	a4 = L9Pointers[1 + (code & 0x1f)];
+
+	if (a4 >= workspace.listarea && a4 < workspace.listarea + LISTAREASIZE) {
+		MinAccess = workspace.listarea;
+		MaxAccess = workspace.listarea + LISTAREASIZE;
+	} else {
+		MinAccess = startdata;
+		MaxAccess = startdata + FileSize;
+	}
+
+	if (code >= 0xe0) {
+		/* listvv */
+#ifndef CODEFOLLOW
+		a4 += *getvar();
+		val = *getvar();
+#else
+		offset = *getvar();
+		a4 += offset;
+		var = getvar();
+		val = *var;
+		fprintf(f, " list %d [%d]=Var[%d] (=%d)", code & 0x1f, offset, var - workspace.vartable, val);
+#endif
+
+		if (a4 >= MinAccess && a4 < MaxAccess) *a4 = (L9BYTE) val;
+#ifdef L9DEBUG
+		else printf("Out of range list access");
+#endif
+	} else if (code >= 0xc0) {
+		/* listv1c */
+#ifndef CODEFOLLOW
+		a4 += *codeptr++;
+		var = getvar();
+#else
+		offset = *codeptr++;
+		a4 += offset;
+		var = getvar();
+		fprintf(f, " Var[%d]= list %d [%d])", var - workspace.vartable, code & 0x1f, offset);
+		if (a4 >= MinAccess && a4 < MaxAccess) fprintf(f, " (=%d)", *a4);
+#endif
+
+		if (a4 >= MinAccess && a4 < MaxAccess) *var = *a4;
+		else {
+			*var = 0;
+#ifdef L9DEBUG
+			printf("Out of range list access");
+#endif
+		}
+	} else if (code >= 0xa0) {
+		/* listv1v */
+#ifndef CODEFOLLOW
+		a4 += *getvar();
+		var = getvar();
+#else
+		offset = *getvar();
+		a4 += offset;
+		var = getvar();
+
+		fprintf(f, " Var[%d] =list %d [%d]", var - workspace.vartable, code & 0x1f, offset);
+		if (a4 >= MinAccess && a4 < MaxAccess) fprintf(f, " (=%d)", *a4);
+#endif
+
+		if (a4 >= MinAccess && a4 < MaxAccess) *var = *a4;
+		else {
+			*var = 0;
+#ifdef L9DEBUG
+			printf("Out of range list access");
+#endif
+		}
+	} else {
+#ifndef CODEFOLLOW
+		a4 += *codeptr++;
+		val = *getvar();
+#else
+		offset = *codeptr++;
+		a4 += offset;
+		var = getvar();
+		val = *var;
+		fprintf(f, " list %d [%d]=Var[%d] (=%d)", code & 0x1f, offset, var - workspace.vartable, val);
+#endif
+
+		if (a4 >= MinAccess && a4 < MaxAccess) *a4 = (L9BYTE) val;
+#ifdef L9DEBUG
+		else printf("Out of range list access");
+#endif
+	}
+}
+
+void executeinstruction(void) {
+#ifdef CODEFOLLOW
+	f = fopen(CODEFOLLOWFILE, "a");
+	fprintf(f, "%ld (s:%d) %x", (L9UINT32)(codeptr - acodeptr) - 1, workspace.stackptr, code);
+	if (!(code & 0x80))
+		fprintf(f, " = %s", codes[code & 0x1f]);
+#endif
+
+	if (code & 0x80)
+		listhandler();
+	else {
+		switch (code & 0x1f) {
+		case 0:
+			Goto();
+			break;
+		case 1:
+			intgosub();
+			break;
+		case 2:
+			intreturn();
+			break;
+		case 3:
+			printnumber();
+			break;
+		case 4:
+			messagev();
+			break;
+		case 5:
+			messagec();
+			break;
+		case 6:
+			function();
+			break;
+		case 7:
+			input();
+			break;
+		case 8:
+			varcon();
+			break;
+		case 9:
+			varvar();
+			break;
+		case 10:
+			_add();
+			break;
+		case 11:
+			_sub();
+			break;
+		case 12:
+			ilins(code & 0x1f);
+			break;
+		case 13:
+			ilins(code & 0x1f);
+			break;
+		case 14:
+			jump();
+			break;
+		case 15:
+			Exit();
+			break;
+		case 16:
+			ifeqvt();
+			break;
+		case 17:
+			ifnevt();
+			break;
+		case 18:
+			ifltvt();
+			break;
+		case 19:
+			ifgtvt();
+			break;
+		case 20:
+			_screen();
+			break;
+		case 21:
+			cleartg();
+			break;
+		case 22:
+			picture();
+			break;
+		case 23:
+			getnextobject();
+			break;
+		case 24:
+			ifeqct();
+			break;
+		case 25:
+			ifnect();
+			break;
+		case 26:
+			ifltct();
+			break;
+		case 27:
+			ifgtct();
+			break;
+		case 28:
+			printinput();
+			break;
+		case 29:
+			ilins(code & 0x1f);
+			break;
+		case 30:
+			ilins(code & 0x1f);
+			break;
+		case 31:
+			ilins(code & 0x1f);
+			break;
+		}
+	}
+#ifdef CODEFOLLOW
+	fprintf(f, "\n");
+	f.close();
+#endif
+}
+
+L9BOOL LoadGame2(const char *filename, char *picname) {
+#ifdef CODEFOLLOW
+	f = fopen(CODEFOLLOWFILE, "w");
+	fprintf(f, "Code follow file...\n");
+	f.close();
+#endif
+
+	/* may be already running a game, maybe in input routine */
+	Running = FALSE;
+	ibuffptr = NULL;
+
+	/* intstart */
+	if (!intinitialise(filename, picname)) return FALSE;
+	/*  if (!checksumgamedata()) return FALSE; */
+
+	codeptr = acodeptr;
+	if (constseed > 0)
+		randomseed = constseed;
+	else
+		randomseed = (L9UINT16)g_system->getMillis();
+	strcpy(LastGame, filename);
+	return Running = TRUE;
+}
+
+L9BOOL LoadGame(const char *filename, char *picname) {
+	L9BOOL ret = LoadGame2(filename, picname);
+	showtitle = 1;
+	clearworkspace();
+	workspace.stackptr = 0;
+	/* need to clear listarea as well */
+	memset((L9BYTE *) workspace.listarea, 0, LISTAREASIZE);
+	return ret;
+}
+
+/* can be called from input to cause fall through for exit */
+void StopGame(void) {
+	Running = FALSE;
+}
+
+L9BOOL RunGame(void) {
+	code = *codeptr++;
+	/*  printf("%d",code); */
+	executeinstruction();
+	return Running;
+}
+
+void RestoreGame(char *filename) {
+	int Bytes;
+	GameState temp;
+	Common::File f;
+
+	if (f.open(filename)) {
+		// TODO: This is horribly unportable
+		Bytes = f.read(&temp, sizeof(GameState));
+		if (Bytes == V1FILESIZE) {
+			printstring("\rGame restored.\r");
+			/* only copy in workspace */
+			memset(workspace.listarea, 0, LISTAREASIZE);
+			memmove(workspace.vartable, &temp, V1FILESIZE);
+		} else if (CheckFile(&temp)) {
+			printstring("\rGame restored.\r");
+			/* full restore */
+			memmove(&workspace, &temp, sizeof(GameState));
+			codeptr = acodeptr + workspace.codeptr;
+		} else
+			printstring("\rSorry, unrecognised format. Unable to restore\r");
+	} else
+		printstring("\rUnable to restore game.\r");
+}
+
+} // End of namespace Level9
+} // End of namespace Glk
diff --git a/engines/glk/level9/level9_main.h b/engines/glk/level9/level9_main.h
new file mode 100644
index 0000000..ab30242
--- /dev/null
+++ b/engines/glk/level9/level9_main.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_LEVEL9_LEVEL9_MAIN
+#define GLK_LEVEL9_LEVEL9_MAIN
+
+#include "common/scummsys.h"
+#include "common/endian.h"
+#include "common/stream.h"
+
+namespace Glk {
+namespace Level9 {
+
+typedef byte L9BYTE;
+typedef uint16 L9UINT16;
+typedef uint32 L9UINT32;
+typedef bool L9BOOL;
+
+#define FALSE false
+#define TRUE true
+
+#define LISTAREASIZE 0x800
+#define STACKSIZE 1024
+#define V1FILESIZE 0x600
+
+#ifndef MAX_PATH
+#define MAX_PATH 256
+#endif
+
+struct GameState {
+	L9UINT32 Id;
+	L9UINT16 codeptr, stackptr, listsize, stacksize, filenamesize, checksum;
+	L9UINT16 vartable[256];
+	L9BYTE listarea[LISTAREASIZE];
+	L9UINT16 stack[STACKSIZE];
+	char filename[MAX_PATH];
+};
+
+enum BitmapType {
+	NO_BITMAPS,
+	AMIGA_BITMAPS,
+	PC1_BITMAPS,
+	PC2_BITMAPS,
+	C64_BITMAPS,
+	BBC_BITMAPS,
+	CPC_BITMAPS,
+	MAC_BITMAPS,
+	ST1_BITMAPS,
+	ST2_BITMAPS
+};
+
+struct Colour {
+	L9BYTE red, green, blue;
+};
+
+struct Bitmap {
+	L9UINT16 width, height;
+	L9BYTE *bitmap;
+	Colour palette[32];
+	L9UINT16 npalette;
+};
+
+#define MAX_BITMAP_WIDTH 512
+#define MAX_BITMAP_HEIGHT 218
+
+#define L9WORD(x) READ_LE_UINT16(x)
+#define L9SETWORD(x,val) WRITE_LE_UINT16(x, val)
+#define L9SETDWORD(x,val) WRITE_LE_UINT32(x, val)
+
+/* routines provided by os dependent code */
+void os_printchar(char c);
+L9BOOL os_input(char *ibuff, int size);
+char os_readchar(int millis);
+L9BOOL os_stoplist(void);
+void os_flush(void);
+L9BOOL os_save_file(L9BYTE *Ptr, int Bytes);
+L9BOOL os_load_file(L9BYTE *Ptr, int *Bytes, int Max);
+L9BOOL os_get_game_file(char *NewName, int Size);
+void os_set_filenumber(char *NewName, int Size, int n);
+void os_graphics(int mode);
+void os_cleargraphics(void);
+void os_setcolour(int colour, int index);
+void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2);
+void os_fill(int x, int y, int colour1, int colour2);
+void os_show_bitmap(int pic, int x, int y);
+Common::SeekableReadStream *os_open_script_file(void);
+
+/* routines provided by level9 interpreter */
+L9BOOL LoadGame(const char *filename, char *picname);
+L9BOOL RunGame(void);
+void StopGame(void);
+void RestoreGame(char *filename);
+void FreeMemory(void);
+void GetPictureSize(int *width, int *height);
+L9BOOL RunGraphics(void);
+
+/* bitmap routines provided by level9 interpreter */
+BitmapType DetectBitmaps(char *dir);
+Bitmap *DecodeBitmap(char *dir, BitmapType type, int num, int x, int y);
+
+} // End of namespace Level9
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
new file mode 100644
index 0000000..ef5499c
--- /dev/null
+++ b/engines/glk/level9/os_glk.cpp
@@ -0,0 +1,6084 @@
+/* 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/level9/level9_main.h"
+#include "glk/level9/level9.h"
+#include "common/textconsole.h"
+
+namespace Glk {
+namespace Level9 {
+
+#define BYTE_MAX 0xff
+#define BITS_PER_CHAR 8
+
+/* File path delimiter, used to be #defined in v2 interpreter. */
+#if defined(_Windows) || defined(__MSDOS__) \
+    || defined (_WIN32) || defined (__WIN32__)
+static const char GLN_FILE_DELIM = '\\';
+#else
+static const char GLN_FILE_DELIM = '/';
+#endif
+
+/*---------------------------------------------------------------------*/
+/*  Module variables, miscellaneous other stuff                        */
+/*---------------------------------------------------------------------*/
+
+/* Glk Level 9 port version number. */
+static const glui32 GLN_PORT_VERSION = 0x00020201;
+
+/*
+ * We use a maximum of three Glk windows, one for status, one for pictures,
+ * and one for everything else.  The status and pictures windows may be
+ * NULL, depending on user selections and the capabilities of the Glk
+ * library.
+ */
+static winid_t gln_main_window = NULL,
+               gln_status_window = NULL,
+               gln_graphics_window = NULL;
+
+/*
+ * Transcript stream and input log.  These are NULL if there is no current
+ * collection of these strings.
+ */
+static strid_t gln_transcript_stream = NULL,
+               gln_inputlog_stream = NULL;
+
+/* Input read log stream, for reading back an input log. */
+static strid_t gln_readlog_stream = NULL;
+
+/* Note about whether graphics is possible, or not. */
+static int gln_graphics_possible = TRUE;
+
+/* Options that may be turned off by command line flags. */
+static int gln_graphics_enabled = TRUE,
+           gln_intercept_enabled = TRUE,
+           gln_prompt_enabled = TRUE,
+           gln_loopcheck_enabled = TRUE,
+           gln_abbreviations_enabled = TRUE,
+           gln_commands_enabled = TRUE;
+
+/* Reason for stopping the game, used to detect restarts and ^C exits. */
+enum StopReason {
+	STOP_NONE, STOP_FORCE, STOP_RESTART, STOP_EXIT
+};
+static StopReason gln_stop_reason = STOP_NONE;
+
+/* Level 9 standard input prompt string. */
+static const char *const GLN_INPUT_PROMPT = "> ";
+
+/*
+ * Typedef equivalents for interpreter types (uncapitalized to avoid appearing
+ * as macros), and some internal interpreter symbols symbols used for our own
+ * deviant purposes.
+ */
+typedef L9BOOL gln_bool;
+typedef L9BYTE gln_byte;
+typedef L9UINT16 gln_uint16;
+typedef L9UINT32 gln_uint32;
+
+extern void save(void);
+extern void restore(void);
+extern gln_bool Cheating;
+extern gln_byte *startdata;
+extern gln_uint32 FileSize;
+
+/* Forward declarations of event wait and other miscellaneous functions. */
+static void gln_event_wait(glui32 wait_type, event_t *event);
+static void gln_event_wait_2(glui32 wait_type_1,
+                             glui32 wait_type_2, event_t *event);
+
+static void gln_watchdog_tick(void);
+static void gln_standout_string(const char *message);
+
+static int gln_confirm(const char *prompt);
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port utility functions                                         */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gln_fatal()
+ *
+ * Fatal error handler.  The function returns, expecting the caller to
+ * abort() or otherwise handle the error.
+ */
+static void gln_fatal(const char *string) {
+	/*
+	 * If the failure happens too early for us to have a window, print
+	 * the message to stderr.
+	 */
+	if (!gln_main_window) {
+		warning("INTERNAL ERROR: %s", string);
+		return;
+	}
+
+	/* Cancel all possible pending window input events. */
+	g_vm->glk_cancel_line_event(gln_main_window, NULL);
+	g_vm->glk_cancel_char_event(gln_main_window);
+
+	/* Print a message indicating the error. */
+	g_vm->glk_set_window(gln_main_window);
+	g_vm->glk_set_style(style_Normal);
+	g_vm->glk_put_string("\n\nINTERNAL ERROR: ");
+	g_vm->glk_put_string(string);
+
+	g_vm->glk_put_string("\n\nPlease record the details of this error, try to"
+	                     " note down everything you did to cause it, and email"
+	                     " this information to simon_baldwin at yahoo.com.\n\n");
+}
+
+
+/*
+ * gln_malloc()
+ * gln_realloc()
+ *
+ * Non-failing malloc and realloc; call gln_fatal and exit if memory
+ * allocation fails.
+ */
+static void *
+gln_malloc(size_t size) {
+	void *pointer;
+
+	pointer = malloc(size);
+	if (!pointer) {
+		gln_fatal("GLK: Out of system memory");
+		g_vm->glk_exit();
+	}
+
+	return pointer;
+}
+
+static void *
+gln_realloc(void *ptr, size_t size) {
+	void *pointer;
+
+	pointer = realloc(ptr, size);
+	if (!pointer) {
+		gln_fatal("GLK: Out of system memory");
+		g_vm->glk_exit();
+	}
+
+	return pointer;
+}
+
+
+/*
+ * gln_strncasecmp()
+ * gln_strcasecmp()
+ *
+ * Strncasecmp and strcasecmp are not ANSI functions, so here are local
+ * definitions to do the same jobs.
+ *
+ * They're global here so that the core interpreter can use them; otherwise
+ * it tries to use the non-ANSI str[n]icmp() functions.
+ */
+int
+gln_strncasecmp(const char *s1, const char *s2, size_t n) {
+	size_t index;
+
+	for (index = 0; index < n; index++) {
+		int diff;
+
+		diff = g_vm->glk_char_to_lower(s1[index]) - g_vm->glk_char_to_lower(s2[index]);
+		if (diff < 0 || diff > 0)
+			return diff < 0 ? -1 : 1;
+	}
+
+	return 0;
+}
+
+int
+gln_strcasecmp(const char *s1, const char *s2) {
+	size_t s1len, s2len;
+	int result;
+
+	s1len = strlen(s1);
+	s2len = strlen(s2);
+
+	result = gln_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
+	if (result < 0 || result > 0)
+		return result;
+	else
+		return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port stub graphics functions                                   */
+/*---------------------------------------------------------------------*/
+
+/*
+ * If we're working with a very stripped down, old, or lazy Glk library
+ * that neither offers Glk graphics nor graphics stubs functions, here
+ * we define our own stubs, to avoid link-time errors.
+ */
+#ifndef GLK_MODULE_IMAGE
+static glui32
+g_vm->glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2) {
+	return FALSE;
+}
+static glui32
+g_vm->glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2,
+                            glui32 width, glui32 height) {
+	return FALSE;
+}
+static glui32
+g_vm->glk_image_get_info(glui32 image, glui32 *width, glui32 *height) {
+	return FALSE;
+}
+static void
+g_vm->glk_window_flow_break(winid_t win) {
+}
+static void
+g_vm->glk_window_erase_rect(winid_t win, glsi32 left, glsi32 top,
+                            glui32 width, glui32 height) {
+}
+static void
+g_vm->glk_window_fill_rect(winid_t win, glui32 color, glsi32 left, glsi32 top,
+                           glui32 width, glui32 height) {
+}
+static void
+g_vm->glk_window_set_background_color(winid_t win, glui32 color) {
+}
+#endif
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port CRC functions                                             */
+/*---------------------------------------------------------------------*/
+
+/* CRC table initialization polynomial. */
+static const gln_uint16 GLN_CRC_POLYNOMIAL = 0xa001;
+
+
+/*
+ * gln_get_buffer_crc()
+ *
+ * Return the CRC of the bytes buffer[0..length-1].
+ *
+ * This algorithm is selected to match the CRCs used in L9cut.  Because of
+ * the odd way CRCs are padded when L9cut calculates the CRC, this function
+ * allows a count of NUL padding bytes to be included within the return CRC.
+ */
+static gln_uint16 gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding) {
+	static int is_initialized = FALSE;
+	static gln_uint16 crc_table[BYTE_MAX + 1];
+
+	const char *buffer = (const char *) void_buffer;
+	gln_uint16 crc;
+	size_t index;
+
+	/* Build the static CRC lookup table on first call. */
+	if (!is_initialized) {
+		for (index = 0; index < BYTE_MAX + 1; index++) {
+			int bit;
+
+			crc = (gln_uint16) index;
+			for (bit = 0; bit < BITS_PER_CHAR; bit++)
+				crc = crc & 1 ? GLN_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
+
+			crc_table[index] = crc;
+		}
+
+		is_initialized = TRUE;
+
+		/* CRC lookup table self-test, after is_initialized set -- recursion. */
+		assert(gln_get_buffer_crc("123456789", 9, 0) == 0xbb3d);
+	}
+
+	/* Start with zero in the crc, then update using table entries. */
+	crc = 0;
+	for (index = 0; index < length; index++)
+		crc = crc_table[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
+
+	/* Add in any requested NUL padding bytes. */
+	for (index = 0; index < padding; index++)
+		crc = crc_table[crc & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
+
+	return crc;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port game identification data and identification functions     */
+/*---------------------------------------------------------------------*/
+
+/*
+ * The game's name, suitable for printing out on a status line, or other
+ * location where game information is relevant.  It's generated on demand,
+ * and may be re-requested when, say, the game changes, perhaps by moving to
+ * the next part of a multipart game.
+ */
+static const char *gln_gameid_game_name = NULL;
+
+
+/*
+ * The following game database is obtained from L9cut's l9data_d.h, and
+ * lets us find a game's name from its data CRC.  Entries marked "WANTED" in
+ * l9data_d.h, and file commentary, have been removed for brevity, and the
+ * file has been reformatted (patchlevel data removed).
+ *
+ * The version of l9data_d.h used is 050 (22 Oct 2002).
+ */
+typedef const struct {
+	const gln_uint16 length;    /* Datafile length in bytes */
+	const gln_byte checksum;    /* 8-bit checksum, last datafile byte */
+	const gln_uint16 crc;       /* 16-bit CRC, L9cut-internal */
+	const char *const name;     /* Game title and platform */
+} gln_game_table_t;
+typedef gln_game_table_t *gln_game_tableref_t;
+
+static gln_game_table_t GLN_GAME_TABLE[] = {
+	{0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
+
+	{0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
+	{0x630e, 0xbe, 0x3374, "Dungeon Adventure (MSX)"},
+
+	{0x5eb9, 0x30, 0xe99a, "Lords of Time (Amstrad CPC)"},
+	{0x5eb9, 0x5d, 0xc098, "Lords of Time (MSX)"},
+	{0x5eb9, 0x6e, 0xc689, "Lords of Time (Spectrum)"},
+
+	{0x5fab, 0x5c, 0xa309, "Snowball (Amstrad CPC)"},
+	{0x5fab, 0x2f, 0x8aa2, "Snowball (MSX)"},
+
+	{0x60c4, 0x28, 0x0154, "Return to Eden (Amstrad CPC/Commodore 64[v1])"},
+	{0x6064, 0x01, 0x5b3c, "Return to Eden (BBC[v1])"},
+	{0x6064, 0x95, 0x510c, "Return to Eden (Commodore 64[v2])"},
+	{0x6064, 0xda, 0xe610, "Return to Eden (Commodore 64[v2] *corrupt*)"},
+	{0x6064, 0xbd, 0x73ec, "Return to Eden (Atari *corrupt*)"},
+	{0x6047, 0x6c, 0x17ab, "Return to Eden (BBC[v2])"},
+	{0x5ca1, 0x33, 0x1c43, "Return to Eden (Spectrum[v1])"},
+	{0x5cb7, 0x64, 0x0790, "Return to Eden (Spectrum[v2])"},
+	{0x5cb7, 0xfe, 0x3533, "Return to Eden (MSX)"},
+
+	{0x34b3, 0x20, 0xccda, "Erik the Viking (BBC/Commodore 64)"},
+	{0x34b3, 0x53, 0x8f00, "Erik the Viking (Spectrum)"},
+	{0x34b3, 0xc7, 0x9058, "Erik the Viking (Amstrad CPC)"},
+
+	{
+		0x63be, 0xd6, 0xcf5d,
+		"Emerald Isle (Atari/Commodore 64/Amstrad CPC/Spectrum)"
+	},
+	{0x63be, 0x0a, 0x21ed, "Emerald Isle (MSX *corrupt*)"},
+	{0x378c, 0x8d, 0x3a21, "Emerald Isle (BBC)"},
+
+	{0x506c, 0xf0, 0xba72, "Red Moon (BBC/Commodore 64/Amstrad CPC/MSX)"},
+	{0x505d, 0x32, 0x2dcf, "Red Moon (Spectrum)"},
+
+	{0x772b, 0xcd, 0xa503, "Worm in Paradise (Spectrum 128)"},
+	{0x546c, 0xb7, 0x9420, "Worm in Paradise (Spectrum 48)"},
+	{0x6d84, 0xf9, 0x49ae, "Worm in Paradise (Commodore 64 *corrupt*)"},
+	{0x6d84, 0xc8, 0x943f, "Worm in Paradise (Commodore 64 *fixed*)"},
+	{0x6030, 0x47, 0x46ad, "Worm in Paradise (Amstrad CPC)"},
+	{0x5828, 0xbd, 0xe7cb, "Worm in Paradise (BBC)"},
+
+	{0x7410, 0x5e, 0x60be, "Price of Magik (Spectrum 128)"},
+	{0x5aa4, 0xc1, 0x10a0, "Price of Magik (Spectrum 48[v1])"},
+	{0x5aa4, 0xc1, 0xeda4, "Price of Magik (Spectrum 48[v2])"},
+	{0x6fc6, 0x14, 0xf9b6, "Price of Magik (Commodore 64)"},
+	{0x5aa4, 0xc1, 0xbbf4, "Price of Magik (Amstrad CPC)"},
+	{0x5671, 0xbc, 0xff35, "Price of Magik (BBC)"},
+
+	{0x76f4, 0x5e, 0x1fe5, "Colossal Adventure /JoD (Amiga/PC)"},
+	{0x76f4, 0x5a, 0xcf4b, "Colossal Adventure /JoD (ST)"},
+	{0x6e60, 0x83, 0x18e0, "Adventure Quest /JoD (Amiga/PC)"},
+	{0x6e5c, 0xf6, 0xd356, "Adventure Quest /JoD (ST)"},
+	{0x6f0c, 0x95, 0x1f64, "Dungeon Adventure /JoD (Amiga/PC/ST)"},
+
+	{0x6f70, 0x40, 0xbd91, "Colossal Adventure /JoD (MSX)"},
+
+	{0x6f6e, 0x78, 0x28cd, "Colossal Adventure /JoD (Spectrum 128)"},
+	{0x6970, 0xd6, 0xa820, "Adventure Quest /JoD (Spectrum 128)"},
+	{0x6de8, 0x4c, 0xd795, "Dungeon Adventure /JoD (Spectrum 128)"},
+
+	{
+		0x6f4d, 0xcb, 0xe8f2,
+		"Colossal Adventure /JoD (Amstrad CPC128[v1]/Spectrum +3)"
+	},
+	{0x6f6a, 0xa5, 0x8dd2, "Colossal Adventure /JoD (Amstrad CPC128[v2])"},
+	{0x6968, 0x32, 0x0c01, "Adventure Quest /JoD (Amstrad CPC128/Spectrum +3)"},
+	{0x6dc0, 0x63, 0x5d95, "Dungeon Adventure /JoD (Amstrad CPC128/Spectrum +3)"},
+
+	{0x5e31, 0x7c, 0xaa54, "Colossal Adventure /JoD (Amstrad CPC64)"},
+	{0x5b50, 0x66, 0x1800, "Adventure Quest /JoD (Amstrad CPC64)"},
+	{0x58a6, 0x24, 0xb50f, "Dungeon Adventure /JoD (Amstrad CPC64)"},
+
+	{0x6c8e, 0xb6, 0x9be3, "Colossal Adventure /JoD (Commodore 64)"},
+	{0x63b6, 0x2e, 0xef38, "Adventure Quest /JoD (Commodore 64)"},
+	{0x6bd2, 0x65, 0xa41f, "Dungeon Adventure /JoD (Commodore 64)"},
+
+	{0x5b16, 0x3b, 0xe2aa, "Colossal Adventure /JoD (Atari)"},
+	{0x5b58, 0x50, 0x332e, "Adventure Quest /JoD (Atari)"},
+	{0x593a, 0x80, 0x7a34, "Dungeon Adventure /JoD (Atari)"},
+
+	{0x5a8e, 0xf2, 0x7cca, "Colossal Adventure /JoD (Spectrum 48)"},
+	{0x5ace, 0x11, 0xdc12, "Adventure Quest /JoD (Spectrum 48)"},
+	{0x58a3, 0x38, 0x8ce4, "Dungeon Adventure /JoD (Spectrum 48)"},
+
+	{0x7b31, 0x6e, 0x2e2b, "Snowball /SD (Amiga/ST)"},
+	{0x7d16, 0xe6, 0x5438, "Return to Eden /SD (Amiga/ST)"},
+	{0x7cd9, 0x0c, 0x4df1, "Worm in Paradise /SD (Amiga/ST)"},
+
+	{0x7b2f, 0x70, 0x6955, "Snowball /SD (Mac/PC/Spectrum 128)"},
+	{0x7b2f, 0x70, 0x6f6c, "Snowball /SD (Amstrad CPC/Spectrum +3)"},
+	{0x7d14, 0xe8, 0xfbab, "Return to Eden /SD (PC)"},
+	{0x7cff, 0xf8, 0x6044, "Return to Eden /SD (Amstrad CPC/Spectrum +3)"},
+	{0x7cf8, 0x24, 0x9c1c, "Return to Eden /SD (Mac)"},
+	{0x7c55, 0x18, 0xdaee, "Return to Eden /SD (Spectrum 128)"},
+	{
+		0x7cd7, 0x0e, 0x4feb,
+		"Worm in Paradise /SD (Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3)"
+	},
+
+	{0x7363, 0x65, 0xa0ab, "Snowball /SD (Commodore 64)"},
+	{0x772f, 0xca, 0x8602, "Return to Eden /SD (Commodore 64)"},
+	{0x788d, 0x72, 0x888a, "Worm in Paradise /SD (Commodore 64)"},
+
+	{0x6bf8, 0x3f, 0xc9f7, "Snowball /SD (Atari)"},
+	{0x60f7, 0x68, 0xc2bc, "Return to Eden /SD (Atari)"},
+	{0x6161, 0xf3, 0xe6d7, "Worm in Paradise /SD (Atari)"},
+
+	{0x67a3, 0x9d, 0x1d05, "Snowball /SD (Apple ][)"},
+	{0x639c, 0x8b, 0x06e2, "Return to Eden /SD (Apple ][)"},
+	{0x60dd, 0xf2, 0x5bb8, "Worm in Paradise /SD (Apple ][)"},
+
+	{0x6541, 0x02, 0x2e6c, "Snowball /SD (Spectrum 48)"},
+	{0x5f43, 0xca, 0x828c, "Return to Eden /SD (Spectrum 48)"},
+	{0x5ebb, 0xf1, 0x4dec, "Worm in Paradise /SD (Spectrum 48)"},
+
+	{0x8333, 0xb7, 0xe2ac, "Adrian Mole I, pt. 1 (Commodore 64)"},
+	{0x844d, 0x50, 0x5353, "Adrian Mole I, pt. 2 (Commodore 64)"},
+	{0x8251, 0x5f, 0x862a, "Adrian Mole I, pt. 3 (Commodore 64)"},
+	{0x7a78, 0x5e, 0x6ea3, "Adrian Mole I, pt. 4 (Commodore 64)"},
+
+	{0x7c6f, 0x0f, 0xba24, "Adrian Mole I, pt. 1 (Amstrad CPC)"},
+
+	{0x72fa, 0x8b, 0x6f12, "Adrian Mole I, pt. 1 (Spectrum)"},
+	{0x738e, 0x5b, 0x7e3d, "Adrian Mole I, pt. 2 (Spectrum)"},
+	{0x7375, 0xe5, 0x3f3e, "Adrian Mole I, pt. 3 (Spectrum)"},
+	{0x78d5, 0xe3, 0xcd7d, "Adrian Mole I, pt. 4 (Spectrum)"},
+
+	{0x3a31, 0xe5, 0x0bdb, "Adrian Mole I, pt. 1 (BBC)"},
+	{0x37f1, 0x77, 0xd231, "Adrian Mole I, pt. 2 (BBC)"},
+	{0x3900, 0x1c, 0x5d9a, "Adrian Mole I, pt. 3 (BBC)"},
+	{0x3910, 0xac, 0x07f9, "Adrian Mole I, pt. 4 (BBC)"},
+	{0x3ad6, 0xa7, 0x95d2, "Adrian Mole I, pt. 5 (BBC)"},
+	{0x38a5, 0x0f, 0xdefc, "Adrian Mole I, pt. 6 (BBC)"},
+	{0x361e, 0x7e, 0xfd9f, "Adrian Mole I, pt. 7 (BBC)"},
+	{0x3934, 0x75, 0xe141, "Adrian Mole I, pt. 8 (BBC)"},
+	{0x3511, 0xcc, 0xd829, "Adrian Mole I, pt. 9 (BBC)"},
+	{0x38dd, 0x31, 0x2534, "Adrian Mole I, pt. 10 (BBC)"},
+	{0x39c0, 0x44, 0x89df, "Adrian Mole I, pt. 11 (BBC)"},
+	{0x3a12, 0x8f, 0xc2bd, "Adrian Mole I, pt. 12 (BBC)"},
+
+	{0x7931, 0xb9, 0xc51b, "Adrian Mole II, pt. 1 (Commodore 64/Amstrad CPC)"},
+	{0x7cdf, 0xa5, 0x43e3, "Adrian Mole II, pt. 2 (Commodore 64/Amstrad CPC)"},
+	{0x7a0c, 0x97, 0x4bea, "Adrian Mole II, pt. 3 (Commodore 64/Amstrad CPC)"},
+	{0x7883, 0xe2, 0xee0e, "Adrian Mole II, pt. 4 (Commodore 64/Amstrad CPC)"},
+
+	{0x6841, 0x4a, 0x94e7, "Adrian Mole II, pt. 1 (Spectrum)"},
+	{0x6bc0, 0x62, 0xab3d, "Adrian Mole II, pt. 2 (Spectrum)"},
+	{0x692c, 0x21, 0x2015, "Adrian Mole II, pt. 3 (Spectrum)"},
+	{0x670a, 0x94, 0xa2a6, "Adrian Mole II, pt. 4 (Spectrum)"},
+
+	{0x593a, 0xaf, 0x30e9, "Adrian Mole II, pt. 1 (BBC)"},
+	{0x57e6, 0x8a, 0xc41a, "Adrian Mole II, pt. 2 (BBC)"},
+	{0x5819, 0xcd, 0x1ba0, "Adrian Mole II, pt. 3 (BBC)"},
+	{0x579b, 0xad, 0xa723, "Adrian Mole II, pt. 4 (BBC)"},
+
+	{0x765d, 0xcd, 0xfc02, "The Archers, pt. 1 (Commodore 64)"},
+	{0x6e58, 0x07, 0xbffc, "The Archers, pt. 2 (Commodore 64)"},
+	{0x7e98, 0x6a, 0x95e5, "The Archers, pt. 3 (Commodore 64)"},
+	{0x81e2, 0xd5, 0xb278, "The Archers, pt. 4 (Commodore 64)"},
+
+	{0x6ce5, 0x58, 0x46de, "The Archers, pt. 1 (Spectrum)"},
+	{0x68da, 0xc1, 0x3b8e, "The Archers, pt. 2 (Spectrum)"},
+	{0x6c67, 0x9a, 0x9a6a, "The Archers, pt. 3 (Spectrum)"},
+	{0x6d91, 0xb9, 0x12a7, "The Archers, pt. 4 (Spectrum)"},
+
+	{0x5834, 0x42, 0xcc9d, "The Archers, pt. 1 (BBC)"},
+	{0x56dd, 0x51, 0xe582, "The Archers, pt. 2 (BBC)"},
+	{0x5801, 0x53, 0xf2ef, "The Archers, pt. 3 (BBC)"},
+	{0x54a4, 0x01, 0xc0ab, "The Archers, pt. 4 (BBC)"},
+
+	{0x579e, 0x97, 0x9faa, "Lords of Time /T&M GD (BBC)"},
+	{0x5500, 0x50, 0xca75, "Red Moon /T&M GD (BBC)"},
+	{0x579a, 0x2a, 0x9373, "Price of Magik /T&M GD (BBC)"},
+
+	{0x4fd2, 0x9d, 0x799a, "Lancelot, pt. 1 GD (BBC)"},
+	{0x4dac, 0xa8, 0x86ed, "Lancelot, pt. 2 GD (BBC)"},
+	{0x4f96, 0x22, 0x30f8, "Lancelot, pt. 3 GD (BBC)"},
+
+	{0x55ce, 0xa1, 0xba12, "Scapeghost, pt. 1 GD (BBC)"},
+	{0x54a6, 0xa9, 0xc9f3, "Scapeghost, pt. 2 GD (BBC)"},
+	{0x51bc, 0xe3, 0x89c3, "Scapeghost, pt. 3 GD (BBC)"},
+
+	{0x46ec, 0x64, 0x2300, "Knight Orc, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x6140, 0x18, 0x4f66, "Knight Orc, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x640e, 0xc1, 0xfc69, "Knight Orc, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x5ff0, 0xf8, 0x3a13, "Gnome Ranger, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x6024, 0x01, 0xaaa9, "Gnome Ranger, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x6036, 0x3d, 0x6c6c, "Gnome Ranger, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x69fe, 0x56, 0xecfb, "Lords of Time /T&M GD (Amstrad CPC/Spectrum +3)"},
+	{0x6888, 0x8d, 0x7f6a, "Red Moon /T&M GD (Amstrad CPC/Spectrum +3)"},
+	{0x5a50, 0xa9, 0xa5fa, "Price of Magik /T&M GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x5c7a, 0x44, 0x460e, "Lancelot, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x53a2, 0x1e, 0x2fae, "Lancelot, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x5914, 0x22, 0x4a31, "Lancelot, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x5a38, 0xf7, 0x876e, "Ingrid's Back, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x531a, 0xed, 0xcf3f, "Ingrid's Back, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x57e4, 0x19, 0xb354, "Ingrid's Back, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x5cbc, 0xa5, 0x0dbe, "Scapeghost, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x5932, 0x4e, 0xb2f5, "Scapeghost, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x5860, 0x95, 0x3227, "Scapeghost, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x74e0, 0x92, 0x885e, "Knight Orc, pt. 1 GD (Spectrum 128)"},
+	{0x6dbc, 0x97, 0x6f55, "Knight Orc, pt. 2 GD (Spectrum 128)"},
+	{0x7402, 0x07, 0x385f, "Knight Orc, pt. 3 GD (Spectrum 128)"},
+
+	{0x52aa, 0xdf, 0x7b5b, "Gnome Ranger, pt. 1 GD (Spectrum 128)"},
+	{0x6ffa, 0xdb, 0xdde2, "Gnome Ranger, pt. 2 GD (Spectrum 128)"},
+	{0x723a, 0x69, 0x039b, "Gnome Ranger, pt. 3 GD (Spectrum 128)"},
+
+	{0x6f1e, 0xda, 0x2ce0, "Lords of Time /T&M GD (Spectrum 128)"},
+	{0x6da0, 0xb8, 0x3802, "Red Moon /T&M GD (Spectrum 128)"},
+	{0x6108, 0xdd, 0xefe7, "Price of Magik /T&M GD (Spectrum 128)"},
+
+	{0x768c, 0xe8, 0x8fc6, "Lancelot, pt. 1 GD (Spectrum 128)"},
+	{0x76b0, 0x1d, 0x0fcd, "Lancelot, pt. 2 GD (Spectrum 128)"},
+	{0x765e, 0x4f, 0x3b73, "Lancelot, pt. 3 GD (Spectrum 128)"},
+
+	{0x76a0, 0x3a, 0xb803, "Ingrid's Back, pt. 1 GD (Spectrum 128)"},
+	{0x7674, 0x0b, 0xe92f, "Ingrid's Back, pt. 2 GD (Spectrum 128)"},
+	{0x765e, 0xba, 0x086d, "Ingrid's Back, pt. 3 GD (Spectrum 128)"},
+
+	{0x762e, 0x82, 0x8848, "Scapeghost, pt. 1 GD (Spectrum 128)"},
+	{0x5bd6, 0x35, 0x79ef, "Scapeghost, pt. 2 GD (Spectrum 128)"},
+	{0x6fa8, 0xa4, 0x62c2, "Scapeghost, pt. 3 GD (Spectrum 128)"},
+
+	{0xbb93, 0x36, 0x6a05, "Knight Orc, pt. 1 (Amiga)"},
+	{0xbb6e, 0xad, 0x4d40, "Knight Orc, pt. 1 (ST)"},
+	{0xc58e, 0x4a, 0x4e9d, "Knight Orc, pt. 2 (Amiga/ST)"},
+	{0xcb9a, 0x0f, 0x0804, "Knight Orc, pt. 3 (Amiga/ST)"},
+
+	{0xbb6e, 0xa6, 0x9753, "Knight Orc, pt. 1 (PC)"},
+	{0xc58e, 0x43, 0xe9ce, "Knight Orc, pt. 2 (PC)"},
+	{0xcb9a, 0x08, 0x6c36, "Knight Orc, pt. 3 (PC)"},
+
+	{0x898a, 0x43, 0xfc8b, "Knight Orc, pt. 1 (Apple ][)"},
+	{0x8b9f, 0x61, 0x7288, "Knight Orc, pt. 2 (Apple ][)"},
+	{0x8af9, 0x61, 0x7542, "Knight Orc, pt. 3 (Apple ][)"},
+
+	{0x8970, 0x6b, 0x3c7b, "Knight Orc, pt. 1 (Commodore 64 Gfx)"},
+	{0x8b90, 0x4e, 0x098c, "Knight Orc, pt. 2 (Commodore 64 Gfx)"},
+	{0x8aea, 0x4e, 0xca54, "Knight Orc, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x86d0, 0xb7, 0xadbd, "Knight Orc, pt. 1 (Spectrum 48)"},
+	{0x8885, 0x22, 0xe293, "Knight Orc, pt. 2 (Spectrum 48)"},
+	{0x87e5, 0x0e, 0xdc33, "Knight Orc, pt. 3 (Spectrum 48)"},
+
+	{0xb1a9, 0x80, 0x5fb7, "Gnome Ranger, pt. 1 (Amiga/ST)"},
+	{0xab9d, 0x31, 0xbe6d, "Gnome Ranger, pt. 2 (Amiga/ST)"},
+	{0xae28, 0x87, 0xb6b6, "Gnome Ranger, pt. 3 (Amiga/ST)"},
+
+	{0xb0ec, 0xc2, 0x0053, "Gnome Ranger, pt. 1 (ST[v1])"},
+	{0xaf82, 0x83, 0x19f7, "Gnome Ranger, pt. 2 (ST[v1])"},
+
+	{0xb1aa, 0xad, 0xaf47, "Gnome Ranger, pt. 1 (PC)"},
+	{0xb19e, 0x92, 0x8f96, "Gnome Ranger, pt. 1 (ST[v2])"},
+	{0xab8b, 0xbf, 0x31e6, "Gnome Ranger, pt. 2 (PC/ST[v2])"},
+	{0xae16, 0x81, 0x8741, "Gnome Ranger, pt. 3 (PC/ST[v2])"},
+
+	{0xad41, 0xa8, 0x42c5, "Gnome Ranger, pt. 1 (Commodore 64 TO)"},
+	{0xa735, 0xf7, 0x2e08, "Gnome Ranger, pt. 2 (Commodore 64 TO)"},
+	{0xa9c0, 0x9e, 0x0d70, "Gnome Ranger, pt. 3 (Commodore 64 TO)"},
+
+	{0x908e, 0x0d, 0x58a7, "Gnome Ranger, pt. 1 (Commodore 64 Gfx)"},
+	{0x8f6f, 0x0a, 0x411a, "Gnome Ranger, pt. 2 (Commodore 64 Gfx)"},
+	{0x9060, 0xbb, 0xe75d, "Gnome Ranger, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x8aab, 0xc0, 0xde5f, "Gnome Ranger, pt. 1 (Spectrum 48)"},
+	{0x8ac8, 0x9a, 0xc89b, "Gnome Ranger, pt. 2 (Spectrum 48)"},
+	{0x8a93, 0x4f, 0x10cc, "Gnome Ranger, pt. 3 (Spectrum 48)"},
+
+	{0xb57c, 0x44, 0x7779, "Lords of Time /T&M (PC)"},
+	{0xa69e, 0x6c, 0xb268, "Red Moon /T&M (PC)"},
+	{0xbac7, 0x7f, 0xddb2, "Price of Magik /T&M (PC)"},
+
+	{0xb579, 0x89, 0x3e89, "Lords of Time /T&M (ST)"},
+	{0xa698, 0x41, 0xcaca, "Red Moon /T&M (ST)"},
+	{0xbac4, 0x80, 0xa750, "Price of Magik /T&M (ST)"},
+
+	{0xb576, 0x2a, 0x7239, "Lords of Time /T&M (Amiga)"},
+	{0xa692, 0xd1, 0x6a99, "Red Moon /T&M (Amiga)"},
+	{0xbaca, 0x3a, 0x221b, "Price of Magik /T&M (Amiga)"},
+
+	{0xb563, 0x6a, 0x0c5c, "Lords of Time /T&M (Mac)"},
+	{0xa67c, 0xb8, 0xff41, "Red Moon /T&M (Mac)"},
+	{0xbab2, 0x87, 0x09f5, "Price of Magik /T&M (Mac)"},
+
+	{0xb38c, 0x37, 0x9f8e, "Lords of Time /T&M (Commodore 64 TO)"},
+	{0xa4e2, 0xa6, 0x016d, "Red Moon /T&M (Commodore 64 TO)"},
+	{0xb451, 0xa8, 0x2682, "Price of Magik /T&M (Commodore 64 TO)"},
+
+	{0x9070, 0x43, 0x45d4, "Lords of Time /T&M (Commodore 64 Gfx)"},
+	{0x903f, 0x6b, 0x603e, "Red Moon /T&M (Commodore 64 Gfx)"},
+	{0x8f51, 0xb2, 0x6c9a, "Price of Magik /T&M (Commodore 64 Gfx)"},
+
+	{0x8950, 0xa1, 0xbb16, "Lords of Time /T&M (Spectrum 48)"},
+	{0x8813, 0x11, 0x22de, "Red Moon /T&M (Spectrum 48)"},
+	{0x8a60, 0x2a, 0x29ed, "Price of Magik /T&M (Spectrum 48)"},
+
+	{0xb260, 0xe5, 0xc5b2, "Lords of Time /T&M (PC/ST *USA*)"},
+	{0xa3a4, 0xdf, 0x6732, "Red Moon /T&M (PC/ST *USA*)"},
+	{0xb7a0, 0x7e, 0x2226, "Price of Magik /T&M (PC/ST *USA*)"},
+
+	{0xb257, 0xf8, 0xfbd5, "Lords of Time /T&M (Amiga *USA*)"},
+	{0xa398, 0x82, 0xd031, "Red Moon /T&M (Amiga *USA*)"},
+	{0xb797, 0x1f, 0x84a9, "Price of Magik /T&M (Amiga *USA*)"},
+
+	{0x8d78, 0x3a, 0xba6e, "Lords of Time /T&M (Commodore 64 Gfx *USA*)"},
+	{0x8d56, 0xd3, 0x146a, "Red Moon /T&M (Commodore 64 Gfx *USA*)"},
+	{0x8c46, 0xf0, 0xcaf6, "Price of Magik /T&M (Commodore 64 Gfx *USA*)"},
+
+	{0xc0cf, 0x4e, 0xb7fa, "Lancelot, pt. 1 (Amiga/PC/ST)"},
+	{0xd5e9, 0x6a, 0x4192, "Lancelot, pt. 2 (Amiga/PC/ST)"},
+	{0xbb8f, 0x1a, 0x7487, "Lancelot, pt. 3 (Amiga/PC/ST)"},
+
+	{0xc0bd, 0x57, 0x6ef1, "Lancelot, pt. 1 (Mac)"},
+	{0xd5d7, 0x99, 0x770b, "Lancelot, pt. 2 (Mac)"},
+	{0xbb7d, 0x17, 0xbc42, "Lancelot, pt. 3 (Mac)"},
+
+	{0xb4c9, 0x94, 0xd784, "Lancelot, pt. 1 (Commodore 64 TO)"},
+	{0xb729, 0x51, 0x8ee5, "Lancelot, pt. 2 (Commodore 64 TO)"},
+	{0xb702, 0xe4, 0x1809, "Lancelot, pt. 3 (Commodore 64 TO)"},
+
+	{0x8feb, 0xba, 0xa800, "Lancelot, pt. 1 (Commodore 64 Gfx)"},
+	{0x8f6b, 0xfa, 0x0f7e, "Lancelot, pt. 2 (Commodore 64 Gfx)"},
+	{0x8f71, 0x2f, 0x0ddc, "Lancelot, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x8ade, 0xf2, 0xfffb, "Lancelot, pt. 1 (Spectrum 48)"},
+	{0x8b0e, 0xfb, 0x0bab, "Lancelot, pt. 2 (Spectrum 48)"},
+	{0x8ab3, 0xc1, 0xcb62, "Lancelot, pt. 3 (Spectrum 48)"},
+
+	{0xbba4, 0x94, 0x0871, "Lancelot, pt. 1 (Amiga/PC *USA*)"},
+	{0xd0c0, 0x56, 0x8c48, "Lancelot, pt. 2 (Amiga/PC *USA*)"},
+	{0xb6ac, 0xc6, 0xaea0, "Lancelot, pt. 3 (Amiga/PC *USA*)"},
+
+	{0x8afc, 0x07, 0x8321, "Lancelot, pt. 1 (Commodore 64 Gfx *USA*)"},
+	{0x8aec, 0x13, 0x6791, "Lancelot, pt. 2 (Commodore 64 Gfx *USA*)"},
+	{0x8aba, 0x0d, 0x5602, "Lancelot, pt. 3 (Commodore 64 Gfx *USA*)"},
+
+	{0xd19b, 0xad, 0x306d, "Ingrid's Back, pt. 1 (PC)"},
+	{0xc5a5, 0xfe, 0x3c98, "Ingrid's Back, pt. 2 (PC)"},
+	{0xd7ae, 0x9e, 0x1878, "Ingrid's Back, pt. 3 (PC)"},
+
+	{0xd188, 0x13, 0xdc60, "Ingrid's Back, pt. 1 (Amiga)"},
+	{0xc594, 0x03, 0xea95, "Ingrid's Back, pt. 2 (Amiga)"},
+	{0xd79f, 0xb5, 0x1661, "Ingrid's Back, pt. 3 (Amiga)"},
+
+	{0xd183, 0x83, 0xef72, "Ingrid's Back, pt. 1 (ST)"},
+	{0xc58f, 0x65, 0xf337, "Ingrid's Back, pt. 2 (ST)"},
+	{0xd79a, 0x57, 0x49c5, "Ingrid's Back, pt. 3 (ST)"},
+
+	{0xb770, 0x03, 0x9a03, "Ingrid's Back, pt. 1 (Commodore 64 TO)"},
+	{0xb741, 0xb6, 0x2aa5, "Ingrid's Back, pt. 2 (Commodore 64 TO)"},
+	{0xb791, 0xa1, 0xd065, "Ingrid's Back, pt. 3 (Commodore 64 TO)"},
+
+	{0x9089, 0xce, 0xc5e2, "Ingrid's Back, pt. 1 (Commodore 64 Gfx)"},
+	{0x908d, 0x80, 0x30c7, "Ingrid's Back, pt. 2 (Commodore 64 Gfx)"},
+	{0x909e, 0x9f, 0xdecc, "Ingrid's Back, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x8ab7, 0x68, 0xee57, "Ingrid's Back, pt. 1 (Spectrum 48)"},
+	{0x8b1e, 0x84, 0x2538, "Ingrid's Back, pt. 2 (Spectrum 48)"},
+	{0x8b1c, 0xa8, 0x9262, "Ingrid's Back, pt. 3 (Spectrum 48)"},
+
+	{0xbeab, 0x2d, 0x94d9, "Scapeghost, pt. 1 (Amiga)"},
+	{0xc132, 0x14, 0x7adc, "Scapeghost, pt. 1 (Amiga *bak*)"},
+	{0xbe94, 0xcc, 0x04b8, "Scapeghost, pt. 1 (PC/ST)"},
+	{0x99bd, 0x65, 0x032e, "Scapeghost, pt. 2 (Amiga/PC/ST)"},
+	{0xbcb6, 0x7a, 0x7d4f, "Scapeghost, pt. 3 (Amiga/PC/ST)"},
+
+	{0x9058, 0xcf, 0x9748, "Scapeghost, pt. 1 (Commodore 64 Gfx)"},
+	{0x8f43, 0xc9, 0xeefd, "Scapeghost, pt. 2 (Commodore 64 Gfx)"},
+	{0x90ac, 0x68, 0xb4a8, "Scapeghost, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x8a21, 0xf4, 0xd9e4, "Scapeghost, pt. 1 (Spectrum 48)"},
+	{0x8a12, 0xe3, 0xc2ff, "Scapeghost, pt. 2 (Spectrum 48)"},
+	{0x8a16, 0xcc, 0x4f3b, "Scapeghost, pt. 3 (Spectrum 48)"},
+
+	{0x3ebb, 0x00, 0xf6dc, "Champion of the Raj (English) 1/2 GD (Amiga)"},
+	{0x0fd8, 0x00, 0xf250, "Champion of the Raj (English) 2/2 GD (Amiga)"},
+
+	{0x3e8f, 0x00, 0x5599, "Champion of the Raj (English) 1/2 GD (ST)"},
+
+	{0x3e4f, 0x00, 0xb202, "Champion of the Raj (English) 1/2 GD (PC)"},
+	{0x14a3, 0x00, 0xa288, "Champion of the Raj (English) 2/2 GD (PC)"},
+
+	{0x1929, 0x00, 0xd4b2, "Champion of the Raj (demo), 1/2 GD (ST)"},
+	{0x40e0, 0x02, 0x080d, "Champion of the Raj (demo), 2/2 GD (ST)"},
+
+	{0x4872, 0x00, 0x9515, "Champion of the Raj (German) 1/2 GD (Amiga)"},
+	{0x11f5, 0x00, 0xbf39, "Champion of the Raj (German) 2/2 GD (Amiga)"},
+
+	{0x4846, 0x00, 0xd9c1, "Champion of the Raj (German) 1/2 GD (ST)"},
+	{0x11f5, 0x00, 0x7aa4, "Champion of the Raj (German) 2/2 GD (ST)"},
+
+	{0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
+
+	{0x0000, 0x00, 0x0000, NULL}
+};
+
+
+/*
+ * The following patch database is obtained from L9cut's l9data_p.h, and
+ * allows CRCs from patched games to be translated into original CRCs for
+ * lookup in the game database above.  Some file commentary has been removed
+ * for brevity, and unused patch fields deleted.
+ *
+ * The version of l9data_p.h used is 012 (22 May 2001).
+ */
+typedef const struct {
+	const gln_uint16 length;        /* Datafile length in bytes */
+	const gln_byte orig_checksum;   /* 8-bit checksum, last datafile byte */
+	const gln_uint16 orig_crc;      /* 16-bit CRC, L9cut-internal */
+	const gln_byte patch_checksum;  /* 8-bit checksum, last datafile byte */
+	const gln_uint16 patch_crc;     /* 16-bit CRC, L9cut-internal */
+} gln_patch_table_t;
+typedef gln_patch_table_t *gln_patch_tableref_t;
+
+static gln_patch_table_t GLN_PATCH_TABLE[] = {
+	/* Price of Magik (Spectrum128) */
+	{0x7410, 0x5e, 0x60be, 0x70, 0x6cef},
+
+	/* Price of Magik (Commodore 64) */
+	{0x6fc6, 0x14, 0xf9b6, 0x26, 0x3326},
+
+	/* Price of Magik (Spectrum48) */
+	{0x5aa4, 0xc1, 0xeda4, 0xd3, 0xed35},
+	{0x5aa4, 0xc1, 0xeda4, 0xc1, 0x8a65},
+
+	/* Colossal Adventure /JoD (Amiga/PC) */
+	{0x76f4, 0x5e, 0x1fe5, 0xea, 0x1305},
+	{0x76f4, 0x5e, 0x1fe5, 0xb5, 0x901f},
+	{0x76f4, 0x5e, 0x1fe5, 0x5e, 0x6ea1},
+
+	/* Colossal Adventure /JoD (ST) */
+	{0x76f4, 0x5a, 0xcf4b, 0xe6, 0x012a},
+	{0x76f4, 0x5a, 0xcf4b, 0xb1, 0x40b1},
+
+	/* Adventure Quest /JoD (Amiga/PC) */
+	{0x6e60, 0x83, 0x18e0, 0x4c, 0xcfb0},
+	{0x6e60, 0x83, 0x18e0, 0xfa, 0x9b3b},
+	{0x6e60, 0x83, 0x18e0, 0x83, 0x303d},
+
+	/* Adventure Quest /JoD (ST) */
+	{0x6e5c, 0xf6, 0xd356, 0xbf, 0xede7},
+	{0x6e5c, 0xf6, 0xd356, 0x6d, 0x662d},
+
+	/* Dungeon Adventure /JoD (Amiga/PC/ST) */
+	{0x6f0c, 0x95, 0x1f64, 0x6d, 0x2443},
+	{0x6f0c, 0x95, 0x1f64, 0x0c, 0x6066},
+	{0x6f0c, 0x95, 0x1f64, 0x96, 0xdaca},
+	{0x6f0c, 0x95, 0x1f64, 0x95, 0x848d},
+
+	/* Colossal Adventure /JoD (Spectrum128) */
+	{0x6f6e, 0x78, 0x28cd, 0xf8, 0xda5f},
+	{0x6f6e, 0x78, 0x28cd, 0x77, 0x5b4e},
+
+	/* Adventure Quest /JoD (Spectrum128) */
+	{0x6970, 0xd6, 0xa820, 0x3b, 0x1870},
+	{0x6970, 0xd6, 0xa820, 0xd5, 0x13c4},
+
+	/* Dungeon Adventure /JoD (Spectrum128) */
+	{0x6de8, 0x4c, 0xd795, 0xa2, 0x3eea},
+	{0x6de8, 0x4c, 0xd795, 0x4b, 0xad30},
+
+	/* Colossal Adventure /JoD (Amstrad CPC) */
+	{0x6f4d, 0xcb, 0xe8f2, 0x4b, 0xb384},
+	{0x6f4d, 0xcb, 0xe8f2, 0xca, 0x96e7},
+
+	/* Adventure Quest /JoD (Amstrad CPC) */
+	{0x6968, 0x32, 0x0c01, 0x97, 0xdded},
+	{0x6968, 0x32, 0x0c01, 0x31, 0xe8c2},
+
+	/* Dungeon Adventure /JoD (Amstrad CPC) */
+	{0x6dc0, 0x63, 0x5d95, 0xb9, 0xc963},
+	{0x6dc0, 0x63, 0x5d95, 0x62, 0x79f7},
+
+	/* Colossal Adventure /JoD (Commodore 64) */
+	{0x6c8e, 0xb6, 0x9be3, 0x36, 0x6971},
+	{0x6c8e, 0xb6, 0x9be3, 0xb5, 0xeba0},
+
+	/* Adventure Quest /JoD (Commodore 64) */
+	{0x63b6, 0x2e, 0xef38, 0x93, 0x4e68},
+	{0x63b6, 0x2e, 0xef38, 0x2d, 0x54dc},
+
+	/* Dungeon Adventure /JoD (Commodore 64) */
+	{0x6bd2, 0x65, 0xa41f, 0xbb, 0x4260},
+	{0x6bd2, 0x65, 0xa41f, 0x64, 0xdf5a},
+
+	/* Colossal Adventure /JoD (Spectrum48) */
+	{0x5a8e, 0xf2, 0x7cca, 0x72, 0x8e58},
+	{0x5a8e, 0xf2, 0x7cca, 0xf1, 0x0c89},
+	{0x5a8e, 0xf2, 0x7cca, 0xf2, 0x2c96},
+
+	/* Adventure Quest /JoD (Spectrum48) */
+	{0x5ace, 0x11, 0xdc12, 0x76, 0x8663},
+	{0x5ace, 0x11, 0xdc12, 0x10, 0xa757},
+	{0x5ace, 0x11, 0xdc12, 0x11, 0xf118},
+
+	/* Dungeon Adventure /JoD (Spectrum48) */
+	{0x58a3, 0x38, 0x8ce4, 0x8e, 0xb61a},
+	{0x58a3, 0x38, 0x8ce4, 0x37, 0x34c0},
+	{0x58a3, 0x38, 0x8ce4, 0x38, 0xa1ee},
+
+	/* Snowball /SD (Amiga/ST) */
+	{0x7b31, 0x6e, 0x2e2b, 0xe5, 0x6017},
+
+	/* Return to Eden /SD (Amiga/ST) */
+	{0x7d16, 0xe6, 0x5438, 0x5d, 0xc770},
+
+	/* Worm in Paradise /SD (Amiga/ST) */
+	{0x7cd9, 0x0c, 0x4df1, 0x83, 0xe997},
+
+	/* Snowball /SD (PC/Spectrum128) */
+	{0x7b2f, 0x70, 0x6955, 0xe7, 0x0af4},
+	{0x7b2f, 0x70, 0x6955, 0x70, 0x1179},
+
+	/* Return to Eden /SD (PC) */
+	{0x7d14, 0xe8, 0xfbab, 0x5f, 0xeab9},
+	{0x7d14, 0xe8, 0xfbab, 0xe8, 0xe216},
+
+	/* Return to Eden /SD (Amstrad CPC) */
+	{0x7cff, 0xf8, 0x6044, 0x6f, 0xbb57},
+
+	/* Return to Eden /SD (Spectrum128) */
+	{0x7c55, 0x18, 0xdaee, 0x8f, 0x01fd},
+
+	/* Worm in Paradise /SD (Amstrad CPC/PC/Spectrum128) */
+	{0x7cd7, 0x0e, 0x4feb, 0x85, 0x4eae},
+	{0x7cd7, 0x0e, 0x4feb, 0x0e, 0xb02c},
+
+	/* Snowball /SD (Commodore 64) */
+	{0x7363, 0x65, 0xa0ab, 0xdc, 0xca6a},
+
+	/* Return to Eden /SD (Commodore 64) */
+	{0x772f, 0xca, 0x8602, 0x41, 0x9bd0},
+
+	/* Worm in Paradise /SD (Commodore 64) */
+	{0x788d, 0x72, 0x888a, 0xe9, 0x4cce},
+
+	/* Snowball /SD (Atari) */
+	{0x6bf8, 0x3f, 0xc9f7, 0x96, 0x1908},
+
+	/* Return to Eden /SD (Atari) */
+	{0x60f7, 0x68, 0xc2bc, 0xdf, 0xd3ae},
+
+	/* Worm in Paradise /SD (Atari) */
+	{0x6161, 0xf3, 0xe6d7, 0x6a, 0xe232},
+
+	/* Snowball /SD (Spectrum48) */
+	{0x6541, 0x02, 0x2e6c, 0x79, 0xb80c},
+	{0x6541, 0x02, 0x2e6c, 0x02, 0x028a},
+
+	/* Return to Eden /SD (Spectrum48) */
+	{0x5f43, 0xca, 0x828c, 0x41, 0x9f5e},
+	{0x5f43, 0xca, 0x828c, 0xca, 0x6e1b},
+
+	/* Worm in Paradise /SD (Spectrum48) */
+	{0x5ebb, 0xf1, 0x4dec, 0x68, 0x4909},
+	{0x5ebb, 0xf1, 0x4dec, 0xf1, 0xcc1a},
+
+	/* Knight Orc, pt. 1 (Amiga) */
+	{0xbb93, 0x36, 0x6a05, 0xad, 0xe52d},
+
+	/* Knight Orc, pt. 1 (ST) */
+	{0xbb6e, 0xad, 0x4d40, 0x24, 0x3bcd},
+
+	/* Knight Orc, pt. 2 (Amiga/ST) */
+	{0xc58e, 0x4a, 0x4e9d, 0xc1, 0xe2bf},
+
+	/* Knight Orc, pt. 3 (Amiga/ST) */
+	{0xcb9a, 0x0f, 0x0804, 0x86, 0x6487},
+
+	/* Knight Orc, pt. 1 (PC) */
+	{0xbb6e, 0xa6, 0x9753, 0x1d, 0x2e7f},
+	{0xbb6e, 0xa6, 0x9753, 0xa6, 0x001d},
+
+	/* Knight Orc, pt. 2 (PC) */
+	{0xc58e, 0x43, 0xe9ce, 0xba, 0x5e4c},
+	{0xc58e, 0x43, 0xe9ce, 0x43, 0xa8f0},
+
+	/* Knight Orc, pt. 3 (PC) */
+	{0xcb9a, 0x08, 0x6c36, 0x7f, 0xf0d4},
+	{0xcb9a, 0x08, 0x6c36, 0x08, 0x2d08},
+
+	/* Knight Orc, pt. 1 (Commodore 64 Gfx) */
+	{0x8970, 0x6b, 0x3c7b, 0xe2, 0xb6f3},
+
+	/* Knight Orc, pt. 1 (Spectrum48) */
+	{0x86d0, 0xb7, 0xadbd, 0x2e, 0x43e1},
+
+	/* Gnome Ranger, pt. 1 (Amiga/ST) */
+	{0xb1a9, 0x80, 0x5fb7, 0xf7, 0x5c6c},
+
+	/* Gnome Ranger, pt. 2 (Amiga/ST) */
+	{0xab9d, 0x31, 0xbe6d, 0xa8, 0xcb96},
+
+	/* Gnome Ranger, pt. 3 (Amiga/ST) */
+	{0xae28, 0x87, 0xb6b6, 0xfe, 0x760c},
+
+	/* Gnome Ranger, pt. 1 (PC) */
+	{0xb1aa, 0xad, 0xaf47, 0x24, 0x5cfd},
+	{0xb1aa, 0xad, 0xaf47, 0xad, 0xe0ed},
+
+	/* Gnome Ranger, pt. 1 (ST-var) */
+	{0xb19e, 0x92, 0x8f96, 0x09, 0x798c},
+
+	/* Gnome Ranger, pt. 2 (PC/ST-var) */
+	{0xab8b, 0xbf, 0x31e6, 0x36, 0x811c},
+	{0xab8b, 0xbf, 0x31e6, 0xbf, 0x8ff3},
+
+	/* Gnome Ranger, pt. 3 (PC/ST-var) */
+	{0xae16, 0x81, 0x8741, 0xf8, 0x47fb},
+	{0xae16, 0x81, 0x8741, 0x81, 0xc8eb},
+
+	/* Gnome Ranger, pt. 1 (Commodore 64 TO) */
+	{0xad41, 0xa8, 0x42c5, 0x1f, 0x7d1e},
+
+	/* Gnome Ranger, pt. 2 (Commodore 64 TO) */
+	{0xa735, 0xf7, 0x2e08, 0x6e, 0x780e},
+
+	/* Gnome Ranger, pt. 3 (Commodore 64 TO) */
+	{0xa9c0, 0x9e, 0x0d70, 0x15, 0x3e6b},
+
+	/* Gnome Ranger, pt. 1 (Commodore 64 Gfx) */
+	{0x908e, 0x0d, 0x58a7, 0x84, 0xab1d},
+
+	/* Gnome Ranger, pt. 2 (Commodore 64 Gfx) */
+	{0x8f6f, 0x0a, 0x411a, 0x81, 0x12bc},
+
+	/* Gnome Ranger, pt. 3 (Commodore 64 Gfx) */
+	{0x9060, 0xbb, 0xe75d, 0x32, 0x14e7},
+
+	/* Lords of Time /T&M (PC) */
+	{0xb57c, 0x44, 0x7779, 0xbb, 0x31a6},
+	{0xb57c, 0x44, 0x7779, 0x44, 0xea72},
+
+	/* Red Moon /T&M (PC) */
+	{0xa69e, 0x6c, 0xb268, 0xe3, 0x4cef},
+	{0xa69e, 0x6c, 0xb268, 0x6c, 0x3799},
+
+	/* Price of Magik /T&M (PC) */
+	{0xbac7, 0x7f, 0xddb2, 0xf6, 0x6ab3},
+	{0xbac7, 0x7f, 0xddb2, 0x7f, 0x905c},
+
+	/* Lords of Time /T&M (ST) */
+	{0xb579, 0x89, 0x3e89, 0x00, 0xa2b7},
+
+	/* Red Moon /T&M (ST) */
+	{0xa698, 0x41, 0xcaca, 0xb8, 0xeeac},
+
+	/* Price of Magik /T&M (ST) */
+	{0xbac4, 0x80, 0xa750, 0xf7, 0xe030},
+
+	/* Lords of Time /T&M (Amiga) */
+	{0xb576, 0x2a, 0x7239, 0xa1, 0x2ea6},
+
+	/* Red Moon /T&M (Amiga) */
+	{0xa692, 0xd1, 0x6a99, 0x48, 0x50ff},
+
+	/* Price of Magik /T&M (Amiga) */
+	{0xbaca, 0x3a, 0x221b, 0xb1, 0x55bb},
+
+	/* Lords of Time /T&M (Commodore 64 TO) */
+	{0xb38c, 0x37, 0x9f8e, 0xae, 0xc6b1},
+
+	/* Red Moon /T&M (Commodore 64 TO) */
+	{0xa4e2, 0xa6, 0x016d, 0x1d, 0x31ab},
+
+	/* Price of Magik /T&M (Commodore 64 TO) */
+	{0xb451, 0xa8, 0x2682, 0x1f, 0x5de2},
+
+	/* Lords of Time /T&M (Commodore 64 Gfx) */
+	{0x9070, 0x43, 0x45d4, 0xba, 0x02eb},
+
+	/* Red Moon /T&M (Commodore 64 Gfx) */
+	{0x903f, 0x6b, 0x603e, 0xe2, 0x9f59},
+
+	/* Price of Magik /T&M (Commodore 64 Gfx) */
+	{0x8f51, 0xb2, 0x6c9a, 0x29, 0xde3b},
+
+	/* Lords of Time /T&M (Spectrum48) */
+	{0x8950, 0xa1, 0xbb16, 0x18, 0x2828},
+	{0x8950, 0xa1, 0xbb16, 0xa1, 0x1ea2},
+
+	/* Red Moon /T&M (Spectrum48) */
+	{0x8813, 0x11, 0x22de, 0x88, 0x18b8},
+	{0x8813, 0x11, 0x22de, 0x11, 0xd0cd},
+
+	/* Price of Magik /T&M (Spectrum48) */
+	{0x8a60, 0x2a, 0x29ed, 0xa1, 0x5e4d},
+
+	/* Lancelot, pt. 1 (Amiga/PC/ST) */
+	{0xc0cf, 0x4e, 0xb7fa, 0xc5, 0x4400},
+
+	/* Lancelot, pt. 2 (Amiga/PC/ST) */
+	{0xd5e9, 0x6a, 0x4192, 0xe1, 0x3b1e},
+
+	/* Lancelot, pt. 3 (Amiga/PC/ST) */
+	{0xbb8f, 0x1a, 0x7487, 0x91, 0x877d},
+
+	/* Lancelot, pt. 1 (Commodore 64 TO) */
+	{0xb4c9, 0x94, 0xd784, 0x0b, 0x203e},
+
+	/* Lancelot, pt. 2 (Commodore 64 TO) */
+	{0xb729, 0x51, 0x8ee5, 0xc8, 0xf1c9},
+
+	/* Lancelot, pt. 3 (Commodore 64 TO) */
+	{0xb702, 0xe4, 0x1809, 0x5b, 0x25b2},
+
+	/* Lancelot, pt. 1 (Commodore 64 Gfx) */
+	{0x8feb, 0xba, 0xa800, 0x31, 0x5bfa},
+
+	/* Lancelot, pt. 2 (Commodore 64 Gfx) */
+	{0x8f6b, 0xfa, 0x0f7e, 0x71, 0x75f2},
+
+	/* Lancelot, pt. 3 (Commodore 64 Gfx) */
+	{0x8f71, 0x2f, 0x0ddc, 0xa6, 0x3e87},
+
+	/* Ingrid's Back, pt. 1 (PC) */
+	{0xd19b, 0xad, 0x306d, 0x24, 0x4504},
+	{0xd19b, 0xad, 0x306d, 0xad, 0x878e},
+
+	/* Ingrid's Back, pt. 2 (PC) */
+	{0xc5a5, 0xfe, 0x3c98, 0x75, 0x8950},
+	{0xc5a5, 0xfe, 0x3c98, 0xfe, 0x8b7b},
+
+	/* Ingrid's Back, pt. 3 (PC) */
+	{0xd7ae, 0x9e, 0x1878, 0x15, 0xadb0},
+	{0xd7ae, 0x9e, 0x1878, 0x9e, 0xaf9b},
+
+	/* Ingrid's Back, pt. 1 (Amiga) */
+	{0xd188, 0x13, 0xdc60, 0x8a, 0x755c},
+
+	/* Ingrid's Back, pt. 2 (Amiga) */
+	{0xc594, 0x03, 0xea95, 0x7a, 0xb5a8},
+
+	/* Ingrid's Back, pt. 3 (Amiga) */
+	{0xd79f, 0xb5, 0x1661, 0x2c, 0xbf5d},
+
+	/* Ingrid's Back, pt. 1 (ST) */
+	{0xd183, 0x83, 0xef72, 0xfa, 0xb04f},
+
+	/* Ingrid's Back, pt. 2 (ST) */
+	{0xc58f, 0x65, 0xf337, 0xdc, 0x900a},
+
+	/* Ingrid's Back, pt. 3 (ST) */
+	{0xd79a, 0x57, 0x49c5, 0xce, 0xe0f9},
+
+	/* Ingrid's Back, pt. 1 (Commodore 64 TO) */
+	{0xb770, 0x03, 0x9a03, 0x7a, 0xdc6a},
+
+	/* Ingrid's Back, pt. 2 (Commodore 64 TO) */
+	{0xb741, 0xb6, 0x2aa5, 0x2d, 0x5a6c},
+
+	/* Ingrid's Back, pt. 3 (Commodore 64 TO) */
+	{0xb791, 0xa1, 0xd065, 0x18, 0xaa0c},
+
+	/* Ingrid's Back, pt. 1 (Commodore 64 Gfx) */
+	{0x9089, 0xce, 0xc5e2, 0x44, 0xeff4},
+
+	/* Ingrid's Back, pt. 2 (Commodore 64 Gfx) */
+	{0x908d, 0x80, 0x30c7, 0xf6, 0x2a11},
+
+	/* Ingrid's Back, pt. 3 (Commodore 64 Gfx) */
+	{0x909e, 0x9f, 0xdecc, 0x15, 0xf4da},
+
+	{0x0000, 0x00, 0x0000, 0x00, 0x0000},
+};
+
+
+/*
+ * gln_gameid_lookup_game()
+ * gln_gameid_lookup_patch()
+ *
+ * Look up and return game table and patch table entries given a game's
+ * length, checksum, and CRC.  Returns the entry, or NULL if not found.
+ */
+static gln_game_tableref_t
+gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
+                       gln_uint16 crc, int ignore_crc) {
+	gln_game_tableref_t game;
+
+	for (game = GLN_GAME_TABLE; game->length; game++) {
+		if (game->length == length && game->checksum == checksum
+		        && (ignore_crc || game->crc == crc))
+			break;
+	}
+
+	return game->length ? game : NULL;
+}
+
+static gln_patch_tableref_t
+gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
+                        gln_uint16 crc) {
+	gln_patch_tableref_t patch;
+
+	for (patch = GLN_PATCH_TABLE; patch->length; patch++) {
+		if (patch->length == length && patch->patch_checksum == checksum
+		        && patch->patch_crc == crc)
+			break;
+	}
+
+	return patch->length ? patch : NULL;
+}
+
+
+/*
+ * gln_gameid_identify_game()
+ *
+ * Identify a game from its data length, checksum, and CRC.  Returns the
+ * entry of the game in the game table, or NULL if not found.
+ *
+ * This function uses startdata and FileSize from the core interpreter.
+ * These aren't advertised symbols, so be warned.
+ */
+static gln_game_tableref_t
+gln_gameid_identify_game(void) {
+	gln_uint16 length, crc;
+	gln_byte checksum;
+	int is_version2;
+	gln_game_tableref_t game;
+	gln_patch_tableref_t patch;
+
+	/* If the data file appears too short for a header, give up now. */
+	if (FileSize < 30)
+		return NULL;
+
+	/*
+	 * Find the version of the game, and the length of game data.  This logic
+	 * is taken from L9cut, with calcword() replaced by simple byte comparisons.
+	 * If the length exceeds the available data, fail.
+	 */
+	assert(startdata);
+	is_version2 = startdata[4] == 0x20 && startdata[5] == 0x00
+	              && startdata[10] == 0x00 && startdata[11] == 0x80
+	              && startdata[20] == startdata[22]
+	              && startdata[21] == startdata[23];
+
+	length = is_version2
+	         ? startdata[28] | startdata[29] << BITS_PER_CHAR
+	         : startdata[0] | startdata[1] << BITS_PER_CHAR;
+	if (length >= FileSize)
+		return NULL;
+
+	/* Calculate or retrieve the checksum, in a version specific way. */
+	if (is_version2) {
+		int index;
+
+		checksum = 0;
+		for (index = 0; index < length + 1; index++)
+			checksum += startdata[index];
+	} else
+		checksum = startdata[length];
+
+	/*
+	 * Generate a CRC for this data.  When L9cut calculates a CRC, it's using a
+	 * copy taken up to length + 1 and then padded with two NUL bytes, so we
+	 * mimic that here.
+	 */
+	crc = gln_get_buffer_crc(startdata, length + 1, 2);
+
+	/*
+	 * See if this is a patched file.  If it is, look up the game based on the
+	 * original CRC and checksum.  If not, use the current CRC and checksum.
+	 */
+	patch = gln_gameid_lookup_patch(length, checksum, crc);
+	game = gln_gameid_lookup_game(length,
+	                              patch ? patch->orig_checksum : checksum,
+	                              patch ? patch->orig_crc : crc,
+	                              FALSE);
+
+	/* If no game identified, retry without the CRC.  This is guesswork. */
+	if (!game)
+		game = gln_gameid_lookup_game(length, checksum, crc, TRUE);
+
+	return game;
+}
+
+
+/*
+ * gln_gameid_get_game_name()
+ *
+ * Return the name of the game, or NULL if not identifiable.
+ *
+ * This function uses startdata from the core interpreter.  This isn't an
+ * advertised symbol, so be warned.
+ */
+static const char *
+gln_gameid_get_game_name(void) {
+	/*
+	 * If no game name yet known, attempt to identify the game.  If it can't
+	 * be identified, set the cached game name to "" -- this special value
+	 * indicates that the game is an unknown one, but suppresses repeated
+	 * attempts to identify it on successive calls.
+	 */
+	if (!gln_gameid_game_name) {
+		gln_game_tableref_t game;
+
+		/*
+		 * If the interpreter hasn't yet loaded a game, startdata is NULL
+		 * (uninitialized, global).  In this case, we return NULL, allowing
+		 * for retries until a game is loaded.
+		 */
+		if (!startdata)
+			return NULL;
+
+		game = gln_gameid_identify_game();
+		gln_gameid_game_name = game ? game->name : "";
+	}
+
+	/* Return the game's name, or NULL if it was unidentifiable. */
+	assert(gln_gameid_game_name);
+	return strlen(gln_gameid_game_name) > 0 ? gln_gameid_game_name : NULL;
+}
+
+
+/*
+ * gln_gameid_game_name_reset()
+ *
+ * Clear the saved game name, forcing a new lookup when next queried.  This
+ * function should be called by actions that may cause the interpreter to
+ * change game file, for example os_set_filenumber().
+ */
+static void
+gln_gameid_game_name_reset(void) {
+	gln_gameid_game_name = NULL;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port bitmap picture functions                                  */
+/*---------------------------------------------------------------------*/
+
+/* R,G,B color triple definition. */
+typedef struct {
+	int red, green, blue;
+} gln_rgb_t;
+typedef gln_rgb_t *gln_rgbref_t;
+
+/*
+ * Maximum number of regions to consider in a single repaint pass.  A
+ * couple of hundred seems to strike the right balance between not too
+ * sluggardly picture updates, and responsiveness to input during graphics
+ * rendering, when combined with short timeouts.
+ */
+static const int GLN_REPAINT_LIMIT = 256;
+
+/*
+ * Graphics timeout; we like an update call after this period (ms).  In
+ * practice, this timeout may actually be shorter than the time taken
+ * to reach the limit on repaint regions, but because Glk guarantees that
+ * user interactions (in this case, line events) take precedence over
+ * timeouts, this should be okay; we'll still see a game that responds to
+ * input each time the background repaint function yields.
+ *
+ * Setting this value is tricky.  We'd like it to be the shortest possible
+ * consistent with getting other stuff done, say 10ms.  However, Xglk has
+ * a granularity of 50ms on checking for timeouts, as it uses a 1/20s
+ * timeout on X select.  This means that the shortest timeout we'll ever
+ * get from Xglk will be 50ms, so there's no point in setting this shorter
+ * than that.  With luck, other Glk libraries will be more efficient than
+ * this, and can give us higher timer resolution; we'll set 50ms here, and
+ * hope that no other Glk library is worse.
+ */
+static const glui32 GLN_GRAPHICS_TIMEOUT = 50;
+
+/*
+ * Count of timeouts to wait on.  Waiting after a repaint smooths the
+ * display where the frame is being resized, by helping to avoid graphics
+ * output while more resize events are received; around 1/2 second seems
+ * okay.
+ */
+static const int GLN_GRAPHICS_REPAINT_WAIT = 10;
+
+/* Pixel size multiplier for image size scaling. */
+static const int GLN_GRAPHICS_PIXEL = 1;
+
+/* Proportion of the display to use for graphics. */
+static const glui32 GLN_GRAPHICS_PROPORTION = 30;
+
+/*
+ * Special title picture number, requiring its own handling, and count of
+ * timeouts to wait on after fully rendering the title picture (~2 seconds).
+ */
+static const int GLN_GRAPHICS_TITLE_PICTURE = 0,
+                 GLN_GRAPHICS_TITLE_WAIT = 40;
+
+/*
+ * Border and shading control.  For cases where we can't detect the back-
+ * ground color of the main window, there's a default, white, background.
+ * Bordering is black, with a 1 pixel border, 2 pixel shading, and 8 steps
+ * of shading fade.
+ */
+static const glui32 GLN_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
+                    GLN_GRAPHICS_BORDER_COLOR = 0x00000000;
+static const int GLN_GRAPHICS_BORDER = 1,
+                 GLN_GRAPHICS_SHADING = 2,
+                 GLN_GRAPHICS_SHADE_STEPS = 8;
+
+/*
+ * Guaranteed unused pixel value.  This value is used to fill the on-screen
+ * buffer on new pictures or repaints, resulting in a full paint of all
+ * pixels since no off-screen, real picture, pixel will match it.
+ */
+static const int GLN_GRAPHICS_UNUSED_PIXEL = 0xff;
+
+/* Graphics file directory, and type of graphics found in it. */
+static char *gln_graphics_bitmap_directory = NULL;
+static BitmapType gln_graphics_bitmap_type = NO_BITMAPS;
+
+/* The current picture id being displayed. */
+enum { GLN_PALETTE_SIZE = 32 };
+static gln_byte *gln_graphics_bitmap = NULL;
+static gln_uint16 gln_graphics_width = 0,
+                  gln_graphics_height = 0;
+static Colour gln_graphics_palette[GLN_PALETTE_SIZE]; /* = { 0, ... }; */
+static int gln_graphics_picture = -1;
+
+/*
+ * Flags set on new picture, and on resize or arrange events, and a flag
+ * to indicate whether background repaint is stopped or active.
+ */
+static int gln_graphics_new_picture = FALSE,
+           gln_graphics_repaint = FALSE,
+           gln_graphics_active = FALSE;
+
+/*
+ * State to monitor the state of interpreter graphics.  The values of the
+ * enumerations match the modes supplied by os_graphics().
+ */
+enum GraphicsState {
+	GLN_GRAPHICS_OFF = 0,
+	GLN_GRAPHICS_LINE_MODE = 1,
+	GLN_GRAPHICS_BITMAP_MODE = 2
+};
+static GraphicsState gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
+
+
+/*
+ * Pointer to the two graphics buffers, one the off-screen representation
+ * of pixels, and the other tracking on-screen data.  These are temporary
+ * graphics malloc'ed memory, and should be free'd on exit.
+ */
+static gln_byte *gln_graphics_off_screen = NULL,
+                 *gln_graphics_on_screen = NULL;
+
+/*
+ * The number of colors used in the palette by the current picture.  Because
+ * of the way it's queried, we risk a race, with admittedly a very low
+ * probability, with the updater.  So, it's initialized instead to the
+ * largest possible value.  The real value in use is inserted on the first
+ * picture update timeout call for a new picture.
+ */
+static int gln_graphics_color_count = GLN_PALETTE_SIZE;
+
+
+/*
+ * gln_graphics_open()
+ *
+ * If it's not open, open the graphics window.  Returns TRUE if graphics
+ * was successfully started, or already on.
+ */
+static int gln_graphics_open(void) {
+	if (!gln_graphics_window) {
+		gln_graphics_window = g_vm->glk_window_open(gln_main_window,
+		                      winmethod_Above
+		                      | winmethod_Proportional,
+		                      GLN_GRAPHICS_PROPORTION,
+		                      wintype_Graphics, 0);
+	}
+
+	return gln_graphics_window != NULL;
+}
+
+
+/*
+ * gln_graphics_close()
+ *
+ * If open, close the graphics window and set back to NULL.
+ */
+static void gln_graphics_close(void) {
+	if (gln_graphics_window) {
+		g_vm->glk_window_close(gln_graphics_window, NULL);
+		gln_graphics_window = NULL;
+	}
+}
+
+
+/*
+ * gln_graphics_start()
+ *
+ * If graphics enabled, start any background picture update processing.
+ */
+static void gln_graphics_start(void) {
+	if (gln_graphics_enabled) {
+		/* If not running, start the updating "thread". */
+		if (!gln_graphics_active) {
+			g_vm->glk_request_timer_events(GLN_GRAPHICS_TIMEOUT);
+			gln_graphics_active = TRUE;
+		}
+	}
+}
+
+
+/*
+ * gln_graphics_stop()
+ *
+ * Stop any background picture update processing.
+ */
+static void gln_graphics_stop(void) {
+	/* If running, stop the updating "thread". */
+	if (gln_graphics_active) {
+		g_vm->glk_request_timer_events(0);
+		gln_graphics_active = FALSE;
+	}
+}
+
+
+/*
+ * gln_graphics_are_displayed()
+ *
+ * Return TRUE if graphics are currently being displayed, FALSE otherwise.
+ */
+static int gln_graphics_are_displayed(void) {
+	return gln_graphics_window != NULL;
+}
+
+
+/*
+ * gln_graphics_paint()
+ *
+ * Set up a complete repaint of the current picture in the graphics window.
+ * This function should be called on the appropriate Glk window resize and
+ * arrange events.
+ */
+static void gln_graphics_paint(void) {
+	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+		/* Set the repaint flag, and start graphics. */
+		gln_graphics_repaint = TRUE;
+		gln_graphics_start();
+	}
+}
+
+
+/*
+ * gln_graphics_restart()
+ *
+ * Restart graphics as if the current picture is a new picture.  This
+ * function should be called whenever graphics is re-enabled after being
+ * disabled.
+ */
+static void gln_graphics_restart(void) {
+	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+		/* Set the new picture flag, and start graphics. */
+		gln_graphics_new_picture = TRUE;
+		gln_graphics_start();
+	}
+}
+
+
+/*
+ * gln_graphics_count_colors()
+ *
+ * Analyze an image, and return an overall count of how many colors out of
+ * the palette are used.
+ */
+static int gln_graphics_count_colors(gln_byte bitmap[],
+                                     gln_uint16 width, gln_uint16 height) {
+	int x, y, count;
+	long usage[GLN_PALETTE_SIZE], index_row;
+	assert(bitmap);
+
+	/*
+	 * Traverse the image, counting each pixel usage.  For the y iterator,
+	 * maintain an index row as an optimization to avoid multiplications in
+	 * the loop.
+	 */
+	count = 0;
+	memset(usage, 0, sizeof(usage));
+	for (y = 0, index_row = 0; y < height; y++, index_row += width) {
+		for (x = 0; x < width; x++) {
+			long index;
+
+			/* Get the pixel index, and update the count for this color. */
+			index = index_row + x;
+			usage[bitmap[index]]++;
+
+			/* If color usage is now 1, note new color encountered. */
+			if (usage[bitmap[index]] == 1)
+				count++;
+		}
+	}
+
+	return count;
+}
+
+
+/*
+ * gln_graphics_split_color()
+ * gln_graphics_combine_color()
+ *
+ * General graphics helper functions, to convert between RGB and Glk glui32
+ * color representations.
+ */
+static void gln_graphics_split_color(glui32 color, gln_rgbref_t rgb_color) {
+	assert(rgb_color);
+
+	rgb_color->red   = (color >> 16) & 0xff;
+	rgb_color->green = (color >> 8) & 0xff;
+	rgb_color->blue  = color & 0xff;
+}
+
+static glui32 gln_graphics_combine_color(gln_rgbref_t rgb_color) {
+	glui32 color;
+	assert(rgb_color);
+
+	color = (rgb_color->red << 16) | (rgb_color->green << 8) | rgb_color->blue;
+	return color;
+}
+
+
+/*
+ * gln_graphics_clear_and_border()
+ *
+ * Clear the graphics window, and border and shade the area where the
+ * picture is going to be rendered.  This attempts a small raised effect
+ * for the picture, in keeping with modern trends.
+ */
+static void gln_graphics_clear_and_border(winid_t glk_window,
+        int x_offset, int y_offset,
+        int pixel_size, gln_uint16 width,
+        gln_uint16 height) {
+	uint background;
+	glui32 fade_color, shading_color;
+	gln_rgb_t rgb_background, rgb_border, rgb_fade;
+	int index;
+	assert(glk_window);
+
+	/*
+	 * Try to detect the background color of the main window, by getting the
+	 * background for Normal style (Glk offers no way to directly get a window's
+	 * background color).  If we can get it, we'll match the graphics window
+	 * background to it.  If we can't, we'll default the color to white.
+	 */
+	if (!g_vm->glk_style_measure(gln_main_window,
+	                             style_Normal, stylehint_BackColor, &background)) {
+		/*
+		 * Unable to get the main window background, so assume, and default
+		 * graphics to white.
+		 */
+		background = GLN_GRAPHICS_DEFAULT_BACKGROUND;
+	}
+
+	/*
+	 * Set the graphics window background to match the main window background,
+	 * as best as we can tell, and clear the window.
+	 */
+	g_vm->glk_window_set_background_color(glk_window, background);
+	g_vm->glk_window_clear(glk_window);
+#ifndef GARGLK
+	/*
+	 * For very small pictures, just border them, but don't try and
+	 * do any shading.  Failing this check is probably highly unlikely.
+	 */
+	if (width < 2 * GLN_GRAPHICS_SHADE_STEPS
+	        || height < 2 * GLN_GRAPHICS_SHADE_STEPS) {
+		/* Paint a rectangle bigger than the picture by border pixels. */
+		g_vm->glk_window_fill_rect(glk_window,
+		                           GLN_GRAPHICS_BORDER_COLOR,
+		                           x_offset - GLN_GRAPHICS_BORDER,
+		                           y_offset - GLN_GRAPHICS_BORDER,
+		                           width * pixel_size + GLN_GRAPHICS_BORDER * 2,
+		                           height * pixel_size + GLN_GRAPHICS_BORDER * 2);
+		return;
+	}
+#endif
+	/*
+	 * Paint a rectangle bigger than the picture by border pixels all round,
+	 * and with additional shading pixels right and below.  Some of these
+	 * shading pixels are later overwritten by the fading loop below.  The
+	 * picture will sit over this rectangle.
+	 */
+	g_vm->glk_window_fill_rect(glk_window,
+	                           GLN_GRAPHICS_BORDER_COLOR,
+	                           x_offset - GLN_GRAPHICS_BORDER,
+	                           y_offset - GLN_GRAPHICS_BORDER,
+	                           width * pixel_size + GLN_GRAPHICS_BORDER * 2
+	                           + GLN_GRAPHICS_SHADING,
+	                           height * pixel_size + GLN_GRAPHICS_BORDER * 2
+	                           + GLN_GRAPHICS_SHADING);
+
+	/*
+	 * Split the main window background color and the border color into
+	 * components.
+	 */
+	gln_graphics_split_color(background, &rgb_background);
+	gln_graphics_split_color(GLN_GRAPHICS_BORDER_COLOR, &rgb_border);
+
+	/*
+	 * Generate the incremental color to use in fade steps.  Here we're
+	 * assuming that the border is always darker than the main window
+	 * background (currently valid, as we're using black).
+	 */
+	rgb_fade.red = (rgb_background.red - rgb_border.red)
+	               / GLN_GRAPHICS_SHADE_STEPS;
+	rgb_fade.green = (rgb_background.green - rgb_border.green)
+	                 / GLN_GRAPHICS_SHADE_STEPS;
+	rgb_fade.blue = (rgb_background.blue - rgb_border.blue)
+	                / GLN_GRAPHICS_SHADE_STEPS;
+
+	/* Combine RGB fade into a single incremental Glk color. */
+	fade_color = gln_graphics_combine_color(&rgb_fade);
+
+	/* Fade in edge, from background to border, shading in stages. */
+	shading_color = background;
+	for (index = 0; index < GLN_GRAPHICS_SHADE_STEPS; index++) {
+		/* Shade the two border areas with this color. */
+		g_vm->glk_window_fill_rect(glk_window, shading_color,
+		                           x_offset + width * pixel_size
+		                           + GLN_GRAPHICS_BORDER,
+		                           y_offset + index - GLN_GRAPHICS_BORDER,
+		                           GLN_GRAPHICS_SHADING, 1);
+		g_vm->glk_window_fill_rect(glk_window, shading_color,
+		                           x_offset + index - GLN_GRAPHICS_BORDER,
+		                           y_offset + height * pixel_size
+		                           + GLN_GRAPHICS_BORDER,
+		                           1, GLN_GRAPHICS_SHADING);
+
+		/* Update the shading color for the fade next iteration. */
+		shading_color -= fade_color;
+	}
+}
+
+
+/*
+ * gln_graphics_convert_palette()
+ *
+ * Convert a Level 9 bitmap color palette to a Glk one.
+ */
+static void gln_graphics_convert_palette(Colour ln_palette[], glui32 glk_palette[]) {
+	int index;
+	assert(ln_palette && glk_palette);
+
+	for (index = 0; index < GLN_PALETTE_SIZE; index++) {
+		Colour colour;
+		gln_rgb_t gln_color;
+
+		/* Convert color from Level 9 to internal RGB, then to Glk color. */
+		colour = ln_palette[index];
+		gln_color.red   = colour.red;
+		gln_color.green = colour.green;
+		gln_color.blue  = colour.blue;
+		glk_palette[index] = gln_graphics_combine_color(&gln_color);
+	}
+}
+
+
+/*
+ * gln_graphics_position_picture()
+ *
+ * Given a picture width and height, return the x and y offsets to center
+ * this picture in the current graphics window.
+ */
+static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
+        gln_uint16 width, gln_uint16 height,
+        int *x_offset, int *y_offset) {
+	uint window_width, window_height;
+	assert(glk_window && x_offset && y_offset);
+
+	/* Measure the current graphics window dimensions. */
+	g_vm->glk_window_get_size(glk_window, &window_width, &window_height);
+
+	/*
+	 * Calculate and return an x and y offset to use on point plotting, so that
+	 * the image centers inside the graphical window.
+	 */
+	*x_offset = ((int) window_width - width * pixel_size) / 2;
+	*y_offset = ((int) window_height - height * pixel_size) / 2;
+}
+
+
+/*
+ * gln_graphics_is_vertex()
+ *
+ * Given a point, return TRUE if that point is the vertex of a fillable
+ * region.  This is a helper function for layering pictures.  When assign-
+ * ing layers, we want to weight the colors that have the most complex
+ * shapes, or the largest count of isolated areas, heavier than simpler
+ * areas.
+ *
+ * By painting the colors with the largest number of isolated areas or
+ * the most complex shapes first, we help to minimize the number of fill
+ * regions needed to render the complete picture.
+ */
+static int gln_graphics_is_vertex(gln_byte off_screen[],
+                                  gln_uint16 width, gln_uint16 height, int x, int y) {
+	gln_byte pixel;
+	int above, below, left, right;
+	long index_row;
+	assert(off_screen);
+
+	/* Use an index row to cut down on multiplications. */
+	index_row = y * width;
+
+	/* Find the color of the reference pixel. */
+	pixel = off_screen[index_row + x];
+	assert(pixel < GLN_PALETTE_SIZE);
+
+	/*
+	 * Detect differences between the reference pixel and its upper, lower, left
+	 * and right neighbors.  Mark as different if the neighbor doesn't exist,
+	 * that is, at the edge of the picture.
+	 */
+	above = (y == 0 || off_screen[index_row - width + x] != pixel);
+	below = (y == height - 1 || off_screen[index_row + width + x] != pixel);
+	left = (x == 0 || off_screen[index_row + x - 1] != pixel);
+	right = (x == width - 1 || off_screen[index_row + x + 1] != pixel);
+
+	/*
+	 * Return TRUE if this pixel lies at the vertex of a rectangular, fillable,
+	 * area.  That is, if two adjacent neighbors aren't the same color (or if
+	 * absent -- at the edge of the picture).
+	 */
+	return ((above || below) && (left || right));
+}
+
+
+/*
+ * gms_graphics_compare_layering_inverted()
+ * gln_graphics_assign_layers()
+ *
+ * Given two sets of image bitmaps, and a palette, this function will
+ * assign layers palette colors.
+ *
+ * Layers are assigned by first counting the number of vertices in the
+ * color plane, to get a measure of the complexity of shapes displayed in
+ * this color, and also the raw number of times each palette color is
+ * used.  This is then sorted, so that layers are assigned to colors, with
+ * the lowest layer being the color with the most complex shapes, and
+ * within this (or where the count of vertices is zero) the most used color.
+ *
+ * The function compares pixels in the two image bitmaps given, these
+ * being the off-screen and on-screen buffers, and generates counts only
+ * where these bitmaps differ.  This ensures that only pixels not yet
+ * painted are included in layering.
+ *
+ * As well as assigning layers, this function returns a set of layer usage
+ * flags, to help the rendering loop to terminate as early as possible.
+ *
+ * By painting lower layers first, the paint can take in larger areas if
+ * it's permitted to include not-yet-validated higher levels.  This helps
+ * minimize the amount of Glk areas fills needed to render a picture.
+ */
+struct gln_layering_t {
+	long complexity;  /* Count of vertices for this color. */
+	long usage;       /* Color usage count. */
+	int color;        /* Color index into palette. */
+};
+
+static int gln_graphics_compare_layering_inverted(const void *void_first,
+        const void *void_second) {
+	const gln_layering_t *first = (const gln_layering_t *)void_first;
+	const gln_layering_t *second = (const gln_layering_t *)void_second;
+
+	/*
+	 * Order by complexity first, then by usage, putting largest first.  Some
+	 * colors may have no vertices at all when doing animation frames, but
+	 * rendering optimization relies on the first layer that contains no areas
+	 * to fill halting the rendering loop.  So it's important here that we order
+	 * indexes so that colors that render complex shapes come first, non-empty,
+	 * but simpler shaped colors next, and finally all genuinely empty layers.
+	 */
+	return second->complexity > first->complexity ? 1 :
+	       first->complexity > second->complexity ? -1 :
+	       second->usage > first->usage ? 1 :
+	       first->usage > second->usage ? -1 : 0;
+}
+
+static void gln_graphics_assign_layers(gln_byte off_screen[], gln_byte on_screen[],
+                                       gln_uint16 width, gln_uint16 height,
+                                       int layers[], long layer_usage[]) {
+	int index, x, y;
+	long index_row;
+	gln_layering_t layering[GLN_PALETTE_SIZE];
+	assert(off_screen && on_screen && layers && layer_usage);
+
+	/* Clear initial complexity and usage counts, and set initial colors. */
+	for (index = 0; index < GLN_PALETTE_SIZE; index++) {
+		layering[index].complexity = 0;
+		layering[index].usage = 0;
+		layering[index].color = index;
+	}
+
+	/*
+	 * Traverse the image, counting vertices and pixel usage where the pixels
+	 * differ between the off-screen and on-screen buffers.  Optimize by
+	 * maintaining an index row to avoid multiplications.
+	 */
+	for (y = 0, index_row = 0; y < height; y++, index_row += width) {
+		for (x = 0; x < width; x++) {
+			long idx;
+
+			/*
+			 * Get the idx for this pixel, and update complexity and usage
+			 * if off-screen and on-screen pixels differ.
+			 */
+			idx = index_row + x;
+			if (on_screen[idx] != off_screen[idx]) {
+				if (gln_graphics_is_vertex(off_screen, width, height, x, y))
+					layering[off_screen[idx]].complexity++;
+
+				layering[off_screen[idx]].usage++;
+			}
+		}
+	}
+
+	/*
+	 * Sort counts to form color indexes.  The primary sort is on the shape
+	 * complexity, and within this, on color usage.
+	 */
+	qsort(layering, GLN_PALETTE_SIZE,
+	      sizeof(*layering), gln_graphics_compare_layering_inverted);
+
+	/*
+	 * Assign a layer to each palette color, and also return the layer usage
+	 * for each layer.
+	 */
+	for (index = 0; index < GLN_PALETTE_SIZE; index++) {
+		layers[layering[index].color] = index;
+		layer_usage[index] = layering[index].usage;
+	}
+}
+
+
+/*
+ * gln_graphics_paint_region()
+ *
+ * This is a partially optimized point plot.  Given a point in the graphics
+ * bitmap, it tries to extend the point to a color region, and fill a number
+ * of pixels in a single Glk rectangle fill.  The goal here is to reduce the
+ * number of Glk rectangle fills, which tend to be extremely inefficient
+ * operations for generalized point plotting.
+ *
+ * The extension works in image layers; each palette color is assigned* a
+ * layer, and we paint each layer individually, starting at the lowest.  So,
+ * the region is free to fill any invalidated pixel in a higher layer, and
+ * all pixels, invalidated or already validated, in the same layer.  In
+ * practice, it is good enough to look for either invalidated pixels or pixels
+ * in the same layer, and construct a region as large as possible from these,
+ * then on marking points as validated, mark only those in the same layer as
+ * the initial point.
+ *
+ * The optimization here is not the best possible, but is reasonable.  What
+ * we do is to try and stretch the region horizontally first, then vertically.
+ * In practice, we might find larger areas by stretching vertically and then
+ * horizontally, or by stretching both dimensions at the same time.  In
+ * mitigation, the number of colors in a picture is small (16), and the
+ * aspect ratio of pictures makes them generally wider than they are tall.
+ *
+ * Once we've found the region, we render it with a single Glk rectangle fill,
+ * and mark all the pixels in this region that match the layer of the initial
+ * given point as validated.
+ */
+static void gln_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
+                                      gln_byte off_screen[], gln_byte on_screen[],
+                                      int x, int y, int x_offset, int y_offset,
+                                      int pixel_size, gln_uint16 width, gln_uint16 height) {
+	gln_byte pixel;
+	int layer, x_min, x_max, y_min, y_max, x_index, y_index;
+	long index_row;
+	assert(glk_window && palette && layers && off_screen && on_screen);
+
+	/* Find the color and layer for the initial pixel. */
+	pixel = off_screen[y * width + x];
+	layer = layers[pixel];
+	assert(pixel < GLN_PALETTE_SIZE);
+
+	/*
+	 * Start by finding the extent to which we can pull the x coordinate and
+	 * still find either invalidated pixels, or pixels in this layer.
+	 *
+	 * Use an index row to remove multiplications from the loops.
+	 */
+	index_row = y * width;
+	for (x_min = x; x_min - 1 >= 0; x_min--) {
+		long index = index_row + x_min - 1;
+
+		if (on_screen[index] == off_screen[index]
+		        && layers[off_screen[index]] != layer)
+			break;
+	}
+	for (x_max = x; x_max + 1 < width; x_max++) {
+		long index = index_row + x_max + 1;
+
+		if (on_screen[index] == off_screen[index]
+		        && layers[off_screen[index]] != layer)
+			break;
+	}
+
+	/*
+	 * Now try to stretch the height of the region, by extending the y
+	 * coordinate as much as possible too.  Again, we're looking for pixels
+	 * that are invalidated or ones in the same layer.  We need to check
+	 * across the full width of the current region.
+	 *
+	 * As above, an index row removes multiplications from the loops.
+	 */
+	for (y_min = y, index_row = (y - 1) * width;
+	        y_min - 1 >= 0; y_min--, index_row -= width) {
+		for (x_index = x_min; x_index <= x_max; x_index++) {
+			long index = index_row + x_index;
+
+			if (on_screen[index] == off_screen[index]
+			        && layers[off_screen[index]] != layer)
+				goto break_y_min;
+		}
+	}
+break_y_min:
+
+	for (y_max = y, index_row = (y + 1) * width;
+	        y_max + 1 < height; y_max++, index_row += width) {
+		for (x_index = x_min; x_index <= x_max; x_index++) {
+			long index = index_row + x_index;
+
+			if (on_screen[index] == off_screen[index]
+			        && layers[off_screen[index]] != layer)
+				goto break_y_max;
+		}
+	}
+break_y_max:
+
+	/* Fill the region using Glk's rectangle fill. */
+	g_vm->glk_window_fill_rect(glk_window, palette[pixel],
+	                           x_min * pixel_size + x_offset,
+	                           y_min * pixel_size + y_offset,
+	                           (x_max - x_min + 1) * pixel_size,
+	                           (y_max - y_min + 1) * pixel_size);
+
+	/*
+	 * Validate each pixel in the reference layer that was rendered by the
+	 * rectangle fill.  We don't validate pixels that are not in this layer
+	 * (and are by definition in higher layers, as we've validated all lower
+	 * layers), since although we colored them, we did it for optimization
+	 * reasons, and they're not yet colored correctly.
+	 *
+	 * Maintain an index row as an optimization to avoid multiplication.
+	 */
+	index_row = y_min * width;
+	for (y_index = y_min; y_index <= y_max; y_index++) {
+		for (x_index = x_min; x_index <= x_max; x_index++) {
+			long index;
+
+			/*
+			 * Get the index for x_index,y_index.  If the layers match, update
+			 * the on-screen buffer.
+			 */
+			index = index_row + x_index;
+			if (layers[off_screen[index]] == layer) {
+				assert(off_screen[index] == pixel);
+				on_screen[index] = off_screen[index];
+			}
+		}
+
+		/* Update row index component on change of y. */
+		index_row += width;
+	}
+}
+
+static void gln_graphics_paint_everything(winid_t glk_window,
+        glui32 palette[],
+        gln_byte off_screen[],
+        int x_offset, int y_offset,
+        gln_uint16 width, gln_uint16 height) {
+	gln_byte        pixel;          /* Reference pixel color */
+	int     x, y;
+
+	for (y = 0; y < height; y++) {
+		for (x = 0; x < width; x ++) {
+			pixel = off_screen[ y * width + x ];
+			g_vm->glk_window_fill_rect(glk_window,
+			                           palette[ pixel ],
+			                           x * GLN_GRAPHICS_PIXEL + x_offset,
+			                           y * GLN_GRAPHICS_PIXEL + y_offset,
+			                           GLN_GRAPHICS_PIXEL, GLN_GRAPHICS_PIXEL);
+		}
+	}
+}
+
+/*
+ * gln_graphics_timeout()
+ *
+ * This is a background function, called on Glk timeouts.  Its job is to
+ * repaint some of the current graphics image.  On successive calls, it
+ * does a part of the repaint, then yields to other processing.  This is
+ * useful since the Glk primitive to plot points in graphical windows is
+ * extremely slow; this way, the repaint doesn't block game play.
+ *
+ * The function should be called on Glk timeout events.  When the repaint
+ * is complete, the function will turn off Glk timers.
+ *
+ * The function uses double-buffering to track how much of the graphics
+ * buffer has been rendered.  This helps to minimize the amount of point
+ * plots required, as only the differences between the two buffers need
+ * to be rendered.
+ */
+static void gln_graphics_timeout(void) {
+	static glui32 palette[GLN_PALETTE_SIZE];   /* Precomputed Glk palette */
+
+	static int deferred_repaint = FALSE;       /* Local delayed repaint flag */
+	static int ignore_counter;                 /* Count of calls ignored */
+
+	static int x_offset, y_offset;             /* Point plot offsets */
+	static int yield_counter;                  /* Yields in rendering */
+	static int saved_layer;                    /* Saved current layer */
+	static int saved_x, saved_y;               /* Saved x,y coord */
+
+	static int total_regions;                  /* Debug statistic */
+
+	gln_byte *on_screen;                       /* On-screen image buffer */
+	gln_byte *off_screen;                      /* Off-screen image buffer */
+	long picture_size;                         /* Picture size in pixels */
+
+	/* Ignore the call if the current graphics state is inactive. */
+	if (!gln_graphics_active)
+		return;
+	assert(gln_graphics_window);
+
+	/*
+	 * On detecting a repaint request, note the flag in a local static variable,
+	 * then set up a graphics delay to wait until, hopefully, the resize, if
+	 * that's what caused it, is complete, and return.  This makes resizing the
+	 * window a lot smoother, since it prevents unnecessary region paints where
+	 * we are receiving consecutive Glk arrange or redraw events.
+	 */
+	if (gln_graphics_repaint) {
+		deferred_repaint = TRUE;
+		gln_graphics_repaint = FALSE;
+		ignore_counter = GLN_GRAPHICS_REPAINT_WAIT - 1;
+		return;
+	}
+
+	/*
+	 * If asked to ignore a given number of calls, decrement the ignore counter
+	 * and return having done nothing more.  This lets us delay graphics
+	 * operations by a number of timeouts, providing partial protection from
+	 * resize event "storms".
+	 *
+	 * Note -- to wait for N timeouts, set the count of timeouts to be ignored
+	 * to N-1.
+	 */
+	assert(ignore_counter >= 0);
+	if (ignore_counter > 0) {
+		ignore_counter--;
+		return;
+	}
+
+	/* Calculate the picture size, and synchronize screen buffer pointers. */
+	picture_size = gln_graphics_width * gln_graphics_height;
+	off_screen = gln_graphics_off_screen;
+	on_screen = gln_graphics_on_screen;
+
+	/*
+	 * If we received a new picture, set up the local static variables for that
+	 * picture -- convert the color palette, and initialize the off_screen
+	 * buffer to be the base picture.
+	 */
+	if (gln_graphics_new_picture) {
+		/* Initialize the off_screen buffer to be a copy of the base picture. */
+		free(off_screen);
+		off_screen = (gln_byte *)gln_malloc(picture_size * sizeof(*off_screen));
+		memcpy(off_screen, gln_graphics_bitmap,
+		       picture_size * sizeof(*off_screen));
+
+		/* Note the buffer for freeing on cleanup. */
+		gln_graphics_off_screen = off_screen;
+
+		/*
+		 * Pre-convert all the picture palette colors into their corresponding
+		 * Glk colors.
+		 */
+		gln_graphics_convert_palette(gln_graphics_palette, palette);
+
+		/* Save the color count for possible queries later. */
+		gln_graphics_color_count =
+		    gln_graphics_count_colors(off_screen,
+		                              gln_graphics_width, gln_graphics_height);
+	}
+
+	/*
+	 * For a new picture, or a repaint of a prior one, calculate new values for
+	 * the x and y offsets used to draw image points, and set the on-screen
+	 * buffer to an unused pixel value, in effect invalidating all on-screen
+	 * data.  Also, reset the saved image scan coordinates so that we scan for
+	 * unpainted pixels from top left starting at layer zero, and clear the
+	 * graphics window.
+	 */
+	if (gln_graphics_new_picture || deferred_repaint) {
+		/*
+		 * Calculate the x and y offset to center the picture in the graphics
+		 * window.
+		 */
+		gln_graphics_position_picture(gln_graphics_window,
+		                              GLN_GRAPHICS_PIXEL,
+		                              gln_graphics_width, gln_graphics_height,
+		                              &x_offset, &y_offset);
+
+		/*
+		 * Reset all on-screen pixels to an unused value, guaranteed not to
+		 * match any in a real picture.  This forces all pixels to be repainted
+		 * on a buffer/on-screen comparison.
+		 */
+		free(on_screen);
+		on_screen = (gln_byte *)gln_malloc(picture_size * sizeof(*on_screen));
+		memset(on_screen, GLN_GRAPHICS_UNUSED_PIXEL,
+		       picture_size * sizeof(*on_screen));
+
+		/* Note the buffer for freeing on cleanup. */
+		gln_graphics_on_screen = on_screen;
+
+		/*
+		 * Assign new layers to the current image.  This sorts colors by usage
+		 * and puts the most used colors in the lower layers.  It also hands us
+		 * a count of pixels in each layer, useful for knowing when to stop
+		 * scanning for layers in the rendering loop.
+		 */
+#ifndef GARGLK
+		gln_graphics_assign_layers(off_screen, on_screen,
+		                           gln_graphics_width, gln_graphics_height,
+		                           layers, layer_usage);
+#endif
+
+		/* Clear the graphics window. */
+		gln_graphics_clear_and_border(gln_graphics_window,
+		                              x_offset, y_offset,
+		                              GLN_GRAPHICS_PIXEL,
+		                              gln_graphics_width, gln_graphics_height);
+
+		/* Start a fresh picture rendering pass. */
+		yield_counter = 0;
+		saved_layer = 0;
+		saved_x = 0;
+		saved_y = 0;
+		total_regions = 0;
+
+		/* Clear the new picture and deferred repaint flags. */
+		gln_graphics_new_picture = FALSE;
+		deferred_repaint = FALSE;
+	}
+
+#ifndef GARGLK
+	int layer;                                 /* Image layer iterator */
+	int x, y;                                  /* Image iterators */
+	int regions;                               /* Count of regions painted */
+	static int layers[GLN_PALETTE_SIZE];       /* Assigned image layers */
+	static long layer_usage[GLN_PALETTE_SIZE]; /* Image layer occupancies */
+
+	/*
+	 * Make a portion of an image pass, from lower to higher image layers,
+	 * scanning for invalidated pixels that are in the current image layer we
+	 * are painting.  Each invalidated pixel gives rise to a region paint,
+	 * which equates to one Glk rectangle fill.
+	 *
+	 * When the limit on regions is reached, save the current image pass layer
+	 * and coordinates, and yield control to the main game playing code by
+	 * returning.  On the next call, pick up where we left off.
+	 *
+	 * As an optimization, we can leave the loop on the first empty layer we
+	 * encounter.  Since layers are ordered by complexity and color usage, all
+	 * layers higher than the first unused one will also be empty, so we don't
+	 * need to scan them.
+	 */
+	regions = 0;
+	for (layer = saved_layer;
+	        layer < GLN_PALETTE_SIZE && layer_usage[layer] > 0; layer++) {
+		long index_row;
+
+		/*
+		 * As an optimization to avoid multiplications in the loop, maintain a
+		 * separate index row.
+		 */
+		index_row = saved_y * gln_graphics_width;
+		for (y = saved_y; y < gln_graphics_height; y++) {
+			for (x = saved_x; x < gln_graphics_width; x++) {
+				long index;
+
+				/* Get the index for this pixel. */
+				index = index_row + x;
+				assert(index < picture_size * sizeof(*off_screen));
+
+				/*
+				 * Ignore pixels not in the current layer, and pixels not
+				 * currently invalid (that is, ones whose on-screen represen-
+				 * tation matches the off-screen buffer).
+				 */
+				if (layers[off_screen[index]] == layer
+				        && on_screen[index] != off_screen[index]) {
+					/*
+					 * Rather than painting just one pixel, here we try to
+					 * paint the maximal region we can for the layer of the
+					 * given pixel.
+					 */
+					gln_graphics_paint_region(gln_graphics_window,
+					                          palette, layers,
+					                          off_screen, on_screen,
+					                          x, y, x_offset, y_offset,
+					                          GLN_GRAPHICS_PIXEL,
+					                          gln_graphics_width,
+					                          gln_graphics_height);
+
+					/*
+					 * Increment count of regions handled, and yield, by
+					 * returning, if the limit on paint regions is reached.
+					 * Before returning, save the current layer and scan
+					 * coordinates, so we can pick up here on the next call.
+					 */
+					regions++;
+					if (regions >= GLN_REPAINT_LIMIT) {
+						yield_counter++;
+						saved_layer = layer;
+						saved_x = x;
+						saved_y = y;
+						total_regions += regions;
+						return;
+					}
+				}
+			}
+
+			/* Reset the saved x coordinate on y increment. */
+			saved_x = 0;
+
+			/* Update the index row on change of y. */
+			index_row += gln_graphics_width;
+		}
+
+		/* Reset the saved y coordinate on layer change. */
+		saved_y = 0;
+	}
+
+	/*
+	 * If we reach this point, then we didn't get to the limit on regions
+	 * painted on this pass.  In that case, we've finished rendering the
+	 * image.
+	 */
+	assert(regions < GLN_REPAINT_LIMIT);
+	total_regions += regions;
+
+#else
+	gln_graphics_paint_everything
+	(gln_graphics_window,
+	 palette, off_screen,
+	 x_offset, y_offset,
+	 gln_graphics_width,
+	 gln_graphics_height);
+#endif
+
+	/* Stop graphics; there's no more to be done until something restarts us. */
+	gln_graphics_stop();
+}
+
+/*
+ * gln_graphics_locate_bitmaps()
+ *
+ * Given the name of the game file being run, try to set up the graphics
+ * directory and bitmap type for that game.  If none available, set the
+ * directory to NULL, and bitmap type to NO_BITMAPS.
+ */
+static void gln_graphics_locate_bitmaps(const char *gamefile) {
+	const char *basename;
+	char *dirname;
+	BitmapType bitmap_type;
+
+	/* Find the start of the last element of the filename passed in. */
+	basename = strrchr(gamefile, GLN_FILE_DELIM);
+	basename = basename ? basename + 1 : gamefile;
+
+	/* Take a copy of the directory part of the filename. */
+	dirname = (char *)gln_malloc(basename - gamefile + 1);
+	strncpy(dirname, gamefile, basename - gamefile);
+	dirname[basename - gamefile] = '\0';
+
+	/*
+	 * Use the core interpreter to search for suitable bitmaps.  If none found,
+	 * free allocated memory and return noting none available.
+	 */
+	bitmap_type = DetectBitmaps(dirname);
+	if (bitmap_type == NO_BITMAPS) {
+		free(dirname);
+		gln_graphics_bitmap_directory = NULL;
+		gln_graphics_bitmap_type = NO_BITMAPS;
+		return;
+	}
+
+	/* Record the bitmap details for later use. */
+	gln_graphics_bitmap_directory = dirname;
+	gln_graphics_bitmap_type = bitmap_type;
+}
+
+
+/*
+ * gln_graphics_handle_title_picture()
+ *
+ * Picture 0 is special, normally the title picture.  Unless we handle it
+ * specially, the next picture comes along and instantly overwrites it.
+ * Here, then, we try to delay until the picture has rendered, allowing the
+ * delay to be broken with a keypress.
+ */
+static void gln_graphics_handle_title_picture(void) {
+	event_t event;
+	int count;
+
+	gln_standout_string("\n[ Press any key to skip the title picture... ]\n\n");
+
+	/* Wait until a keypress or graphics rendering is complete. */
+	g_vm->glk_request_char_event(gln_main_window);
+	do {
+		gln_event_wait_2(evtype_CharInput, evtype_Timer, &event);
+
+		/*
+		 * If a character was pressed, return.  This will let the game
+		 * progress, probably into showing the next bitmap.
+		 */
+		if (event.type == evtype_CharInput) {
+			gln_watchdog_tick();
+			return;
+		}
+	} while (gln_graphics_active);
+
+	/*
+	 * Now wait another couple of seconds, or until a keypress.  We'll do this
+	 * in graphics timeout chunks, so that if graphics restarts while we're
+	 * delaying, and it requests timer events and overwrites ours, we wind up
+	 * with the identical timer event period to the one we're expecting anyway.
+	 */
+	g_vm->glk_request_timer_events(GLN_GRAPHICS_TIMEOUT);
+	for (count = 0; count < GLN_GRAPHICS_TITLE_WAIT; count++) {
+		gln_event_wait_2(evtype_CharInput, evtype_Timer, &event);
+
+		if (event.type == evtype_CharInput)
+			break;
+	}
+
+	/*
+	 * While we waited, a Glk arrange or redraw event could have triggered
+	 * graphics into repainting, and using timers.  To handle this, stop timers
+	 * only if graphics is inactive.  If active, graphics will stop timers
+	 * itself when it finishes rendering.  We can't stop timers here while
+	 * graphics is active; that will hang the graphics "thread".
+	 */
+	if (!gln_graphics_active)
+		g_vm->glk_request_timer_events(0);
+
+	/* Cancel possible pending character event, and continue on. */
+	g_vm->glk_cancel_char_event(gln_main_window);
+	gln_watchdog_tick();
+}
+
+
+/*
+ * os_show_bitmap()
+ *
+ * Called by the main interpreter when it wants us to display a picture.
+ *
+ * The function gets the picture bitmap, palette, and dimensions, and saves
+ * them, and the picture id, in module variables for the background rendering
+ * function.
+ */
+void os_show_bitmap(int picture, int x, int y) {
+	Bitmap *bitmap;
+	long picture_bytes;
+
+	/*
+	 * If interpreter graphics are disabled, the only way we can get into here
+	 * is using #picture.  It seems that the interpreter won't always deliver
+	 * correct bitmaps with #picture when in text mode, so it's simplest here
+	 * if we just ignore those calls.
+	 */
+	if (gln_graphics_interpreter_state != GLN_GRAPHICS_BITMAP_MODE)
+		return;
+
+	/* Ignore repeat calls for the currently displayed picture. */
+	if (picture == gln_graphics_picture)
+		return;
+
+	/*
+	 * Get the core interpreter's bitmap for the requested picture.  If this
+	 * returns NULL, the picture doesn't exist, so ignore the call silently.
+	 */
+	bitmap = DecodeBitmap(gln_graphics_bitmap_directory,
+	                      gln_graphics_bitmap_type, picture, x, y);
+	if (!bitmap)
+		return;
+
+	/*
+	 * Note the last thing passed to os_show_bitmap, to avoid possible repaints
+	 * of the current picture.
+	 */
+	gln_graphics_picture = picture;
+
+	/* Calculate the picture size in bytes. */
+	picture_bytes = bitmap->width * bitmap->height * sizeof(*bitmap->bitmap);
+
+	/*
+	 * Save the picture details for the update code.  Here we take a complete
+	 * local copy of the bitmap, dimensions, and palette.  The core interpreter
+	 * may return a palette with fewer colors than our maximum, so unused local
+	 * palette entries are set to zero.
+	 */
+	free(gln_graphics_bitmap);
+	gln_graphics_bitmap = (gln_byte *)gln_malloc(picture_bytes);
+	memcpy(gln_graphics_bitmap, bitmap->bitmap, picture_bytes);
+	gln_graphics_width = bitmap->width;
+	gln_graphics_height = bitmap->height;
+	memset(gln_graphics_palette, 0, sizeof(gln_graphics_palette));
+	memcpy(gln_graphics_palette, bitmap->palette,
+	       bitmap->npalette * sizeof(bitmap->palette[0]));
+
+	/*
+	 * If graphics are enabled, both at the Glk level and in the core
+	 * interpreter, ensure the window is displayed, set the appropriate flags,
+	 * and start graphics update.  If they're not enabled, the picture details
+	 * will simply stick around in module variables until they are required.
+	 */
+	if (gln_graphics_enabled
+	        && gln_graphics_interpreter_state == GLN_GRAPHICS_BITMAP_MODE) {
+		/*
+		 * Ensure graphics on, then set the new picture flag and start the
+		 * updating "thread".  If this is the title picture, start special
+		 * handling.
+		 */
+		if (gln_graphics_open()) {
+			gln_graphics_new_picture = TRUE;
+			gln_graphics_start();
+
+			if (picture == GLN_GRAPHICS_TITLE_PICTURE)
+				gln_graphics_handle_title_picture();
+		}
+	}
+}
+
+
+/*
+ * gln_graphics_picture_is_available()
+ *
+ * Return TRUE if the graphics module data is loaded with a usable picture,
+ * FALSE if there is no picture available to display.
+ */
+static int gln_graphics_picture_is_available(void) {
+	return gln_graphics_bitmap != NULL;
+}
+
+
+/*
+ * gln_graphics_get_picture_details()
+ *
+ * Return the width and height of the currently loaded picture.  The function
+ * returns FALSE if no picture is loaded, otherwise TRUE, with picture details
+ * in the return arguments.
+ */
+static int gln_graphics_get_picture_details(int *width, int *height) {
+	if (gln_graphics_picture_is_available()) {
+		if (width)
+			*width = gln_graphics_width;
+		if (height)
+			*height = gln_graphics_height;
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+/*
+ * gln_graphics_get_rendering_details()
+ *
+ * Returns the type of bitmap in use (if any), as a string, the count of
+ * colors in the picture, and a flag indicating if graphics is active (busy).
+ * The function return FALSE if graphics is not enabled or if not being
+ * displayed, otherwise TRUE with the bitmap type, color count, and active
+ * flag in the return arguments.
+ *
+ * This function races with the graphics timeout, as it returns information
+ * set up by the first timeout following a new picture.  There's a very
+ * very small chance that it might win the race, in which case out-of-date
+ * values are returned.
+ */
+static int gln_graphics_get_rendering_details(const char **bitmap_type,
+        int *color_count, int *is_active) {
+	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+		/*
+		 * Convert the detected bitmap type into a string and return it.
+		 * A NULL bitmap string implies no bitmaps.
+		 */
+		if (bitmap_type) {
+			const char *return_type;
+
+			switch (gln_graphics_bitmap_type) {
+			case AMIGA_BITMAPS:
+				return_type = "Amiga";
+				break;
+			case PC1_BITMAPS:
+				return_type = "IBM PC(1)";
+				break;
+			case PC2_BITMAPS:
+				return_type = "IBM PC(2)";
+				break;
+			case C64_BITMAPS:
+				return_type = "Commodore 64";
+				break;
+			case BBC_BITMAPS:
+				return_type = "BBC B";
+				break;
+			case CPC_BITMAPS:
+				return_type = "Amstrad CPC/Spectrum";
+				break;
+			case MAC_BITMAPS:
+				return_type = "Macintosh";
+				break;
+			case ST1_BITMAPS:
+				return_type = "Atari ST(1)";
+				break;
+			case ST2_BITMAPS:
+				return_type = "Atari ST(2)";
+				break;
+			case NO_BITMAPS:
+			default:
+				return_type = NULL;
+				break;
+			}
+
+			*bitmap_type = return_type;
+		}
+
+		/*
+		 * Return the color count noted by timeouts on the first timeout
+		 * following a new picture.  We might return the one for the prior
+		 * picture.
+		 */
+		if (color_count)
+			*color_count = gln_graphics_color_count;
+
+		/* Return graphics active flag. */
+		if (is_active)
+			*is_active = gln_graphics_active;
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+/*
+ * gln_graphics_interpreter_enabled()
+ *
+ * Return TRUE if it looks like interpreter graphics are turned on, FALSE
+ * otherwise.
+ */
+static int gln_graphics_interpreter_enabled(void) {
+	return gln_graphics_interpreter_state != GLN_GRAPHICS_OFF;
+}
+
+
+/*
+ * gln_graphics_cleanup()
+ *
+ * Free memory resources allocated by graphics functions.  Called on game
+ * end.
+ */
+static void gln_graphics_cleanup(void) {
+	free(gln_graphics_bitmap);
+	gln_graphics_bitmap = NULL;
+	free(gln_graphics_off_screen);
+	gln_graphics_off_screen = NULL;
+	free(gln_graphics_on_screen);
+	gln_graphics_on_screen = NULL;
+	free(gln_graphics_bitmap_directory);
+	gln_graphics_bitmap_directory = NULL;
+
+	gln_graphics_bitmap_type = NO_BITMAPS;
+	gln_graphics_picture = -1;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port line drawing picture adapter functions                    */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Graphics color table.  These eight colors are selected into the four-
+ * color palette by os_setcolour().  The standard Amiga palette is rather
+ * over-vibrant, so to soften it a bit this table uses non-primary colors.
+ */
+static const gln_rgb_t GLN_LINEGRAPHICS_COLOR_TABLE[] = {
+	{ 47,  79,  79},  /* DarkSlateGray  [Black] */
+	{238,  44,  44},  /* Firebrick2     [Red] */
+	{ 67, 205, 128},  /* SeaGreen3      [Green] */
+	{238, 201,   0},  /* Gold2          [Yellow] */
+	{ 92, 172, 238},  /* SteelBlue2     [Blue] */
+	{139,  87,  66},  /* LightSalmon4   [Brown] */
+	{175, 238, 238},  /* PaleTurquoise  [Cyan] */
+	{245, 245, 245},  /* WhiteSmoke     [White] */
+};
+
+/*
+ * Structure of a Seed Fill segment entry, and a growable stack-based array
+ * of segments pending fill.  When length exceeds size, size is increased
+ * and the array grown.
+ */
+struct gln_linegraphics_segment_t {
+	int y;   /* Segment y coordinate */
+	int xl;  /* Segment x left hand side coordinate */
+	int xr;  /* Segment x right hand side coordinate */
+	int dy;  /* Segment y delta */
+};
+
+static gln_linegraphics_segment_t *gln_linegraphics_fill_segments = NULL;
+static int gln_linegraphics_fill_segments_allocation = 0,
+           gln_linegraphics_fill_segments_length = 0;
+
+
+/*
+ * gln_linegraphics_create_context()
+ *
+ * Initialize a new constructed bitmap graphics context for line drawn
+ * graphics.
+ */
+static void gln_linegraphics_create_context(void) {
+	int width, height;
+	long picture_bytes;
+
+	/* Get the picture size, and calculate the bytes in the bitmap. */
+	GetPictureSize(&width, &height);
+	picture_bytes = width * height * sizeof(*gln_graphics_bitmap);
+
+	/*
+	 * Destroy any current bitmap, and begin a fresh one.  Here we set the
+	 * bitmap and the palette to all zeroes; this equates to all black.
+	 */
+	free(gln_graphics_bitmap);
+	gln_graphics_bitmap = (gln_byte *)gln_malloc(picture_bytes);
+	memset(gln_graphics_bitmap, 0, picture_bytes);
+	gln_graphics_width = width;
+	gln_graphics_height = height;
+	memset(gln_graphics_palette, 0, sizeof(gln_graphics_palette));
+
+	/* Set graphics picture number to -1; this is not a real game bitmap. */
+	gln_graphics_picture = -1;
+}
+
+
+/*
+ * gln_linegraphics_clear_context()
+ *
+ * Clear the complete graphical drawing area, setting all pixels to zero,
+ * and resetting the palette to all black as well.
+ */
+static void gln_linegraphics_clear_context(void) {
+	long picture_bytes;
+
+	/* Get the picture size, and zero all bytes in the bitmap. */
+	picture_bytes = gln_graphics_width
+	                * gln_graphics_height * sizeof(*gln_graphics_bitmap);
+	memset(gln_graphics_bitmap, 0, picture_bytes);
+
+	/* Clear palette colors to all black. */
+	memset(gln_graphics_palette, 0, sizeof(gln_graphics_palette));
+}
+
+
+/*
+ * gln_linegraphics_set_palette_color()
+ *
+ * Copy the indicated main color table entry into the palette.
+ */
+static void gln_linegraphics_set_palette_color(int colour, int index) {
+	const gln_rgb_t *entry;
+	assert(colour < GLN_PALETTE_SIZE);
+	assert(index < (int)sizeof(GLN_LINEGRAPHICS_COLOR_TABLE)
+	       / (int)sizeof(GLN_LINEGRAPHICS_COLOR_TABLE[0]));
+
+	/* Copy the color table entry to the constructed game palette. */
+	entry = GLN_LINEGRAPHICS_COLOR_TABLE + index;
+	gln_graphics_palette[colour].red   = entry->red;
+	gln_graphics_palette[colour].green = entry->green;
+	gln_graphics_palette[colour].blue  = entry->blue;
+}
+
+
+/*
+ * gln_linegraphics_get_pixel()
+ * gln_linegraphics_set_pixel()
+ *
+ * Return and set the bitmap pixel at x,y.
+ */
+static gln_byte gln_linegraphics_get_pixel(int x, int y) {
+	assert(x >= 0 && x < gln_graphics_width
+	       && y >= 0 && y < gln_graphics_height);
+
+	return gln_graphics_bitmap[y * gln_graphics_width + x];
+}
+
+static void gln_linegraphics_set_pixel(int x, int y, gln_byte color) {
+	assert(x >= 0 && x < gln_graphics_width
+	       && y >= 0 && y < gln_graphics_height);
+
+	gln_graphics_bitmap[y * gln_graphics_width + x] = color;
+}
+
+
+/*
+ * gln_linegraphics_plot_clip()
+ * gln_linegraphics_draw_line_if()
+ *
+ * Draw a line from x1,y1 to x2,y2 in colour1, where the existing pixel
+ * colour is colour2.  The function uses Bresenham's algorithm.  The second
+ * function, gln_graphics_plot_clip, is a line drawing helper; it handles
+ * clipping, and the requirement to plot a point only if it matches colour2.
+ */
+static void gln_linegraphics_plot_clip(int x, int y, int colour1, int colour2) {
+	/*
+	 * Clip the plot if the value is outside the context.  Otherwise, plot the
+	 * pixel as colour1 if it is currently colour2.
+	 */
+	if (x >= 0 && x < gln_graphics_width && y >= 0 && y < gln_graphics_height) {
+		if (gln_linegraphics_get_pixel(x, y) == colour2)
+			gln_linegraphics_set_pixel(x, y, colour1);
+	}
+}
+
+static void gln_linegraphics_draw_line_if(int x1, int y1, int x2, int y2,
+        int colour1, int colour2) {
+	int x, y, dx, dy, incx, incy, balance;
+
+	/* Ignore any odd request where there will be no colour changes. */
+	if (colour1 == colour2)
+		return;
+
+	/* Normalize the line into deltas and increments. */
+	if (x2 >= x1) {
+		dx = x2 - x1;
+		incx = 1;
+	} else {
+		dx = x1 - x2;
+		incx = -1;
+	}
+
+	if (y2 >= y1) {
+		dy = y2 - y1;
+		incy = 1;
+	} else {
+		dy = y1 - y2;
+		incy = -1;
+	}
+
+	/* Start at x1,y1. */
+	x = x1;
+	y = y1;
+
+	/* Decide on a direction to progress in. */
+	if (dx >= dy) {
+		dy <<= 1;
+		balance = dy - dx;
+		dx <<= 1;
+
+		/* Loop until we reach the end point of the line. */
+		while (x != x2) {
+			gln_linegraphics_plot_clip(x, y, colour1, colour2);
+			if (balance >= 0) {
+				y += incy;
+				balance -= dx;
+			}
+			balance += dy;
+			x += incx;
+		}
+		gln_linegraphics_plot_clip(x, y, colour1, colour2);
+	} else {
+		dx <<= 1;
+		balance = dx - dy;
+		dy <<= 1;
+
+		/* Loop until we reach the end point of the line. */
+		while (y != y2) {
+			gln_linegraphics_plot_clip(x, y, colour1, colour2);
+			if (balance >= 0) {
+				x += incx;
+				balance -= dy;
+			}
+			balance += dx;
+			y += incy;
+		}
+		gln_linegraphics_plot_clip(x, y, colour1, colour2);
+	}
+}
+
+
+/*
+ * gln_linegraphics_push_fill_segment()
+ * gln_linegraphics_pop_fill_segment()
+ * gln_linegraphics_fill_4way_if()
+ *
+ * Area fill algorithm, set a region to colour1 if it is currently set to
+ * colour2.  This function is a derivation of Paul Heckbert's Seed Fill,
+ * from "Graphics Gems", Academic Press, 1990, which fills 4-connected
+ * neighbors.
+ *
+ * The main modification is to make segment stacks growable, through the
+ * helper push and pop functions.  There is also a small adaptation to
+ * check explicitly for color2, to meet the Level 9 API.
+ */
+static void gln_linegraphics_push_fill_segment(int y, int xl, int xr, int dy) {
+	/* Clip points outside the graphics context. */
+	if (!(y + dy < 0 || y + dy >= gln_graphics_height)) {
+		int length, allocation;
+
+		length = ++gln_linegraphics_fill_segments_length;
+		allocation = gln_linegraphics_fill_segments_allocation;
+
+		/* Grow the segments stack if required, successively doubling. */
+		if (length > allocation) {
+			size_t bytes;
+
+			allocation = allocation == 0 ? 1 : allocation << 1;
+
+			bytes = allocation * sizeof(*gln_linegraphics_fill_segments);
+			gln_linegraphics_fill_segments =
+			    (gln_linegraphics_segment_t *)gln_realloc(gln_linegraphics_fill_segments, bytes);
+		}
+
+		/* Push top of segments stack. */
+		gln_linegraphics_fill_segments[length - 1].y  = y;
+		gln_linegraphics_fill_segments[length - 1].xl = xl;
+		gln_linegraphics_fill_segments[length - 1].xr = xr;
+		gln_linegraphics_fill_segments[length - 1].dy = dy;
+
+		/* Write back local dimensions copies. */
+		gln_linegraphics_fill_segments_length = length;
+		gln_linegraphics_fill_segments_allocation = allocation;
+	}
+}
+
+static void gln_linegraphics_pop_fill_segment(int *y, int *xl, int *xr, int *dy) {
+	int length;
+	assert(gln_linegraphics_fill_segments_length > 0);
+
+	length = --gln_linegraphics_fill_segments_length;
+
+	/* Pop top of segments stack. */
+	*y  = gln_linegraphics_fill_segments[length].y;
+	*xl = gln_linegraphics_fill_segments[length].xl;
+	*xr = gln_linegraphics_fill_segments[length].xr;
+	*dy = gln_linegraphics_fill_segments[length].dy;
+}
+
+static void gln_linegraphics_fill_4way_if(int x, int y, int colour1, int colour2) {
+	/* Ignore any odd request where there will be no colour changes. */
+	if (colour1 == colour2)
+		return;
+
+	/* Clip fill requests to visible graphics region. */
+	if (x >= 0 && x < gln_graphics_width && y >= 0 && y < gln_graphics_height) {
+		int left, x1, x2, dy, x_lo, x_hi;
+
+		/*
+		 * Level 9 API; explicit check for a match against colour2.  This also
+		 * covers the standard Seed Fill check that old pixel value should not
+		 * equal colour1, because of the color1 == colour2 comparison above.
+		 */
+		if (gln_linegraphics_get_pixel(x, y) != colour2)
+			return;
+
+		/*
+		 * Set up inclusive window dimension to ease algorithm translation.
+		 * The original worked with inclusive rectangle limits.
+		 */
+		x_lo = 0;
+		x_hi = gln_graphics_width - 1;
+
+		/*
+		 * The first of these is "needed in some cases", the second is the seed
+		 * segment, popped first.
+		 */
+		gln_linegraphics_push_fill_segment(y, x, x, 1);
+		gln_linegraphics_push_fill_segment(y + 1, x, x, -1);
+
+		while (gln_linegraphics_fill_segments_length > 0) {
+			/* Pop segment off stack and add delta to y coord. */
+			gln_linegraphics_pop_fill_segment(&y, &x1, &x2, &dy);
+			y += dy;
+
+			/*
+			 * Segment of scan line y-dy for x1<=x<=x2 was previously filled,
+			 * now explore adjacent pixels in scan line y.
+			 */
+			for (x = x1;
+			        x >= x_lo && gln_linegraphics_get_pixel(x, y) == colour2;
+			        x--) {
+				gln_linegraphics_set_pixel(x, y, colour1);
+			}
+
+			if (x >= x1)
+				goto skip;
+
+			left = x + 1;
+			if (left < x1) {
+				/* Leak on left? */
+				gln_linegraphics_push_fill_segment(y, left, x1 - 1, -dy);
+			}
+
+			x = x1 + 1;
+			do {
+				for (;
+				        x <= x_hi && gln_linegraphics_get_pixel(x, y) == colour2;
+				        x++) {
+					gln_linegraphics_set_pixel(x, y, colour1);
+				}
+
+				gln_linegraphics_push_fill_segment(y, left, x - 1, dy);
+
+				if (x > x2 + 1) {
+					/* Leak on right? */
+					gln_linegraphics_push_fill_segment(y, x2 + 1, x - 1, -dy);
+				}
+
+skip:
+				for (x++;
+				        x <= x2 && gln_linegraphics_get_pixel(x, y) != colour2;
+				        x++)
+					;
+
+				left = x;
+			} while (x <= x2);
+		}
+	}
+}
+
+
+/*
+ * os_cleargraphics()
+ * os_setcolour()
+ * os_drawline()
+ * os_fill()
+ *
+ * Interpreter entry points for line drawing graphics.  All calls to these
+ * are ignored if line drawing mode is not set.
+ */
+void os_cleargraphics(void) {
+	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+		gln_linegraphics_clear_context();
+}
+
+void os_setcolour(int colour, int index) {
+	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+		gln_linegraphics_set_palette_color(colour, index);
+}
+
+void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) {
+	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+		gln_linegraphics_draw_line_if(x1, y1, x2, y2, colour1, colour2);
+}
+
+void os_fill(int x, int y, int colour1, int colour2) {
+	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+		gln_linegraphics_fill_4way_if(x, y, colour1, colour2);
+}
+
+
+/*
+ * gln_linegraphics_process()
+ *
+ * Process as many graphics opcodes as are available, constructing the
+ * resulting image as a bitmap.  When complete, treat as normal bitmaps.
+ */
+static void gln_linegraphics_process(void) {
+	/*
+	 * If interpreter graphics are not set to line mode, ignore any call that
+	 * arrives here.
+	 */
+	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE) {
+		int opcodes_count;
+
+		/* Run all the available graphics opcodes. */
+		for (opcodes_count = 0; RunGraphics();) {
+			opcodes_count++;
+			g_vm->glk_tick();
+		}
+
+		/*
+		 * If graphics is enabled and we created an image with graphics
+		 * opcodes above, open a graphics window and start bitmap display.
+		 */
+		if (gln_graphics_enabled && opcodes_count > 0) {
+			if (gln_graphics_open()) {
+				/* Set the new picture flag, and start the updating "thread". */
+				gln_graphics_new_picture = TRUE;
+				gln_graphics_start();
+			}
+		}
+	}
+}
+
+
+/*
+ * gln_linegraphics_cleanup()
+ *
+ * Free memory resources allocated by line graphics functions.  Called on
+ * game end.
+ */
+static void gln_linegraphics_cleanup(void) {
+	free(gln_linegraphics_fill_segments);
+	gln_linegraphics_fill_segments = NULL;
+
+	gln_linegraphics_fill_segments_allocation = 0;
+	gln_linegraphics_fill_segments_length = 0;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk picture dispatch (bitmap or line), and timer arbitration       */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Note of the current set graphics mode, to detect changes in mode from
+ * the core interpreter.
+ */
+static int gln_graphics_current_mode = -1;
+
+/* Note indicating if the graphics "thread" is temporarily suspended. */
+static int gln_graphics_suspended = FALSE;
+
+
+/*
+ * os_graphics()
+ *
+ * Called by the main interpreter to turn graphics on and off.  Mode 0
+ * turns graphics off, mode 1 is line drawing graphics, and mode 2 is
+ * bitmap graphics.
+ *
+ * This function tracks the current state of interpreter graphics setting
+ * using gln_graphics_interpreter_state.
+ */
+void os_graphics(int mode) {
+	/* Ignore the call unless it changes the graphics mode. */
+	if (mode != gln_graphics_current_mode) {
+		/* Set tracked interpreter state given the input mode. */
+		switch (mode) {
+		case 0:
+			gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
+			break;
+
+		case 1:
+			gln_graphics_interpreter_state = GLN_GRAPHICS_LINE_MODE;
+			break;
+
+		case 2:
+			/* If no graphics bitmaps were detected, ignore this call. */
+			if (!gln_graphics_bitmap_directory
+			        || gln_graphics_bitmap_type == NO_BITMAPS)
+				return;
+
+			gln_graphics_interpreter_state = GLN_GRAPHICS_BITMAP_MODE;
+			break;
+		}
+
+		/* Given the interpreter state, update graphics activities. */
+		switch (gln_graphics_interpreter_state) {
+		case GLN_GRAPHICS_OFF:
+
+			/* If currently displaying graphics, stop and close window. */
+			if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+				gln_graphics_stop();
+				gln_graphics_close();
+			}
+			break;
+
+		case GLN_GRAPHICS_LINE_MODE:
+		case GLN_GRAPHICS_BITMAP_MODE:
+
+			/* Create a new graphics context on switch to line mode. */
+			if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
+				gln_linegraphics_create_context();
+
+			/*
+			 * If we have a picture loaded already, restart graphics. If not,
+			 * we'll delay this until one is supplied by a call to
+			 * os_show_bitmap().
+			 */
+			if (gln_graphics_enabled && gln_graphics_bitmap) {
+				if (gln_graphics_open())
+					gln_graphics_restart();
+			}
+			break;
+		}
+
+		/* Note the current mode so changes can be detected. */
+		gln_graphics_current_mode = mode;
+	}
+}
+
+
+/*
+ * gln_arbitrate_request_timer_events()
+ *
+ * Shim function for g_vm->glk_request_timer_events(), this function should be
+ * called by other functional areas in place of the main timer event setting
+ * function.  It suspends graphics if busy when setting timer events, and
+ * resumes graphics if necessary when clearing timer events.
+ *
+ * On resuming, it calls the graphics timeout function to simulate the
+ * timeout that has (probably) been missed.  This also ensures that tight
+ * loops that enable then disable timers using this function don't lock out
+ * the graphics completely.
+ *
+ * Use only in paired calls, the first non-zero, the second zero, and use
+ * no graphics functions between calls.
+ */
+static void gln_arbitrate_request_timer_events(glui32 millisecs) {
+	if (millisecs > 0) {
+		/* Setting timer events; suspend graphics if currently active. */
+		if (gln_graphics_active) {
+			gln_graphics_suspended = TRUE;
+			gln_graphics_stop();
+		}
+
+		/* Set timer events as requested. */
+		g_vm->glk_request_timer_events(millisecs);
+	} else {
+		/*
+		 * Resume graphics if currently suspended, otherwise cancel timer
+		 * events as requested by the caller.
+		 */
+		if (gln_graphics_suspended) {
+			gln_graphics_suspended = FALSE;
+			gln_graphics_start();
+
+			/* Simulate the "missed" graphics timeout. */
+			gln_graphics_timeout();
+		} else
+			g_vm->glk_request_timer_events(0);
+	}
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port infinite loop detection functions                         */
+/*---------------------------------------------------------------------*/
+
+/* Short timeout to wait purely in order to get the display updated. */
+static const glui32 GLN_WATCHDOG_FIXUP = 50;
+
+/*
+ * Timestamp of the last watchdog tick call, and timeout.  This is used to
+ * monitor the elapsed time since the interpreter made an I/O call.  If it
+ * remains silent for long enough, set by the timeout, we'll offer the
+ * option to end the game.  A timeout of zero disables the watchdog.
+ */
+static time_t gln_watchdog_monitor = 0;
+static double gln_watchdog_timeout_secs = 0.0;
+
+/*
+ * To save thrashing in time(), we want to check for timeouts less frequently
+ * than we're polled.  Here's the control for that.
+ */
+static int gln_watchdog_check_period = 0,
+           gln_watchdog_check_counter = 0;
+
+
+/*
+ * gln_watchdog_start()
+ * gln_watchdog_stop()
+ *
+ * Start and stop watchdog monitoring.
+ */
+static void gln_watchdog_start(int timeout, int period) {
+	assert(timeout > 0 && period > 0);
+
+	gln_watchdog_timeout_secs = (double) timeout;
+	gln_watchdog_check_period = period;
+	gln_watchdog_check_counter = period;
+	gln_watchdog_monitor = g_system->getMillis();
+}
+
+static void gln_watchdog_stop(void) {
+	gln_watchdog_timeout_secs = 0;
+}
+
+
+/*
+ * gln_watchdog_tick()
+ *
+ * Set the watchdog timestamp to the current system time.
+ *
+ * This function should be called just before almost every os_* function
+ * returns to the interpreter, as a means of timing how long the interpreter
+ * dwells in running game code.
+ */
+static void gln_watchdog_tick(void) {
+	gln_watchdog_monitor = g_system->getMillis();
+}
+
+
+/*
+ * gln_watchdog_has_timed_out()
+ *
+ * Check to see if too much time has elapsed since the last tick.  If it has,
+ * offer the option to stop the game, and if accepted, return TRUE.  Otherwise,
+ * if no timeout, or if the watchdog is disabled, return FALSE.
+ *
+ * This function only checks every N calls; it's called extremely frequently
+ * from opcode handling, and will thrash in time() if it checks on each call.
+ */
+static int gln_watchdog_has_timed_out(void) {
+	/* If loop detection is off or the timeout is set to zero, do nothing. */
+	if (gln_loopcheck_enabled && gln_watchdog_timeout_secs > 0) {
+		time_t now;
+		double delta_time;
+
+		/*
+		 * Wait until we've seen enough calls to make a timeout check.  If we
+		 * haven't, return FALSE, otherwise reset the counter and continue.
+		 */
+		if (--gln_watchdog_check_counter > 0)
+			return FALSE;
+		else
+			gln_watchdog_check_counter = gln_watchdog_check_period;
+
+		/*
+		 * Determine how much time has passed, and offer to end the game if it
+		 * exceeds the allowable timeout.
+		 */
+		now = g_system->getMillis();
+		delta_time = (now - gln_watchdog_monitor) / 1000;
+
+		if (delta_time >= gln_watchdog_timeout_secs) {
+			if (gln_confirm("\nThe game may be in an infinite loop.  Do you"
+			                " want to stop it? [Y or N] ")) {
+				gln_watchdog_tick();
+				return TRUE;
+			}
+
+			/*
+			 * If we have timers, set a really short timeout and let it expire.
+			 * This is to force a display update with the response of the
+			 * confirm -- without this, we may not get a screen update for a
+			 * while since at this point the game isn't, by definition, doing
+			 * any input or output.  If we don't have timers, no biggie.
+			 */
+			if (g_vm->glk_gestalt(gestalt_Timer, 0)) {
+				event_t event;
+
+				gln_arbitrate_request_timer_events(GLN_WATCHDOG_FIXUP);
+				gln_event_wait(evtype_Timer, &event);
+				gln_arbitrate_request_timer_events(0);
+			}
+
+			/* Reset the monitor and drop into FALSE return -- stop rejected. */
+			gln_watchdog_tick();
+		}
+	}
+
+	/* No timeout indicated, or offer rejected by the user. */
+	return FALSE;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port status line functions                                     */
+/*---------------------------------------------------------------------*/
+
+/* Default width used for non-windowing Glk status lines. */
+static const int GLN_DEFAULT_STATUS_WIDTH = 74;
+
+
+/*
+ * gln_status_update()
+ *
+ * Update the information in the status window with the current contents of
+ * the current game identity string, or a default string if no game identity
+ * could be established.
+ */
+static void gln_status_update(void) {
+	uint width, height;
+	assert(gln_status_window);
+
+	g_vm->glk_window_get_size(gln_status_window, &width, &height);
+	if (height > 0) {
+		const char *game_name;
+
+		g_vm->glk_window_clear(gln_status_window);
+		g_vm->glk_window_move_cursor(gln_status_window, 0, 0);
+		g_vm->glk_set_window(gln_status_window);
+
+		/*
+		 * Try to establish a game identity to display; if none, use a standard
+		 * message instead.
+		 */
+		game_name = gln_gameid_get_game_name();
+		g_vm->glk_put_string(game_name ? game_name : "ScummVM GLK Level 9 Game");
+
+		g_vm->glk_set_window(gln_main_window);
+	}
+}
+
+
+/*
+ * gln_status_print()
+ *
+ * Print the current contents of the game identity out in the main window,
+ * if it has changed since the last call.  This is for non-windowing Glk
+ * libraries.
+ *
+ * To save memory management hassles, this function uses the CRC functions
+ * to detect changes of game identity string, and gambles a little on the
+ * belief that two games' strings won't have the same CRC.
+ */
+static void gln_status_print(void) {
+	static int is_initialized = FALSE;
+	static gln_uint16 crc = 0;
+
+	const char *game_name;
+
+	/* Get the current game name, and do nothing if none available. */
+	game_name = gln_gameid_get_game_name();
+	if (game_name) {
+		gln_uint16 new_crc;
+
+		/*
+		 * If not the first call and the game identity string has not changed,
+		 * again, do nothing.
+		 */
+		new_crc = gln_get_buffer_crc(game_name, strlen(game_name), 0);
+		if (!is_initialized || new_crc != crc) {
+			int index;
+
+#ifndef GARGLK
+			/* Set fixed width font to try to preserve status line formatting. */
+			g_vm->glk_set_style(style_Preformatted);
+#endif
+
+			/* Bracket, and output the extracted game name. */
+			g_vm->glk_put_string("[ ");
+			g_vm->glk_put_string((char *) game_name);
+
+			for (index = strlen(game_name);
+			        index <= GLN_DEFAULT_STATUS_WIDTH; index++)
+				g_vm->glk_put_char(' ');
+			g_vm->glk_put_string(" ]\n");
+
+			crc = new_crc;
+			is_initialized = TRUE;
+		}
+	}
+}
+
+
+/*
+ * gln_status_notify()
+ *
+ * Front end function for updating status.  Either updates the status window
+ * or prints the status line to the main window.
+ */
+static void gln_status_notify(void) {
+	if (gln_status_window)
+		gln_status_update();
+	else
+		gln_status_print();
+}
+
+
+/*
+ * gln_status_redraw()
+ *
+ * Redraw the contents of any status window with the buffered status string.
+ * This function should be called on the appropriate Glk window resize and
+ * arrange events.
+ */
+static void gln_status_redraw(void) {
+	if (gln_status_window) {
+		winid_t parent;
+
+		/*
+		 * Rearrange the status window, without changing its actual arrangement
+		 * in any way.  This is a hack to work round incorrect window repainting
+		 * in Xglk; it forces a complete repaint of affected windows on Glk
+		 * window resize and arrange events, and works in part because Xglk
+		 * doesn't check for actual arrangement changes in any way before
+		 * invalidating its windows.  The hack should be harmless to Glk
+		 * libraries other than Xglk, moreover, we're careful to activate it
+		 * only on resize and arrange events.
+		 */
+		parent = g_vm->glk_window_get_parent(gln_status_window);
+		g_vm->glk_window_set_arrangement(parent,
+		                                 winmethod_Above | winmethod_Fixed, 1, NULL);
+
+		gln_status_update();
+	}
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port output functions                                          */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Flag for if the user entered "help" as their last input, or if hints have
+ * been silenced as a result of already using a Glk command.
+ */
+static int gln_help_requested = FALSE,
+           gln_help_hints_silenced = FALSE;
+
+/*
+ * Output buffer.  We receive characters one at a time, and it's a bit
+ * more efficient for everyone if we buffer them, and output a complete
+ * string on a flush call.
+ */
+static char *gln_output_buffer = NULL;
+static int gln_output_allocation = 0,
+           gln_output_length = 0;
+
+/*
+ * Output activity flag.  Set when os_printchar() is called, and queried
+ * periodically by os_readchar().  Helps os_readchar() judge whether it must
+ * request input, or when it's being used as a crude scroll control.
+ */
+static int gln_output_activity = FALSE;
+
+/*
+ * Flag to indicate if the last buffer flushed looked like it ended in a
+ * "> " prompt.  Some later games switch to this mode after a while, and
+ * it's nice not to duplicate this prompt with our own.
+ */
+static int gln_output_prompt = FALSE;
+
+
+/*
+ * gln_output_notify()
+ *
+ * Register recent text output from the interpreter.  This function is
+ * called by os_printchar().
+ */
+static void gln_output_notify(void) {
+	gln_output_activity = TRUE;
+}
+
+
+/*
+ * gln_recent_output()
+ *
+ * Return TRUE if the interpreter has recently output text, FALSE otherwise.
+ * Clears the flag, so that more output text is required before the next
+ * call returns TRUE.
+ */
+static int gln_recent_output(void) {
+	int result;
+
+	result = gln_output_activity;
+	gln_output_activity = FALSE;
+
+	return result;
+}
+
+
+/*
+ * gln_output_register_help_request()
+ * gln_output_silence_help_hints()
+ * gln_output_provide_help_hint()
+ *
+ * Register a request for help, and print a note of how to get Glk command
+ * help from the interpreter unless silenced.
+ */
+static void gln_output_register_help_request(void) {
+	gln_help_requested = TRUE;
+}
+
+static void gln_output_silence_help_hints(void) {
+	gln_help_hints_silenced = TRUE;
+}
+
+static void gln_output_provide_help_hint(void) {
+	if (gln_help_requested && !gln_help_hints_silenced) {
+		g_vm->glk_set_style(style_Emphasized);
+		g_vm->glk_put_string("[Try 'glk help' for help on special interpreter"
+		                     " commands]\n");
+
+		gln_help_requested = FALSE;
+		g_vm->glk_set_style(style_Normal);
+	}
+}
+
+
+/*
+ * gln_game_prompted()
+ *
+ * Return TRUE if the last game output appears to have been a "> " prompt.
+ * Once called, the flag is reset to FALSE, and requires more game output
+ * to set it again.
+ */
+static int gln_game_prompted(void) {
+	int result;
+
+	result = gln_output_prompt;
+	gln_output_prompt = FALSE;
+
+	return result;
+}
+
+
+/*
+ * gln_detect_game_prompt()
+ *
+ * See if the last non-newline-terminated line in the output buffer seems
+ * to be a prompt, and set the game prompted flag if it does, otherwise
+ * clear it.
+ */
+static void gln_detect_game_prompt(void) {
+	int index;
+
+	gln_output_prompt = FALSE;
+
+	/*
+	 * Search for a prompt across any last unterminated buffered line; a prompt
+	 * is any non-space character on that line.
+	 */
+	for (index = gln_output_length - 1;
+	        index >= 0 && gln_output_buffer[index] != '\n'; index--) {
+		if (gln_output_buffer[index] != ' ') {
+			gln_output_prompt = TRUE;
+			break;
+		}
+	}
+}
+
+
+/*
+ * gln_output_delete()
+ *
+ * Delete all buffered output text.  Free all malloc'ed buffer memory, and
+ * return the buffer variables to their initial values.
+ */
+static void gln_output_delete(void) {
+	free(gln_output_buffer);
+	gln_output_buffer = NULL;
+	gln_output_allocation = gln_output_length = 0;
+}
+
+
+/*
+ * gln_output_flush()
+ *
+ * Flush any buffered output text to the Glk main window, and clear the
+ * buffer.  Check in passing for game prompts that duplicate our's.
+ */
+static void gln_output_flush(void) {
+	assert(g_vm->glk_stream_get_current());
+
+	if (gln_output_length > 0) {
+		/*
+		 * See if the game issued a standard prompt, then print the buffer to
+		 * the main window.  If providing a help hint, position that before
+		 * the game's prompt (if any).
+		 */
+		gln_detect_game_prompt();
+
+		if (gln_output_prompt) {
+			int index;
+
+			for (index = gln_output_length - 1;
+			        index >= 0 && gln_output_buffer[index] != '\n';)
+				index--;
+
+			g_vm->glk_put_buffer(gln_output_buffer, index + 1);
+			gln_output_provide_help_hint();
+			g_vm->glk_put_buffer(gln_output_buffer + index + 1,
+			                     gln_output_length - index - 1);
+		} else {
+			g_vm->glk_put_buffer(gln_output_buffer, gln_output_length);
+			gln_output_provide_help_hint();
+		}
+
+		gln_output_delete();
+	}
+}
+
+
+/*
+ * os_printchar()
+ *
+ * Buffer a character for eventual printing to the main window.
+ */
+void os_printchar(char c) {
+	int bytes;
+	assert(gln_output_length <= gln_output_allocation);
+
+	/* Grow the output buffer if necessary. */
+	for (bytes = gln_output_allocation; bytes < gln_output_length + 1;)
+		bytes = bytes == 0 ? 1 : bytes << 1;
+
+	if (bytes > gln_output_allocation) {
+		gln_output_buffer = (char *)gln_realloc(gln_output_buffer, bytes);
+		gln_output_allocation = bytes;
+	}
+
+	/*
+	 * Add the character to the buffer, handling return as a newline, and
+	 * note that the game created some output.
+	 */
+	gln_output_buffer[gln_output_length++] = (c == '\r' ? '\n' : c);
+	gln_output_notify();
+}
+
+
+/*
+ * gln_styled_string()
+ * gln_styled_char()
+ * gln_standout_string()
+ * gln_standout_char()
+ * gln_normal_string()
+ * gln_normal_char()
+ * gln_header_string()
+ * gln_banner_string()
+ *
+ * Convenience functions to print strings in assorted styles.  A standout
+ * string is one that hints that it's from the interpreter, not the game.
+ */
+static void gln_styled_string(glui32 style, const char *message) {
+	assert(message);
+
+	g_vm->glk_set_style(style);
+	g_vm->glk_put_string(message);
+	g_vm->glk_set_style(style_Normal);
+}
+
+static void gln_styled_char(glui32 style, char c) {
+	char buffer[2];
+
+	buffer[0] = c;
+	buffer[1] = '\0';
+	gln_styled_string(style, buffer);
+}
+
+static void gln_standout_string(const char *message) {
+	gln_styled_string(style_Emphasized, message);
+}
+
+static void gln_standout_char(char c) {
+	gln_styled_char(style_Emphasized, c);
+}
+
+static void gln_normal_string(const char *message) {
+	gln_styled_string(style_Normal, message);
+}
+
+static void gln_normal_char(char c) {
+	gln_styled_char(style_Normal, c);
+}
+
+static void gln_header_string(const char *message) {
+	gln_styled_string(style_Header, message);
+}
+
+static void gln_banner_string(const char *message) {
+	gln_styled_string(style_Subheader, message);
+}
+
+
+/*
+ * os_flush()
+ *
+ * Handle a core interpreter call to flush the output buffer.  Because Glk
+ * only flushes its buffers and displays text on g_vm->glk_select(), we can ignore
+ * these calls as long as we call g_vm->glk_output_flush() when reading line or
+ * character input.
+ *
+ * Taking os_flush() at face value can cause game text to appear before status
+ * line text where we are working with a non-windowing Glk, so it's best
+ * ignored where we can.
+ */
+void os_flush(void) {
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk command escape functions                                       */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gln_command_script()
+ *
+ * Turn game output scripting (logging) on and off.
+ */
+static void gln_command_script(const char *argument) {
+	assert(argument);
+
+	if (gln_strcasecmp(argument, "on") == 0) {
+		frefid_t fileref;
+
+		if (gln_transcript_stream) {
+			gln_normal_string("Glk transcript is already on.\n");
+			return;
+		}
+
+		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_Transcript
+		          | fileusage_TextMode,
+		          filemode_WriteAppend, 0);
+		if (!fileref) {
+			gln_standout_string("Glk transcript failed.\n");
+			return;
+		}
+
+		gln_transcript_stream = g_vm->glk_stream_open_file(fileref,
+		                        filemode_WriteAppend, 0);
+		g_vm->glk_fileref_destroy(fileref);
+		if (!gln_transcript_stream) {
+			gln_standout_string("Glk transcript failed.\n");
+			return;
+		}
+
+		g_vm->glk_window_set_echo_stream(gln_main_window, gln_transcript_stream);
+
+		gln_normal_string("Glk transcript is now on.\n");
+	}
+
+	else if (gln_strcasecmp(argument, "off") == 0) {
+		if (!gln_transcript_stream) {
+			gln_normal_string("Glk transcript is already off.\n");
+			return;
+		}
+
+		g_vm->glk_stream_close(gln_transcript_stream, NULL);
+		gln_transcript_stream = NULL;
+
+		g_vm->glk_window_set_echo_stream(gln_main_window, NULL);
+
+		gln_normal_string("Glk transcript is now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gln_normal_string("Glk transcript is ");
+		gln_normal_string(gln_transcript_stream ? "on" : "off");
+		gln_normal_string(".\n");
+	}
+
+	else {
+		gln_normal_string("Glk transcript can be ");
+		gln_standout_string("on");
+		gln_normal_string(", or ");
+		gln_standout_string("off");
+		gln_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gln_command_inputlog()
+ *
+ * Turn game input logging on and off.
+ */
+static void gln_command_inputlog(const char *argument) {
+	assert(argument);
+
+	if (gln_strcasecmp(argument, "on") == 0) {
+		frefid_t fileref;
+
+		if (gln_inputlog_stream) {
+			gln_normal_string("Glk input logging is already on.\n");
+			return;
+		}
+
+		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
+		          | fileusage_BinaryMode,
+		          filemode_WriteAppend, 0);
+		if (!fileref) {
+			gln_standout_string("Glk input logging failed.\n");
+			return;
+		}
+
+		gln_inputlog_stream = g_vm->glk_stream_open_file(fileref,
+		                      filemode_WriteAppend, 0);
+		g_vm->glk_fileref_destroy(fileref);
+		if (!gln_inputlog_stream) {
+			gln_standout_string("Glk input logging failed.\n");
+			return;
+		}
+
+		gln_normal_string("Glk input logging is now on.\n");
+	}
+
+	else if (gln_strcasecmp(argument, "off") == 0) {
+		if (!gln_inputlog_stream) {
+			gln_normal_string("Glk input logging is already off.\n");
+			return;
+		}
+
+		g_vm->glk_stream_close(gln_inputlog_stream, NULL);
+		gln_inputlog_stream = NULL;
+
+		gln_normal_string("Glk input log is now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gln_normal_string("Glk input logging is ");
+		gln_normal_string(gln_inputlog_stream ? "on" : "off");
+		gln_normal_string(".\n");
+	}
+
+	else {
+		gln_normal_string("Glk input logging can be ");
+		gln_standout_string("on");
+		gln_normal_string(", or ");
+		gln_standout_string("off");
+		gln_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gln_command_readlog()
+ *
+ * Set the game input log, to read input from a file.
+ */
+static void gln_command_readlog(const char *argument) {
+	assert(argument);
+
+	if (gln_strcasecmp(argument, "on") == 0) {
+		frefid_t fileref;
+
+		if (gln_readlog_stream) {
+			gln_normal_string("Glk read log is already on.\n");
+			return;
+		}
+
+		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
+		          | fileusage_BinaryMode,
+		          filemode_Read, 0);
+		if (!fileref) {
+			gln_standout_string("Glk read log failed.\n");
+			return;
+		}
+
+		if (!g_vm->glk_fileref_does_file_exist(fileref)) {
+			g_vm->glk_fileref_destroy(fileref);
+			gln_standout_string("Glk read log failed.\n");
+			return;
+		}
+
+		gln_readlog_stream = g_vm->glk_stream_open_file(fileref, filemode_Read, 0);
+		g_vm->glk_fileref_destroy(fileref);
+		if (!gln_readlog_stream) {
+			gln_standout_string("Glk read log failed.\n");
+			return;
+		}
+
+		gln_normal_string("Glk read log is now on.\n");
+	}
+
+	else if (gln_strcasecmp(argument, "off") == 0) {
+		if (!gln_readlog_stream) {
+			gln_normal_string("Glk read log is already off.\n");
+			return;
+		}
+
+		g_vm->glk_stream_close(gln_readlog_stream, NULL);
+		gln_readlog_stream = NULL;
+
+		gln_normal_string("Glk read log is now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gln_normal_string("Glk read log is ");
+		gln_normal_string(gln_readlog_stream ? "on" : "off");
+		gln_normal_string(".\n");
+	}
+
+	else {
+		gln_normal_string("Glk read log can be ");
+		gln_standout_string("on");
+		gln_normal_string(", or ");
+		gln_standout_string("off");
+		gln_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gln_command_abbreviations()
+ *
+ * Turn abbreviation expansions on and off.
+ */
+static void gln_command_abbreviations(const char *argument) {
+	assert(argument);
+
+	if (gln_strcasecmp(argument, "on") == 0) {
+		if (gln_abbreviations_enabled) {
+			gln_normal_string("Glk abbreviation expansions are already on.\n");
+			return;
+		}
+
+		gln_abbreviations_enabled = TRUE;
+		gln_normal_string("Glk abbreviation expansions are now on.\n");
+	}
+
+	else if (gln_strcasecmp(argument, "off") == 0) {
+		if (!gln_abbreviations_enabled) {
+			gln_normal_string("Glk abbreviation expansions are already off.\n");
+			return;
+		}
+
+		gln_abbreviations_enabled = FALSE;
+		gln_normal_string("Glk abbreviation expansions are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gln_normal_string("Glk abbreviation expansions are ");
+		gln_normal_string(gln_abbreviations_enabled ? "on" : "off");
+		gln_normal_string(".\n");
+	}
+
+	else {
+		gln_normal_string("Glk abbreviation expansions can be ");
+		gln_standout_string("on");
+		gln_normal_string(", or ");
+		gln_standout_string("off");
+		gln_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gln_command_graphics()
+ *
+ * Enable or disable graphics more permanently than is done by the main
+ * interpreter.  Also, print out a few brief details about the graphics
+ * state of the program.
+ */
+static void gln_command_graphics(const char *argument) {
+	assert(argument);
+
+	if (!gln_graphics_possible) {
+		gln_normal_string("Glk graphics are not available.\n");
+		return;
+	}
+
+	if (gln_strcasecmp(argument, "on") == 0) {
+		if (gln_graphics_enabled) {
+			gln_normal_string("Glk graphics are already on.\n");
+			return;
+		}
+
+		gln_graphics_enabled = TRUE;
+
+		/* If a picture is loaded, call the restart function to repaint it. */
+		if (gln_graphics_picture_is_available()) {
+			if (!gln_graphics_open()) {
+				gln_normal_string("Glk graphics error.\n");
+				return;
+			}
+			gln_graphics_restart();
+		}
+
+		gln_normal_string("Glk graphics are now on.\n");
+	}
+
+	else if (gln_strcasecmp(argument, "off") == 0) {
+		if (!gln_graphics_enabled) {
+			gln_normal_string("Glk graphics are already off.\n");
+			return;
+		}
+
+		/*
+		 * Set graphics to disabled, and stop any graphics processing.  Close
+		 * the graphics window.
+		 */
+		gln_graphics_enabled = FALSE;
+		gln_graphics_stop();
+		gln_graphics_close();
+
+		gln_normal_string("Glk graphics are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gln_normal_string("Glk graphics are available,");
+		gln_normal_string(gln_graphics_enabled
+		                  ? " and enabled.\n" : " but disabled.\n");
+
+		if (gln_graphics_picture_is_available()) {
+			int width, height;
+
+			if (gln_graphics_get_picture_details(&width, &height)) {
+				char buffer[16];
+
+				gln_normal_string("There is a picture loaded, ");
+
+				sprintf(buffer, "%d", width);
+				gln_normal_string(buffer);
+				gln_normal_string(" by ");
+
+				sprintf(buffer, "%d", height);
+				gln_normal_string(buffer);
+
+				gln_normal_string(" pixels.\n");
+			}
+		}
+
+		if (!gln_graphics_interpreter_enabled())
+			gln_normal_string("Interpreter graphics are disabled.\n");
+
+		if (gln_graphics_enabled && gln_graphics_are_displayed()) {
+			const char *bitmap_type;
+			int color_count, is_active;
+
+			if (gln_graphics_get_rendering_details(&bitmap_type,
+			                                       &color_count, &is_active)) {
+				char buffer[16];
+
+				gln_normal_string("Graphics are ");
+				gln_normal_string(is_active ? "active, " : "displayed, ");
+
+				sprintf(buffer, "%d", color_count);
+				gln_normal_string(buffer);
+				gln_normal_string(" colours");
+
+				if (bitmap_type) {
+					gln_normal_string(", ");
+					gln_normal_string(bitmap_type);
+					gln_normal_string(" bitmaps");
+				}
+				gln_normal_string(".\n");
+			} else
+				gln_normal_string("Graphics are being displayed.\n");
+		}
+
+		if (gln_graphics_enabled && !gln_graphics_are_displayed())
+			gln_normal_string("Graphics are not being displayed.\n");
+	}
+
+	else {
+		gln_normal_string("Glk graphics can be ");
+		gln_standout_string("on");
+		gln_normal_string(", or ");
+		gln_standout_string("off");
+		gln_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gln_command_loopchecks()
+ *
+ * Turn loop checking (for game infinite loops) on and off.
+ */
+static void gln_command_loopchecks(const char *argument) {
+	assert(argument);
+
+	if (gln_strcasecmp(argument, "on") == 0) {
+		if (gln_loopcheck_enabled) {
+			gln_normal_string("Glk loop detection is already on.\n");
+			return;
+		}
+
+		gln_loopcheck_enabled = TRUE;
+		gln_normal_string("Glk loop detection is now on.\n");
+	}
+
+	else if (gln_strcasecmp(argument, "off") == 0) {
+		if (!gln_loopcheck_enabled) {
+			gln_normal_string("Glk loop detection is already off.\n");
+			return;
+		}
+
+		gln_loopcheck_enabled = FALSE;
+		gln_normal_string("Glk loop detection is now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gln_normal_string("Glk loop detection is ");
+		gln_normal_string(gln_loopcheck_enabled ? "on" : "off");
+		gln_normal_string(".\n");
+	}
+
+	else {
+		gln_normal_string("Glk loop detection can be ");
+		gln_standout_string("on");
+		gln_normal_string(", or ");
+		gln_standout_string("off");
+		gln_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gln_command_locals()
+ *
+ * Turn local interpretation of "quit" etc. on and off.
+ */
+static void gln_command_locals(const char *argument) {
+	assert(argument);
+
+	if (gln_strcasecmp(argument, "on") == 0) {
+		if (gln_intercept_enabled) {
+			gln_normal_string("Glk local commands are already on.\n");
+			return;
+		}
+
+		gln_intercept_enabled = TRUE;
+		gln_normal_string("Glk local commands are now on.\n");
+	}
+
+	else if (gln_strcasecmp(argument, "off") == 0) {
+		if (!gln_intercept_enabled) {
+			gln_normal_string("Glk local commands are already off.\n");
+			return;
+		}
+
+		gln_intercept_enabled = FALSE;
+		gln_normal_string("Glk local commands are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gln_normal_string("Glk local commands are ");
+		gln_normal_string(gln_intercept_enabled ? "on" : "off");
+		gln_normal_string(".\n");
+	}
+
+	else {
+		gln_normal_string("Glk local commands can be ");
+		gln_standout_string("on");
+		gln_normal_string(", or ");
+		gln_standout_string("off");
+		gln_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gln_command_prompts()
+ *
+ * Turn the extra "> " prompt output on and off.
+ */
+static void gln_command_prompts(const char *argument) {
+	assert(argument);
+
+	if (gln_strcasecmp(argument, "on") == 0) {
+		if (gln_prompt_enabled) {
+			gln_normal_string("Glk extra prompts are already on.\n");
+			return;
+		}
+
+		gln_prompt_enabled = TRUE;
+		gln_normal_string("Glk extra prompts are now on.\n");
+
+		/* Check for a game prompt to clear the flag. */
+		gln_game_prompted();
+	}
+
+	else if (gln_strcasecmp(argument, "off") == 0) {
+		if (!gln_prompt_enabled) {
+			gln_normal_string("Glk extra prompts are already off.\n");
+			return;
+		}
+
+		gln_prompt_enabled = FALSE;
+		gln_normal_string("Glk extra prompts are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gln_normal_string("Glk extra prompts are ");
+		gln_normal_string(gln_prompt_enabled ? "on" : "off");
+		gln_normal_string(".\n");
+	}
+
+	else {
+		gln_normal_string("Glk extra prompts can be ");
+		gln_standout_string("on");
+		gln_normal_string(", or ");
+		gln_standout_string("off");
+		gln_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gln_command_print_version_number()
+ * gln_command_version()
+ *
+ * Print out the Glk library version number.
+ */
+static void gln_command_print_version_number(glui32 version) {
+	char buffer[64];
+
+	sprintf(buffer, "%lu.%lu.%lu",
+	        (unsigned long) version >> 16,
+	        (unsigned long)(version >> 8) & 0xff,
+	        (unsigned long) version & 0xff);
+	gln_normal_string(buffer);
+}
+
+static void gln_command_version(const char *argument) {
+	glui32 version;
+	assert(argument);
+
+	gln_normal_string("This is version ");
+	gln_command_print_version_number(GLN_PORT_VERSION);
+	gln_normal_string(" of the Glk Level 9 port.\n");
+
+	version = g_vm->glk_gestalt(gestalt_Version, 0);
+	gln_normal_string("The Glk library version is ");
+	gln_command_print_version_number(version);
+	gln_normal_string(".\n");
+}
+
+
+/*
+ * gln_command_commands()
+ *
+ * Turn command escapes off.  Once off, there's no way to turn them back on.
+ * Commands must be on already to enter this function.
+ */
+static void gln_command_commands(const char *argument) {
+	assert(argument);
+
+	if (gln_strcasecmp(argument, "on") == 0) {
+		gln_normal_string("Glk commands are already on.\n");
+	}
+
+	else if (gln_strcasecmp(argument, "off") == 0) {
+		gln_commands_enabled = FALSE;
+		gln_normal_string("Glk commands are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gln_normal_string("Glk commands are ");
+		gln_normal_string(gln_commands_enabled ? "on" : "off");
+		gln_normal_string(".\n");
+	}
+
+	else {
+		gln_normal_string("Glk commands can be ");
+		gln_standout_string("on");
+		gln_normal_string(", or ");
+		gln_standout_string("off");
+		gln_normal_string(".\n");
+	}
+}
+
+
+/* Glk subcommands and handler functions. */
+typedef const struct {
+	const char *const command;                      /* Glk subcommand. */
+	void (* const handler)(const char *argument);   /* Subcommand handler. */
+	const int takes_argument;                       /* Argument flag. */
+} gln_command_t;
+typedef gln_command_t *gln_commandref_t;
+
+static void gln_command_summary(const char *argument);
+static void gln_command_help(const char *argument);
+
+static gln_command_t GLN_COMMAND_TABLE[] = {
+	{"summary",        gln_command_summary,        FALSE},
+	{"script",         gln_command_script,         TRUE},
+	{"inputlog",       gln_command_inputlog,       TRUE},
+	{"readlog",        gln_command_readlog,        TRUE},
+	{"abbreviations",  gln_command_abbreviations,  TRUE},
+	{"graphics",       gln_command_graphics,       TRUE},
+	{"loopchecks",     gln_command_loopchecks,     TRUE},
+	{"locals",         gln_command_locals,         TRUE},
+	{"prompts",        gln_command_prompts,        TRUE},
+	{"version",        gln_command_version,        FALSE},
+	{"commands",       gln_command_commands,       TRUE},
+	{"help",           gln_command_help,           TRUE},
+	{NULL, NULL, FALSE}
+};
+
+
+/*
+ * gln_command_summary()
+ *
+ * Report all current Glk settings.
+ */
+static void
+gln_command_summary(const char *argument) {
+	gln_commandref_t entry;
+	assert(argument);
+
+	/*
+	 * Call handlers that have status to report with an empty argument,
+	 * prompting each to print its current setting.
+	 */
+	for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
+		if (entry->handler == gln_command_summary
+		        || entry->handler == gln_command_help)
+			continue;
+
+		entry->handler("");
+	}
+}
+
+
+/*
+ * gln_command_help()
+ *
+ * Document the available Glk commands.
+ */
+static void
+gln_command_help(const char *command) {
+	gln_commandref_t entry, matched;
+	assert(command);
+
+	if (strlen(command) == 0) {
+		gln_normal_string("Glk commands are");
+		for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
+			gln_commandref_t next;
+
+			next = entry + 1;
+			gln_normal_string(next->command ? " " : " and ");
+			gln_standout_string(entry->command);
+			gln_normal_string(next->command ? "," : ".\n\n");
+		}
+
+		gln_normal_string("Glk commands may be abbreviated, as long as"
+		                  " the abbreviation is unambiguous.  Use ");
+		gln_standout_string("glk help");
+		gln_normal_string(" followed by a Glk command name for help on that"
+		                  " command.\n");
+		return;
+	}
+
+	matched = NULL;
+	for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
+		if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
+			if (matched) {
+				gln_normal_string("The Glk command ");
+				gln_standout_string(command);
+				gln_normal_string(" is ambiguous.  Try ");
+				gln_standout_string("glk help");
+				gln_normal_string(" for more information.\n");
+				return;
+			}
+			matched = entry;
+		}
+	}
+	if (!matched) {
+		gln_normal_string("The Glk command ");
+		gln_standout_string(command);
+		gln_normal_string(" is not valid.  Try ");
+		gln_standout_string("glk help");
+		gln_normal_string(" for more information.\n");
+		return;
+	}
+
+	if (matched->handler == gln_command_summary) {
+		gln_normal_string("Prints a summary of all the current Glk Level 9"
+		                  " settings.\n");
+	}
+
+	else if (matched->handler == gln_command_script) {
+		gln_normal_string("Logs the game's output to a file.\n\nUse ");
+		gln_standout_string("glk script on");
+		gln_normal_string(" to begin logging game output, and ");
+		gln_standout_string("glk script off");
+		gln_normal_string(" to end it.  Glk Level 9 will ask you for a file"
+		                  " when you turn scripts on.\n");
+	}
+
+	else if (matched->handler == gln_command_inputlog) {
+		gln_normal_string("Records the commands you type into a game.\n\nUse ");
+		gln_standout_string("glk inputlog on");
+		gln_normal_string(", to begin recording your commands, and ");
+		gln_standout_string("glk inputlog off");
+		gln_normal_string(" to turn off input logs.  You can play back"
+		                  " recorded commands into a game with the ");
+		gln_standout_string("glk readlog");
+		gln_normal_string(" command.\n");
+	}
+
+	else if (matched->handler == gln_command_readlog) {
+		gln_normal_string("Plays back commands recorded with ");
+		gln_standout_string("glk inputlog on");
+		gln_normal_string(".\n\nUse ");
+		gln_standout_string("glk readlog on");
+		gln_normal_string(".  Command play back stops at the end of the"
+		                  " file.  You can also play back commands from a"
+		                  " text file created using any standard editor.\n");
+	}
+
+	else if (matched->handler == gln_command_abbreviations) {
+		gln_normal_string("Controls abbreviation expansion.\n\nGlk Level 9"
+		                  " automatically expands several standard single"
+		                  " letter abbreviations for you; for example, \"x\""
+		                  " becomes \"examine\".  Use ");
+		gln_standout_string("glk abbreviations on");
+		gln_normal_string(" to turn this feature on, and ");
+		gln_standout_string("glk abbreviations off");
+		gln_normal_string(" to turn it off.  While the feature is on, you"
+		                  " can bypass abbreviation expansion for an"
+		                  " individual game command by prefixing it with a"
+		                  " single quote.\n");
+	}
+
+	else if (matched->handler == gln_command_graphics) {
+		gln_normal_string("Turns interpreter graphics on and off.\n\nUse ");
+		gln_standout_string("glk graphics on");
+		gln_normal_string(" to enable interpreter graphics, and ");
+		gln_standout_string("glk graphics off");
+		gln_normal_string(" to turn graphics off and close the graphics window."
+		                  "  This control works slightly differently to the"
+		                  " 'graphics' command in Level 9 games themselves; the"
+		                  " game's 'graphics' command may disable new images,"
+		                  " but leave old ones displayed.  For graphics to be"
+		                  " displayed, they must be turned on in both the game"
+		                  " and the interpreter.\n");
+	}
+
+	else if (matched->handler == gln_command_loopchecks) {
+		gln_normal_string("Controls game infinite loop monitoring.\n\n"
+		                  "Some Level 9 games can enter an infinite loop if they"
+		                  " have nothing better to do.  A game might do this"
+		                  " after it has ended, should you decline its offer"
+		                  " to rerun.  To avoid the need to kill the interpreter"
+		                  " completely if a game does this, Glk Level 9 monitors"
+		                  " a game's input and output, and offers the option to"
+		                  " end the program gracefully if a game is silent for"
+		                  " a few seconds.  Use ");
+		gln_standout_string("glk loopchecks on");
+		gln_normal_string(" to turn this feature on, and ");
+		gln_standout_string("glk loopchecks off");
+		gln_normal_string(" to turn it off.\n");
+	}
+
+	else if (matched->handler == gln_command_locals) {
+		gln_normal_string("Controls interception of selected game commands.\n\n"
+		                  "Some Level 9 games were written for cassette tape"
+		                  " based microprocessor systems, and the way in which"
+		                  " they save, restore, and restart games can reflect"
+		                  " this.  There is also often no straightforward way"
+		                  " to quit from a game.\n\nTo make playing a Level 9"
+		                  " game appear similar to other systems, Glk Level 9"
+		                  " will trap the commands 'quit', 'restart', 'save',"
+		                  " 'restore', and 'load' (a synonym for 'restore') and"
+		                  " handle them locally within the interpreter.  Use ");
+		gln_standout_string("glk locals on");
+		gln_normal_string(" to turn this feature on, and ");
+		gln_standout_string("glk locals off");
+		gln_normal_string(" to turn it off.\n");
+	}
+
+	else if (matched->handler == gln_command_prompts) {
+		gln_normal_string("Controls extra input prompting.\n\n"
+		                  "Glk Level 9 can issue a replacement '>' input"
+		                  " prompt if it detects that the game hasn't prompted"
+		                  " after, say, an empty input line.  Use ");
+		gln_standout_string("glk prompts on");
+		gln_normal_string(" to turn this feature on, and ");
+		gln_standout_string("glk prompts off");
+		gln_normal_string(" to turn it off.\n");
+	}
+
+	else if (matched->handler == gln_command_version) {
+		gln_normal_string("Prints the version numbers of the Glk library"
+		                  " and the Glk Level 9 port.\n");
+	}
+
+	else if (matched->handler == gln_command_commands) {
+		gln_normal_string("Turn off Glk commands.\n\nUse ");
+		gln_standout_string("glk commands off");
+		gln_normal_string(" to disable all Glk commands, including this one."
+		                  "  Once turned off, there is no way to turn Glk"
+		                  " commands back on while inside the game.\n");
+	}
+
+	else if (matched->handler == gln_command_help)
+		gln_command_help("");
+
+	else
+		gln_normal_string("There is no help available on that Glk command."
+		                  "  Sorry.\n");
+}
+
+
+/*
+ * gln_command_escape()
+ *
+ * This function is handed each input line.  If the line contains a specific
+ * Glk port command, handle it and return TRUE, otherwise return FALSE.
+ */
+static int gln_command_escape(const char *string) {
+	int posn;
+	char *string_copy, *command, *argument;
+	assert(string);
+
+	/*
+	 * Return FALSE if the string doesn't begin with the Glk command escape
+	 * introducer.
+	 */
+	posn = strspn(string, "\t ");
+	if (gln_strncasecmp(string + posn, "glk", strlen("glk")) != 0)
+		return FALSE;
+
+	/* Take a copy of the string, without any leading space or introducer. */
+	string_copy = (char *)gln_malloc(strlen(string + posn) + 1 - strlen("glk"));
+	strcpy(string_copy, string + posn + strlen("glk"));
+
+	/*
+	 * Find the subcommand; the first word in the string copy.  Find its end,
+	 * and ensure it terminates with a NUL.
+	 */
+	posn = strspn(string_copy, "\t ");
+	command = string_copy + posn;
+	posn += strcspn(string_copy + posn, "\t ");
+	if (string_copy[posn] != '\0')
+		string_copy[posn++] = '\0';
+
+	/*
+	 * Now find any argument data for the command, ensuring it too terminates
+	 * with a NUL.
+	 */
+	posn += strspn(string_copy + posn, "\t ");
+	argument = string_copy + posn;
+	posn += strcspn(string_copy + posn, "\t ");
+	string_copy[posn] = '\0';
+
+	/*
+	 * Try to handle the command and argument as a Glk subcommand.  If it
+	 * doesn't run unambiguously, print command usage.  Treat an empty command
+	 * as "help".
+	 */
+	if (strlen(command) > 0) {
+		gln_commandref_t entry, matched;
+		int matches;
+
+		/*
+		 * Search for the first unambiguous table command string matching
+		 * the command passed in.
+		 */
+		matches = 0;
+		matched = NULL;
+		for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
+			if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
+				matches++;
+				matched = entry;
+			}
+		}
+
+		/* If the match was unambiguous, call the command handler. */
+		if (matches == 1) {
+			gln_normal_char('\n');
+			matched->handler(argument);
+
+			if (!matched->takes_argument && strlen(argument) > 0) {
+				gln_normal_string("[The ");
+				gln_standout_string(matched->command);
+				gln_normal_string(" command ignores arguments.]\n");
+			}
+		}
+
+		/* No match, or the command was ambiguous. */
+		else {
+			gln_normal_string("\nThe Glk command ");
+			gln_standout_string(command);
+			gln_normal_string(" is ");
+			gln_normal_string(matches == 0 ? "not valid" : "ambiguous");
+			gln_normal_string(".  Try ");
+			gln_standout_string("glk help");
+			gln_normal_string(" for more information.\n");
+		}
+	} else {
+		gln_normal_char('\n');
+		gln_command_help("");
+	}
+
+	/* The string contained a Glk command; return TRUE. */
+	free(string_copy);
+	return TRUE;
+}
+
+
+/*
+ * gln_command_intercept()
+ *
+ * The Level 9 games handle the commands "quit" and "restart" oddly, and
+ * somewhat similarly.  Both prompt "Press SPACE to play again", and then
+ * ignore all characters except space.  This makes it especially hard to exit
+ * from a game without killing the interpreter process.  They also handle
+ * "restore" via an odd security mechanism which has no real place here (the
+ * base Level 9 interpreter sidesteps this with its "#restore" command, and
+ * has some bugs in "save").
+ *
+ * To try to improve these, here we'll catch and special case the input lines
+ * "quit", "save", "restore", and "restart".  "Load" is a synonym for
+ * "restore".
+ *
+ * On "quit" or "restart", the function sets the interpreter stop reason
+ * code, stops the current game run.  On "save" or "restore" it calls the
+ * appropriate internal interpreter function.
+ *
+ * The return value is TRUE if an intercepted command was found, otherwise
+ * FALSE.
+ */
+static int gln_command_intercept(char *string) {
+	int posn, result;
+	char *string_copy, *trailing;
+	assert(string);
+
+	result = FALSE;
+
+	/* Take a copy of the string, excluding any leading whitespace. */
+	posn = strspn(string, "\t ");
+	string_copy = (char *)gln_malloc(strlen(string + posn) + 1);
+	strcpy(string_copy, string + posn);
+
+	/*
+	 * Find the space or NUL after the first word, and check that anything
+	 * after it the first word is whitespace only.
+	 */
+	posn = strcspn(string_copy, "\t ");
+	trailing = string_copy + posn;
+	if (trailing[strspn(trailing, "\t ")] == '\0') {
+		/* Terminate the string copy for easy comparisons. */
+		string_copy[posn] = '\0';
+
+		/* If this command was "quit", confirm, then call StopGame(). */
+		if (gln_strcasecmp(string_copy, "quit") == 0) {
+			if (gln_confirm("\nDo you really want to stop? [Y or N] ")) {
+				gln_stop_reason = STOP_EXIT;
+				StopGame();
+			}
+			result = TRUE;
+		}
+
+		/* If this command was "restart", confirm, then call StopGame(). */
+		else if (gln_strcasecmp(string_copy, "restart") == 0) {
+			if (gln_confirm("\nDo you really want to restart? [Y or N] ")) {
+				gln_stop_reason = STOP_RESTART;
+				StopGame();
+			}
+			result = TRUE;
+		}
+
+		/* If this command was "save", simply call save(). */
+		else if (gln_strcasecmp(string_copy, "save") == 0) {
+			gln_standout_string("\nSaving using interpreter\n\n");
+			save();
+			result = TRUE;
+		}
+
+		/* If this command was "restore" or "load", call restore(). */
+		else if (gln_strcasecmp(string_copy, "restore") == 0
+		         || gln_strcasecmp(string_copy, "load") == 0) {
+			gln_standout_string("\nRestoring using interpreter\n\n");
+			restore();
+			result = TRUE;
+		}
+	}
+
+	free(string_copy);
+	return result;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port input functions                                           */
+/*---------------------------------------------------------------------*/
+
+/* Ctrl-C and Ctrl-U character constants. */
+static const char GLN_CONTROL_C = '\003',
+                  GLN_CONTROL_U = '\025';
+
+/*
+ * os_readchar() call count limit, after which we really read a character.
+ * Also, call count limit on os_stoplist calls, after which we poll for a
+ * character press to stop the listing, and a stoplist poll timeout.
+ */
+static const int GLN_READCHAR_LIMIT = 1024,
+                 GLN_STOPLIST_LIMIT = 10;
+static const glui32 GLN_STOPLIST_TIMEOUT = 50;
+
+/* Quote used to suppress abbreviation expansion and local commands. */
+static const char GLN_QUOTED_INPUT = '\'';
+
+
+/*
+ * Note of when the interpreter is in list output.  The last element of any
+ * list generally lacks a terminating newline, and unless we do something
+ * special with it, it'll look like a valid prompt to us.
+ */
+static int gln_inside_list = FALSE;
+
+
+/* Table of single-character command abbreviations. */
+typedef const struct {
+	const char abbreviation;       /* Abbreviation character. */
+	const char *const expansion;   /* Expansion string. */
+} gln_abbreviation_t;
+typedef gln_abbreviation_t *gln_abbreviationref_t;
+
+static gln_abbreviation_t GLN_ABBREVIATIONS[] = {
+	{'c', "close"},    {'g', "again"},  {'i', "inventory"},
+	{'k', "attack"},   {'l', "look"},   {'p', "open"},
+	{'q', "quit"},     {'r', "drop"},   {'t', "take"},
+	{'x', "examine"},  {'y', "yes"},    {'z', "wait"},
+	{'\0', NULL}
+};
+
+
+/*
+ * gln_expand_abbreviations()
+ *
+ * Expand a few common one-character abbreviations commonly found in other
+ * game systems, but not always normal in Level 9 games.
+ */
+static void gln_expand_abbreviations(char *buffer, int size) {
+	char *command, abbreviation;
+	const char *expansion;
+	gln_abbreviationref_t entry;
+	assert(buffer);
+
+	/* Ignore anything that isn't a single letter command. */
+	command = buffer + strspn(buffer, "\t ");
+	if (!(strlen(command) == 1
+	        || (strlen(command) > 1 && Common::isSpace(command[1]))))
+		return;
+
+	/* Scan the abbreviations table for a match. */
+	abbreviation = g_vm->glk_char_to_lower((unsigned char) command[0]);
+	expansion = NULL;
+	for (entry = GLN_ABBREVIATIONS; entry->expansion; entry++) {
+		if (entry->abbreviation == abbreviation) {
+			expansion = entry->expansion;
+			break;
+		}
+	}
+
+	/*
+	 * If a match found, check for a fit, then replace the character with the
+	 * expansion string.
+	 */
+	if (expansion) {
+		if (strlen(buffer) + strlen(expansion) - 1 >= (uint)size)


Commit: 8b0250cb09629c72aad6e23701b9fc40c8bc7a3a
    https://github.com/scummvm/scummvm/commit/8b0250cb09629c72aad6e23701b9fc40c8bc7a3a
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Janitorial

Changed paths:
    engines/glk/level9/level9_main.cpp
    engines/glk/level9/os_glk.cpp


diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 719d05b..7aa95a7 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -152,7 +152,7 @@ struct L9V1GameInfo {
 	L9BYTE dictVal1, dictVal2;
 	int dictStart, L9Ptrs[5], absData, msgStart, msgLen;
 };
-struct L9V1GameInfo L9V1Games[] = {
+const L9V1GameInfo L9V1Games[] = {
 	0x1a, 0x24, 301, 0x0000, -0x004b, 0x0080, -0x002b, 0x00d0, 0x03b0, 0x0f80, 0x4857, /* Colossal Adventure */
 	0x20, 0x3b, 283, -0x0583, 0x0000, -0x0508, -0x04e0, 0x0000, 0x0800, 0x1000, 0x39d1, /* Adventure Quest */
 	0x14, 0xff, 153, -0x00d6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0a20, 0x16bf, 0x420d, /* Dungeon Adventure */
@@ -164,7 +164,7 @@ int L9V1Game = -1;
 
 /* Prototypes */
 L9BOOL LoadGame2(char *filename, char *picname);
-int getlongcode(void);
+int getlongcode();
 L9BOOL GetWordV2(char *buff, int Word);
 L9BOOL GetWordV3(char *buff, int Word);
 void show_picture(int pic);
@@ -174,7 +174,7 @@ void show_picture(int pic);
 #define CODEFOLLOWFILE "c:\\temp\\level9.txt"
 FILE *f;
 L9UINT16 *cfvar, *cfvar2;
-char *codes[] = {
+const char *codes[] = {
 	"Goto",
 	"intgosub",
 	"intreturn",
@@ -208,7 +208,7 @@ char *codes[] = {
 	"ilins",
 	"ilins",
 };
-char *functions[] = {
+const char *functions[] = {
 	"calldriver",
 	"L9Random",
 	"save",
@@ -216,7 +216,7 @@ char *functions[] = {
 	"clearworkspace",
 	"clearstack"
 };
-char *drivercalls[] = {
+const char *drivercalls[] = {
 	"init",
 	"drivercalcchecksum",
 	"driveroswrch",
@@ -260,7 +260,7 @@ void initdict(L9BYTE *ptr) {
 	unpackcount = 8;
 }
 
-char getdictionarycode(void) {
+char getdictionarycode() {
 	if (unpackcount != 8) return unpackbuf[unpackcount++];
 	else {
 		/* unpackbytes */
@@ -287,7 +287,7 @@ int getdictionary(int d0) {
 	else return d0 + 0x61;
 }
 
-int getlongcode(void) {
+int getlongcode() {
 	int d0, d1;
 	d0 = getdictionarycode();
 	if (d0 == 0x10) {
@@ -626,7 +626,7 @@ void L9Allocate(L9BYTE **ptr, L9UINT32 Size) {
 	}
 }
 
-void FreeMemory(void) {
+void FreeMemory() {
 	if (startfile) {
 		free(startfile);
 		startfile = NULL;
@@ -1402,24 +1402,24 @@ L9BOOL intinitialise(const char *filename, char *picname) {
 	return TRUE;
 }
 
-L9BOOL checksumgamedata(void) {
+L9BOOL checksumgamedata() {
 	return calcchecksum(startdata, L9WORD(startdata) + 1) == 0;
 }
 
-L9UINT16 movewa5d0(void) {
+L9UINT16 movewa5d0() {
 	L9UINT16 ret = L9WORD(codeptr);
 	codeptr += 2;
 	return ret;
 }
 
-L9UINT16 getcon(void) {
+L9UINT16 getcon() {
 	if (code & 64) {
 		/* getconsmall */
 		return *codeptr++;
 	} else return movewa5d0();
 }
 
-L9BYTE *getaddr(void) {
+L9BYTE *getaddr() {
 	if (code & 0x20) {
 		/* getaddrshort */
 		signed char diff = *codeptr++;
@@ -1429,7 +1429,7 @@ L9BYTE *getaddr(void) {
 	}
 }
 
-L9UINT16 *getvar(void) {
+L9UINT16 *getvar() {
 #ifndef CODEFOLLOW
 	return workspace.vartable + *codeptr++;
 #else
@@ -1438,7 +1438,7 @@ L9UINT16 *getvar(void) {
 #endif
 }
 
-void Goto(void) {
+void Goto() {
 	L9BYTE *target = getaddr();
 	if (target == codeptr - 2)
 		Running = FALSE; /* Endless loop! */
@@ -1446,7 +1446,7 @@ void Goto(void) {
 		codeptr = target;
 }
 
-void intgosub(void) {
+void intgosub() {
 	L9BYTE *newcodeptr = getaddr();
 	if (workspace.stackptr == STACKSIZE) {
 		error("\rStack overflow error\r");
@@ -1457,7 +1457,7 @@ void intgosub(void) {
 	codeptr = newcodeptr;
 }
 
-void intreturn(void) {
+void intreturn() {
 	if (workspace.stackptr == 0) {
 		error("\rStack underflow error\r");
 		Running = FALSE;
@@ -1466,18 +1466,18 @@ void intreturn(void) {
 	codeptr = acodeptr + workspace.stack[--workspace.stackptr];
 }
 
-void printnumber(void) {
+void printnumber() {
 	printdecimald0(*getvar());
 }
 
-void messagec(void) {
+void messagec() {
 	if (L9GameType <= L9_V2)
 		printmessageV2(getcon());
 	else
 		printmessage(getcon());
 }
 
-void messagev(void) {
+void messagev() {
 	if (L9GameType <= L9_V2)
 		printmessageV2(*getvar());
 	else
@@ -1711,7 +1711,7 @@ void ramload(int i) {
 	memmove(workspace.vartable, ramsavearea + i, sizeof(SaveStruct));
 }
 
-void calldriver(void) {
+void calldriver() {
 	L9BYTE *a6 = list9startptr;
 	int d0 = *a6++;
 #ifdef CODEFOLLOW
@@ -1744,7 +1744,7 @@ void calldriver(void) {
 	} else driver(d0, a6);
 }
 
-void L9Random(void) {
+void L9Random() {
 #ifdef CODEFOLLOW
 	fprintf(f, " %d", randomseed);
 #endif
@@ -1755,7 +1755,7 @@ void L9Random(void) {
 #endif
 }
 
-void save(void) {
+void save() {
 	L9UINT16 checksum;
 	int i;
 #ifdef L9DEBUG
@@ -1804,7 +1804,7 @@ L9BOOL CheckFile(GameState *gs) {
 	return FALSE;
 }
 
-void NormalRestore(void) {
+void NormalRestore() {
 	GameState temp;
 	int Bytes;
 #ifdef L9DEBUG
@@ -1831,7 +1831,7 @@ void NormalRestore(void) {
 	} else printstring("\rUnable to restore game.\r");
 }
 
-void restore(void) {
+void restore() {
 	int Bytes;
 	GameState temp;
 	if (os_load_file((L9BYTE *) &temp, &Bytes, sizeof(GameState))) {
@@ -1851,7 +1851,7 @@ void restore(void) {
 	} else printstring("\rUnable to restore game.\r");
 }
 
-void playback(void) {
+void playback() {
 	if (scriptfile)
 		delete scriptfile;
 	scriptfile = os_open_script_file();
@@ -1924,7 +1924,7 @@ L9BOOL scriptinput(char *buffer, int size) {
 	return FALSE;
 }
 
-void clearworkspace(void) {
+void clearworkspace() {
 	memset(workspace.vartable, 0, sizeof(workspace.vartable));
 }
 
@@ -1933,7 +1933,7 @@ void ilins(int d0) {
 	Running = FALSE;
 }
 
-void function(void) {
+void function() {
 	int d0 = *codeptr++;
 #ifdef CODEFOLLOW
 	fprintf(f, " %s", d0 == 250 ? "printstr" : functions[d0 - 1]);
@@ -2014,7 +2014,7 @@ void findmsgequiv(int d7) {
 	} while (TRUE);
 }
 
-L9BOOL unpackword(void) {
+L9BOOL unpackword() {
 	L9BYTE *a3;
 
 	if (unpackd3 == 0x1b) return TRUE;
@@ -2055,7 +2055,7 @@ L9UINT32 readdecimal(char *buff) {
 	return atol(buff);
 }
 
-void checknumber(void) {
+void checknumber() {
 	if (*obuff >= 0x30 && *obuff < 0x3a) {
 		if (L9GameType == L9_V4) {
 			*list9ptr = 1;
@@ -2071,7 +2071,7 @@ void checknumber(void) {
 	}
 }
 
-void NextCheat(void) {
+void NextCheat() {
 	/* restore game status */
 	memmove(&workspace, &CheatWorkspace, sizeof(GameState));
 	codeptr = acodeptr + workspace.codeptr;
@@ -2083,7 +2083,7 @@ void NextCheat(void) {
 	}
 }
 
-void StartCheat(void) {
+void StartCheat() {
 	Cheating = TRUE;
 	CheatWord = 0;
 
@@ -2116,7 +2116,7 @@ L9BOOL GetWordV3(char *buff, int Word) {
 	return TRUE;
 }
 
-L9BOOL CheckHash(void) {
+L9BOOL CheckHash() {
 	if (scumm_stricmp(ibuff, "#cheat") == 0) StartCheat();
 	else if (scumm_stricmp(ibuff, "#save") == 0) {
 		save();
@@ -2171,7 +2171,7 @@ L9BOOL IsInputChar(char c) {
 	return Common::isAlnum(c);
 }
 
-L9BOOL corruptinginput(void) {
+L9BOOL corruptinginput() {
 	L9BYTE *a0, *a2, *a6;
 	int d0, d1, d2, keywordnumber, abrevword;
 	char *iptr;
@@ -2452,7 +2452,7 @@ L9BOOL inputV2(int *wordcount) {
 	}
 }
 
-void input(void) {
+void input() {
 	if (L9GameType == L9_V3 && FirstPicture >= 0) {
 		show_picture(FirstPicture);
 		FirstPicture = -1;
@@ -2476,7 +2476,7 @@ void input(void) {
 	} else if (corruptinginput()) codeptr += 5;
 }
 
-void varcon(void) {
+void varcon() {
 	L9UINT16 d6 = getcon();
 	*getvar() = d6;
 
@@ -2485,7 +2485,7 @@ void varcon(void) {
 #endif
 }
 
-void varvar(void) {
+void varvar() {
 	L9UINT16 d6 = *getvar();
 	*getvar() = d6;
 
@@ -2494,7 +2494,7 @@ void varvar(void) {
 #endif
 }
 
-void _add(void) {
+void _add() {
 	L9UINT16 d0 = *getvar();
 	*getvar() += d0;
 
@@ -2503,7 +2503,7 @@ void _add(void) {
 #endif
 }
 
-void _sub(void) {
+void _sub() {
 	L9UINT16 d0 = *getvar();
 	*getvar() -= d0;
 
@@ -2512,7 +2512,7 @@ void _sub(void) {
 #endif
 }
 
-void jump(void) {
+void jump() {
 	L9UINT16 d0 = L9WORD(codeptr);
 	L9BYTE *a0;
 	codeptr += 2;
@@ -2561,7 +2561,7 @@ notfn4:
 	*d5p = 0;
 }
 
-void Exit(void) {
+void Exit() {
 	L9BYTE d4, d5v;
 	L9BYTE d7 = (L9BYTE) * getvar();
 	L9BYTE d6 = (L9BYTE) * getvar();
@@ -2578,7 +2578,7 @@ void Exit(void) {
 #endif
 }
 
-void ifeqvt(void) {
+void ifeqvt() {
 	L9UINT16 d0 = *getvar();
 	L9UINT16 d1 = *getvar();
 	L9BYTE *a0 = getaddr();
@@ -2589,7 +2589,7 @@ void ifeqvt(void) {
 #endif
 }
 
-void ifnevt(void) {
+void ifnevt() {
 	L9UINT16 d0 = *getvar();
 	L9UINT16 d1 = *getvar();
 	L9BYTE *a0 = getaddr();
@@ -2600,7 +2600,7 @@ void ifnevt(void) {
 #endif
 }
 
-void ifltvt(void) {
+void ifltvt() {
 	L9UINT16 d0 = *getvar();
 	L9UINT16 d1 = *getvar();
 	L9BYTE *a0 = getaddr();
@@ -2611,7 +2611,7 @@ void ifltvt(void) {
 #endif
 }
 
-void ifgtvt(void) {
+void ifgtvt() {
 	L9UINT16 d0 = *getvar();
 	L9UINT16 d1 = *getvar();
 	L9BYTE *a0 = getaddr();
@@ -2630,7 +2630,7 @@ int scaley(int y) {
 	return (gfx_mode == GFX_V2) ? 127 - (y >> 7) : 95 - (((y >> 5) + (y >> 6)) >> 3);
 }
 
-void detect_gfx_mode(void) {
+void detect_gfx_mode() {
 	if (L9GameType == L9_V3) {
 		/* These V3 games use graphics logic similar to the V2 games */
 		if (strstr(FirstLine, "price of magik") != 0)
@@ -2659,7 +2659,7 @@ void detect_gfx_mode(void) {
 		gfx_mode = GFX_V2;
 }
 
-void _screen(void) {
+void _screen() {
 	int mode = 0;
 
 	if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
@@ -2699,7 +2699,7 @@ void _screen(void) {
 	/* screent */
 }
 
-void cleartg(void) {
+void cleartg() {
 	int d0 = *codeptr++;
 #ifdef L9DEBUG
 	printf("cleartg %s", d0 ? "graphics" : "text");
@@ -2970,7 +2970,7 @@ void reflect(int d7) {
 	reflectflag = d7;
 }
 
-void notimp(void) {
+void notimp() {
 #ifdef L9DEBUG
 	printf("gfx - notimp");
 #endif
@@ -3006,7 +3006,7 @@ void opt(L9BYTE **a5) {
 	option = d0;
 }
 
-void restorescale(void) {
+void restorescale() {
 #ifdef L9DEBUG
 	printf("gfx - restorescale");
 #endif
@@ -3139,7 +3139,7 @@ void show_picture(int pic) {
 	}
 }
 
-void picture(void) {
+void picture() {
 	show_picture(*getvar());
 }
 
@@ -3157,7 +3157,7 @@ void GetPictureSize(int *width, int *height) {
 	}
 }
 
-L9BOOL RunGraphics(void) {
+L9BOOL RunGraphics() {
 	if (gfxa5) {
 		if (!getinstruction(&gfxa5))
 			gfxa5 = NULL;
@@ -3166,14 +3166,14 @@ L9BOOL RunGraphics(void) {
 	return FALSE;
 }
 
-void initgetobj(void) {
+void initgetobj() {
 	int i;
 	numobjectfound = 0;
 	object = 0;
 	for (i = 0; i < 32; i++) gnoscratch[i] = 0;
 }
 
-void getnextobject(void) {
+void getnextobject() {
 	int d2, d3, d4;
 	L9UINT16 *hisearchposvar, *searchposvar;
 
@@ -3259,7 +3259,7 @@ void getnextobject(void) {
 	*getvar() = searchdepth;
 }
 
-void ifeqct(void) {
+void ifeqct() {
 	L9UINT16 d0 = *getvar();
 	L9UINT16 d1 = getcon();
 	L9BYTE *a0 = getaddr();
@@ -3269,7 +3269,7 @@ void ifeqct(void) {
 #endif
 }
 
-void ifnect(void) {
+void ifnect() {
 	L9UINT16 d0 = *getvar();
 	L9UINT16 d1 = getcon();
 	L9BYTE *a0 = getaddr();
@@ -3279,7 +3279,7 @@ void ifnect(void) {
 #endif
 }
 
-void ifltct(void) {
+void ifltct() {
 	L9UINT16 d0 = *getvar();
 	L9UINT16 d1 = getcon();
 	L9BYTE *a0 = getaddr();
@@ -3289,7 +3289,7 @@ void ifltct(void) {
 #endif
 }
 
-void ifgtct(void) {
+void ifgtct() {
 	L9UINT16 d0 = *getvar();
 	L9UINT16 d1 = getcon();
 	L9BYTE *a0 = getaddr();
@@ -3299,7 +3299,7 @@ void ifgtct(void) {
 #endif
 }
 
-void printinput(void) {
+void printinput() {
 	L9BYTE *ptr = (L9BYTE *) obuff;
 	char c;
 	while ((c = *ptr++) != ' ') printchar(c);
@@ -3309,7 +3309,7 @@ void printinput(void) {
 #endif
 }
 
-void listhandler(void) {
+void listhandler() {
 	L9BYTE *a4, *MinAccess, *MaxAccess;
 	L9UINT16 val;
 	L9UINT16 *var;
@@ -3409,7 +3409,7 @@ void listhandler(void) {
 	}
 }
 
-void executeinstruction(void) {
+void executeinstruction() {
 #ifdef CODEFOLLOW
 	f = fopen(CODEFOLLOWFILE, "a");
 	fprintf(f, "%ld (s:%d) %x", (L9UINT32)(codeptr - acodeptr) - 1, workspace.stackptr, code);
@@ -3560,11 +3560,11 @@ L9BOOL LoadGame(const char *filename, char *picname) {
 }
 
 /* can be called from input to cause fall through for exit */
-void StopGame(void) {
+void StopGame() {
 	Running = FALSE;
 }
 
-L9BOOL RunGame(void) {
+L9BOOL RunGame() {
 	code = *codeptr++;
 	/*  printf("%d",code); */
 	executeinstruction();
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index ef5499c..be63ae5 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -95,8 +95,8 @@ typedef L9BYTE gln_byte;
 typedef L9UINT16 gln_uint16;
 typedef L9UINT32 gln_uint32;
 
-extern void save(void);
-extern void restore(void);
+extern void save();
+extern void restore();
 extern gln_bool Cheating;
 extern gln_byte *startdata;
 extern gln_uint32 FileSize;
@@ -106,7 +106,7 @@ static void gln_event_wait(glui32 wait_type, event_t *event);
 static void gln_event_wait_2(glui32 wait_type_1,
                              glui32 wait_type_2, event_t *event);
 
-static void gln_watchdog_tick(void);
+static void gln_watchdog_tick();
 static void gln_standout_string(const char *message);
 
 static int gln_confirm(const char *prompt);
@@ -155,8 +155,7 @@ static void gln_fatal(const char *string) {
  * Non-failing malloc and realloc; call gln_fatal and exit if memory
  * allocation fails.
  */
-static void *
-gln_malloc(size_t size) {
+static void *gln_malloc(size_t size) {
 	void *pointer;
 
 	pointer = malloc(size);
@@ -168,8 +167,7 @@ gln_malloc(size_t size) {
 	return pointer;
 }
 
-static void *
-gln_realloc(void *ptr, size_t size) {
+static void *gln_realloc(void *ptr, size_t size) {
 	void *pointer;
 
 	pointer = realloc(ptr, size);
@@ -192,8 +190,7 @@ gln_realloc(void *ptr, size_t size) {
  * They're global here so that the core interpreter can use them; otherwise
  * it tries to use the non-ANSI str[n]icmp() functions.
  */
-int
-gln_strncasecmp(const char *s1, const char *s2, size_t n) {
+int gln_strncasecmp(const char *s1, const char *s2, size_t n) {
 	size_t index;
 
 	for (index = 0; index < n; index++) {
@@ -207,8 +204,7 @@ gln_strncasecmp(const char *s1, const char *s2, size_t n) {
 	return 0;
 }
 
-int
-gln_strcasecmp(const char *s1, const char *s2) {
+int gln_strcasecmp(const char *s1, const char *s2) {
 	size_t s1len, s2len;
 	int result;
 
@@ -222,47 +218,6 @@ gln_strcasecmp(const char *s1, const char *s2) {
 		return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
 }
 
-
-/*---------------------------------------------------------------------*/
-/*  Glk port stub graphics functions                                   */
-/*---------------------------------------------------------------------*/
-
-/*
- * If we're working with a very stripped down, old, or lazy Glk library
- * that neither offers Glk graphics nor graphics stubs functions, here
- * we define our own stubs, to avoid link-time errors.
- */
-#ifndef GLK_MODULE_IMAGE
-static glui32
-g_vm->glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2) {
-	return FALSE;
-}
-static glui32
-g_vm->glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2,
-                            glui32 width, glui32 height) {
-	return FALSE;
-}
-static glui32
-g_vm->glk_image_get_info(glui32 image, glui32 *width, glui32 *height) {
-	return FALSE;
-}
-static void
-g_vm->glk_window_flow_break(winid_t win) {
-}
-static void
-g_vm->glk_window_erase_rect(winid_t win, glsi32 left, glsi32 top,
-                            glui32 width, glui32 height) {
-}
-static void
-g_vm->glk_window_fill_rect(winid_t win, glui32 color, glsi32 left, glsi32 top,
-                           glui32 width, glui32 height) {
-}
-static void
-g_vm->glk_window_set_background_color(winid_t win, glui32 color) {
-}
-#endif
-
-
 /*---------------------------------------------------------------------*/
 /*  Glk port CRC functions                                             */
 /*---------------------------------------------------------------------*/
@@ -340,15 +295,15 @@ static const char *gln_gameid_game_name = NULL;
  *
  * The version of l9data_d.h used is 050 (22 Oct 2002).
  */
-typedef const struct {
+struct gln_game_table_t {
 	const gln_uint16 length;    /* Datafile length in bytes */
 	const gln_byte checksum;    /* 8-bit checksum, last datafile byte */
 	const gln_uint16 crc;       /* 16-bit CRC, L9cut-internal */
 	const char *const name;     /* Game title and platform */
-} gln_game_table_t;
-typedef gln_game_table_t *gln_game_tableref_t;
+};
+typedef const gln_game_table_t *gln_game_tableref_t;
 
-static gln_game_table_t GLN_GAME_TABLE[] = {
+static const gln_game_table_t GLN_GAME_TABLE[] = {
 	{0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
 
 	{0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
@@ -763,16 +718,16 @@ static gln_game_table_t GLN_GAME_TABLE[] = {
  *
  * The version of l9data_p.h used is 012 (22 May 2001).
  */
-typedef const struct {
+struct gln_patch_table_t {
 	const gln_uint16 length;        /* Datafile length in bytes */
 	const gln_byte orig_checksum;   /* 8-bit checksum, last datafile byte */
 	const gln_uint16 orig_crc;      /* 16-bit CRC, L9cut-internal */
 	const gln_byte patch_checksum;  /* 8-bit checksum, last datafile byte */
 	const gln_uint16 patch_crc;     /* 16-bit CRC, L9cut-internal */
-} gln_patch_table_t;
-typedef gln_patch_table_t *gln_patch_tableref_t;
+};
+typedef const gln_patch_table_t *gln_patch_tableref_t;
 
-static gln_patch_table_t GLN_PATCH_TABLE[] = {
+static const gln_patch_table_t GLN_PATCH_TABLE[] = {
 	/* Price of Magik (Spectrum128) */
 	{0x7410, 0x5e, 0x60be, 0x70, 0x6cef},
 
@@ -1132,28 +1087,26 @@ static gln_patch_table_t GLN_PATCH_TABLE[] = {
  * Look up and return game table and patch table entries given a game's
  * length, checksum, and CRC.  Returns the entry, or NULL if not found.
  */
-static gln_game_tableref_t
-gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
-                       gln_uint16 crc, int ignore_crc) {
+static gln_game_tableref_t gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
+		gln_uint16 crc, int ignore_crc) {
 	gln_game_tableref_t game;
 
 	for (game = GLN_GAME_TABLE; game->length; game++) {
 		if (game->length == length && game->checksum == checksum
-		        && (ignore_crc || game->crc == crc))
+				&& (ignore_crc || game->crc == crc))
 			break;
 	}
 
 	return game->length ? game : NULL;
 }
 
-static gln_patch_tableref_t
-gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
-                        gln_uint16 crc) {
+static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
+		gln_uint16 crc) {
 	gln_patch_tableref_t patch;
 
 	for (patch = GLN_PATCH_TABLE; patch->length; patch++) {
 		if (patch->length == length && patch->patch_checksum == checksum
-		        && patch->patch_crc == crc)
+				&& patch->patch_crc == crc)
 			break;
 	}
 
@@ -1170,8 +1123,7 @@ gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
  * This function uses startdata and FileSize from the core interpreter.
  * These aren't advertised symbols, so be warned.
  */
-static gln_game_tableref_t
-gln_gameid_identify_game(void) {
+static gln_game_tableref_t gln_gameid_identify_game() {
 	gln_uint16 length, crc;
 	gln_byte checksum;
 	int is_version2;
@@ -1242,8 +1194,7 @@ gln_gameid_identify_game(void) {
  * This function uses startdata from the core interpreter.  This isn't an
  * advertised symbol, so be warned.
  */
-static const char *
-gln_gameid_get_game_name(void) {
+static const char *gln_gameid_get_game_name() {
 	/*
 	 * If no game name yet known, attempt to identify the game.  If it can't
 	 * be identified, set the cached game name to "" -- this special value
@@ -1278,8 +1229,7 @@ gln_gameid_get_game_name(void) {
  * function should be called by actions that may cause the interpreter to
  * change game file, for example os_set_filenumber().
  */
-static void
-gln_gameid_game_name_reset(void) {
+static void gln_gameid_game_name_reset() {
 	gln_gameid_game_name = NULL;
 }
 
@@ -1289,9 +1239,9 @@ gln_gameid_game_name_reset(void) {
 /*---------------------------------------------------------------------*/
 
 /* R,G,B color triple definition. */
-typedef struct {
+struct gln_rgb_t {
 	int red, green, blue;
-} gln_rgb_t;
+};
 typedef gln_rgb_t *gln_rgbref_t;
 
 /*
@@ -1417,7 +1367,7 @@ static int gln_graphics_color_count = GLN_PALETTE_SIZE;
  * If it's not open, open the graphics window.  Returns TRUE if graphics
  * was successfully started, or already on.
  */
-static int gln_graphics_open(void) {
+static int gln_graphics_open() {
 	if (!gln_graphics_window) {
 		gln_graphics_window = g_vm->glk_window_open(gln_main_window,
 		                      winmethod_Above
@@ -1435,7 +1385,7 @@ static int gln_graphics_open(void) {
  *
  * If open, close the graphics window and set back to NULL.
  */
-static void gln_graphics_close(void) {
+static void gln_graphics_close() {
 	if (gln_graphics_window) {
 		g_vm->glk_window_close(gln_graphics_window, NULL);
 		gln_graphics_window = NULL;
@@ -1448,7 +1398,7 @@ static void gln_graphics_close(void) {
  *
  * If graphics enabled, start any background picture update processing.
  */
-static void gln_graphics_start(void) {
+static void gln_graphics_start() {
 	if (gln_graphics_enabled) {
 		/* If not running, start the updating "thread". */
 		if (!gln_graphics_active) {
@@ -1464,7 +1414,7 @@ static void gln_graphics_start(void) {
  *
  * Stop any background picture update processing.
  */
-static void gln_graphics_stop(void) {
+static void gln_graphics_stop() {
 	/* If running, stop the updating "thread". */
 	if (gln_graphics_active) {
 		g_vm->glk_request_timer_events(0);
@@ -1478,7 +1428,7 @@ static void gln_graphics_stop(void) {
  *
  * Return TRUE if graphics are currently being displayed, FALSE otherwise.
  */
-static int gln_graphics_are_displayed(void) {
+static int gln_graphics_are_displayed() {
 	return gln_graphics_window != NULL;
 }
 
@@ -1490,7 +1440,7 @@ static int gln_graphics_are_displayed(void) {
  * This function should be called on the appropriate Glk window resize and
  * arrange events.
  */
-static void gln_graphics_paint(void) {
+static void gln_graphics_paint() {
 	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
 		/* Set the repaint flag, and start graphics. */
 		gln_graphics_repaint = TRUE;
@@ -1506,7 +1456,7 @@ static void gln_graphics_paint(void) {
  * function should be called whenever graphics is re-enabled after being
  * disabled.
  */
-static void gln_graphics_restart(void) {
+static void gln_graphics_restart() {
 	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
 		/* Set the new picture flag, and start graphics. */
 		gln_graphics_new_picture = TRUE;
@@ -1521,8 +1471,7 @@ static void gln_graphics_restart(void) {
  * Analyze an image, and return an overall count of how many colors out of
  * the palette are used.
  */
-static int gln_graphics_count_colors(gln_byte bitmap[],
-                                     gln_uint16 width, gln_uint16 height) {
+static int gln_graphics_count_colors(gln_byte bitmap[], gln_uint16 width, gln_uint16 height) {
 	int x, y, count;
 	long usage[GLN_PALETTE_SIZE], index_row;
 	assert(bitmap);
@@ -1583,10 +1532,8 @@ static glui32 gln_graphics_combine_color(gln_rgbref_t rgb_color) {
  * picture is going to be rendered.  This attempts a small raised effect
  * for the picture, in keeping with modern trends.
  */
-static void gln_graphics_clear_and_border(winid_t glk_window,
-        int x_offset, int y_offset,
-        int pixel_size, gln_uint16 width,
-        gln_uint16 height) {
+static void gln_graphics_clear_and_border(winid_t glk_window, int x_offset, int y_offset,
+		int pixel_size, gln_uint16 width, gln_uint16 height) {
 	uint background;
 	glui32 fade_color, shading_color;
 	gln_rgb_t rgb_background, rgb_border, rgb_fade;
@@ -1620,7 +1567,7 @@ static void gln_graphics_clear_and_border(winid_t glk_window,
 	 * do any shading.  Failing this check is probably highly unlikely.
 	 */
 	if (width < 2 * GLN_GRAPHICS_SHADE_STEPS
-	        || height < 2 * GLN_GRAPHICS_SHADE_STEPS) {
+			|| height < 2 * GLN_GRAPHICS_SHADE_STEPS) {
 		/* Paint a rectangle bigger than the picture by border pixels. */
 		g_vm->glk_window_fill_rect(glk_window,
 		                           GLN_GRAPHICS_BORDER_COLOR,
@@ -1719,8 +1666,7 @@ static void gln_graphics_convert_palette(Colour ln_palette[], glui32 glk_palette
  * this picture in the current graphics window.
  */
 static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
-        gln_uint16 width, gln_uint16 height,
-        int *x_offset, int *y_offset) {
+        gln_uint16 width, gln_uint16 height, int *x_offset, int *y_offset) {
 	uint window_width, window_height;
 	assert(glk_window && x_offset && y_offset);
 
@@ -1749,8 +1695,8 @@ static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
  * the most complex shapes first, we help to minimize the number of fill
  * regions needed to render the complete picture.
  */
-static int gln_graphics_is_vertex(gln_byte off_screen[],
-                                  gln_uint16 width, gln_uint16 height, int x, int y) {
+static int gln_graphics_is_vertex(gln_byte off_screen[], gln_uint16 width, gln_uint16 height,
+		int x, int y) {
 	gln_byte pixel;
 	int above, below, left, right;
 	long index_row;
@@ -1834,8 +1780,7 @@ static int gln_graphics_compare_layering_inverted(const void *void_first,
 }
 
 static void gln_graphics_assign_layers(gln_byte off_screen[], gln_byte on_screen[],
-                                       gln_uint16 width, gln_uint16 height,
-                                       int layers[], long layer_usage[]) {
+		gln_uint16 width, gln_uint16 height, int layers[], long layer_usage[]) {
 	int index, x, y;
 	long index_row;
 	gln_layering_t layering[GLN_PALETTE_SIZE];
@@ -1919,9 +1864,8 @@ static void gln_graphics_assign_layers(gln_byte off_screen[], gln_byte on_screen
  * given point as validated.
  */
 static void gln_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
-                                      gln_byte off_screen[], gln_byte on_screen[],
-                                      int x, int y, int x_offset, int y_offset,
-                                      int pixel_size, gln_uint16 width, gln_uint16 height) {
+		gln_byte off_screen[], gln_byte on_screen[], int x, int y, int x_offset, int y_offset,
+		int pixel_size, gln_uint16 width, gln_uint16 height) {
 	gln_byte pixel;
 	int layer, x_min, x_max, y_min, y_max, x_index, y_index;
 	long index_row;
@@ -2023,11 +1967,8 @@ break_y_max:
 	}
 }
 
-static void gln_graphics_paint_everything(winid_t glk_window,
-        glui32 palette[],
-        gln_byte off_screen[],
-        int x_offset, int y_offset,
-        gln_uint16 width, gln_uint16 height) {
+static void gln_graphics_paint_everything(winid_t glk_window, glui32 palette[],
+		gln_byte off_screen[], int x_offset, int y_offset, gln_uint16 width, gln_uint16 height) {
 	gln_byte        pixel;          /* Reference pixel color */
 	int     x, y;
 
@@ -2060,7 +2001,7 @@ static void gln_graphics_paint_everything(winid_t glk_window,
  * plots required, as only the differences between the two buffers need
  * to be rendered.
  */
-static void gln_graphics_timeout(void) {
+static void gln_graphics_timeout() {
 	static glui32 palette[GLN_PALETTE_SIZE];   /* Precomputed Glk palette */
 
 	static int deferred_repaint = FALSE;       /* Local delayed repaint flag */
@@ -2361,7 +2302,7 @@ static void gln_graphics_locate_bitmaps(const char *gamefile) {
  * Here, then, we try to delay until the picture has rendered, allowing the
  * delay to be broken with a keypress.
  */
-static void gln_graphics_handle_title_picture(void) {
+static void gln_graphics_handle_title_picture() {
 	event_t event;
 	int count;
 
@@ -2501,7 +2442,7 @@ void os_show_bitmap(int picture, int x, int y) {
  * Return TRUE if the graphics module data is loaded with a usable picture,
  * FALSE if there is no picture available to display.
  */
-static int gln_graphics_picture_is_available(void) {
+static int gln_graphics_picture_is_available() {
 	return gln_graphics_bitmap != NULL;
 }
 
@@ -2542,7 +2483,7 @@ static int gln_graphics_get_picture_details(int *width, int *height) {
  * values are returned.
  */
 static int gln_graphics_get_rendering_details(const char **bitmap_type,
-        int *color_count, int *is_active) {
+		int *color_count, int *is_active) {
 	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
 		/*
 		 * Convert the detected bitmap type into a string and return it.
@@ -2613,7 +2554,7 @@ static int gln_graphics_get_rendering_details(const char **bitmap_type,
  * Return TRUE if it looks like interpreter graphics are turned on, FALSE
  * otherwise.
  */
-static int gln_graphics_interpreter_enabled(void) {
+static int gln_graphics_interpreter_enabled() {
 	return gln_graphics_interpreter_state != GLN_GRAPHICS_OFF;
 }
 
@@ -2624,7 +2565,7 @@ static int gln_graphics_interpreter_enabled(void) {
  * Free memory resources allocated by graphics functions.  Called on game
  * end.
  */
-static void gln_graphics_cleanup(void) {
+static void gln_graphics_cleanup() {
 	free(gln_graphics_bitmap);
 	gln_graphics_bitmap = NULL;
 	free(gln_graphics_off_screen);
@@ -2682,7 +2623,7 @@ static int gln_linegraphics_fill_segments_allocation = 0,
  * Initialize a new constructed bitmap graphics context for line drawn
  * graphics.
  */
-static void gln_linegraphics_create_context(void) {
+static void gln_linegraphics_create_context() {
 	int width, height;
 	long picture_bytes;
 
@@ -2712,7 +2653,7 @@ static void gln_linegraphics_create_context(void) {
  * Clear the complete graphical drawing area, setting all pixels to zero,
  * and resetting the palette to all black as well.
  */
-static void gln_linegraphics_clear_context(void) {
+static void gln_linegraphics_clear_context() {
 	long picture_bytes;
 
 	/* Get the picture size, and zero all bytes in the bitmap. */
@@ -2786,7 +2727,7 @@ static void gln_linegraphics_plot_clip(int x, int y, int colour1, int colour2) {
 }
 
 static void gln_linegraphics_draw_line_if(int x1, int y1, int x2, int y2,
-        int colour1, int colour2) {
+		int colour1, int colour2) {
 	int x, y, dx, dy, incx, incy, balance;
 
 	/* Ignore any odd request where there will be no colour changes. */
@@ -3001,7 +2942,7 @@ skip:
  * Interpreter entry points for line drawing graphics.  All calls to these
  * are ignored if line drawing mode is not set.
  */
-void os_cleargraphics(void) {
+void os_cleargraphics() {
 	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
 		gln_linegraphics_clear_context();
 }
@@ -3028,7 +2969,7 @@ void os_fill(int x, int y, int colour1, int colour2) {
  * Process as many graphics opcodes as are available, constructing the
  * resulting image as a bitmap.  When complete, treat as normal bitmaps.
  */
-static void gln_linegraphics_process(void) {
+static void gln_linegraphics_process() {
 	/*
 	 * If interpreter graphics are not set to line mode, ignore any call that
 	 * arrives here.
@@ -3063,7 +3004,7 @@ static void gln_linegraphics_process(void) {
  * Free memory resources allocated by line graphics functions.  Called on
  * game end.
  */
-static void gln_linegraphics_cleanup(void) {
+static void gln_linegraphics_cleanup() {
 	free(gln_linegraphics_fill_segments);
 	gln_linegraphics_fill_segments = NULL;
 
@@ -3237,7 +3178,7 @@ static void gln_watchdog_start(int timeout, int period) {
 	gln_watchdog_monitor = g_system->getMillis();
 }
 
-static void gln_watchdog_stop(void) {
+static void gln_watchdog_stop() {
 	gln_watchdog_timeout_secs = 0;
 }
 
@@ -3251,7 +3192,7 @@ static void gln_watchdog_stop(void) {
  * returns to the interpreter, as a means of timing how long the interpreter
  * dwells in running game code.
  */
-static void gln_watchdog_tick(void) {
+static void gln_watchdog_tick() {
 	gln_watchdog_monitor = g_system->getMillis();
 }
 
@@ -3266,7 +3207,7 @@ static void gln_watchdog_tick(void) {
  * This function only checks every N calls; it's called extremely frequently
  * from opcode handling, and will thrash in time() if it checks on each call.
  */
-static int gln_watchdog_has_timed_out(void) {
+static int gln_watchdog_has_timed_out() {
 	/* If loop detection is off or the timeout is set to zero, do nothing. */
 	if (gln_loopcheck_enabled && gln_watchdog_timeout_secs > 0) {
 		time_t now;
@@ -3335,7 +3276,7 @@ static const int GLN_DEFAULT_STATUS_WIDTH = 74;
  * the current game identity string, or a default string if no game identity
  * could be established.
  */
-static void gln_status_update(void) {
+static void gln_status_update() {
 	uint width, height;
 	assert(gln_status_window);
 
@@ -3370,7 +3311,7 @@ static void gln_status_update(void) {
  * to detect changes of game identity string, and gambles a little on the
  * belief that two games' strings won't have the same CRC.
  */
-static void gln_status_print(void) {
+static void gln_status_print() {
 	static int is_initialized = FALSE;
 	static gln_uint16 crc = 0;
 
@@ -3416,7 +3357,7 @@ static void gln_status_print(void) {
  * Front end function for updating status.  Either updates the status window
  * or prints the status line to the main window.
  */
-static void gln_status_notify(void) {
+static void gln_status_notify() {
 	if (gln_status_window)
 		gln_status_update();
 	else
@@ -3431,7 +3372,7 @@ static void gln_status_notify(void) {
  * This function should be called on the appropriate Glk window resize and
  * arrange events.
  */
-static void gln_status_redraw(void) {
+static void gln_status_redraw() {
 	if (gln_status_window) {
 		winid_t parent;
 
@@ -3495,7 +3436,7 @@ static int gln_output_prompt = FALSE;
  * Register recent text output from the interpreter.  This function is
  * called by os_printchar().
  */
-static void gln_output_notify(void) {
+static void gln_output_notify() {
 	gln_output_activity = TRUE;
 }
 
@@ -3507,7 +3448,7 @@ static void gln_output_notify(void) {
  * Clears the flag, so that more output text is required before the next
  * call returns TRUE.
  */
-static int gln_recent_output(void) {
+static int gln_recent_output() {
 	int result;
 
 	result = gln_output_activity;
@@ -3525,15 +3466,15 @@ static int gln_recent_output(void) {
  * Register a request for help, and print a note of how to get Glk command
  * help from the interpreter unless silenced.
  */
-static void gln_output_register_help_request(void) {
+static void gln_output_register_help_request() {
 	gln_help_requested = TRUE;
 }
 
-static void gln_output_silence_help_hints(void) {
+static void gln_output_silence_help_hints() {
 	gln_help_hints_silenced = TRUE;
 }
 
-static void gln_output_provide_help_hint(void) {
+static void gln_output_provide_help_hint() {
 	if (gln_help_requested && !gln_help_hints_silenced) {
 		g_vm->glk_set_style(style_Emphasized);
 		g_vm->glk_put_string("[Try 'glk help' for help on special interpreter"
@@ -3552,7 +3493,7 @@ static void gln_output_provide_help_hint(void) {
  * Once called, the flag is reset to FALSE, and requires more game output
  * to set it again.
  */
-static int gln_game_prompted(void) {
+static int gln_game_prompted() {
 	int result;
 
 	result = gln_output_prompt;
@@ -3569,7 +3510,7 @@ static int gln_game_prompted(void) {
  * to be a prompt, and set the game prompted flag if it does, otherwise
  * clear it.
  */
-static void gln_detect_game_prompt(void) {
+static void gln_detect_game_prompt() {
 	int index;
 
 	gln_output_prompt = FALSE;
@@ -3594,7 +3535,7 @@ static void gln_detect_game_prompt(void) {
  * Delete all buffered output text.  Free all malloc'ed buffer memory, and
  * return the buffer variables to their initial values.
  */
-static void gln_output_delete(void) {
+static void gln_output_delete() {
 	free(gln_output_buffer);
 	gln_output_buffer = NULL;
 	gln_output_allocation = gln_output_length = 0;
@@ -3607,7 +3548,7 @@ static void gln_output_delete(void) {
  * Flush any buffered output text to the Glk main window, and clear the
  * buffer.  Check in passing for game prompts that duplicate our's.
  */
-static void gln_output_flush(void) {
+static void gln_output_flush() {
 	assert(g_vm->glk_stream_get_current());
 
 	if (gln_output_length > 0) {
@@ -3732,7 +3673,7 @@ static void gln_banner_string(const char *message) {
  * line text where we are working with a non-windowing Glk, so it's best
  * ignored where we can.
  */
-void os_flush(void) {
+void os_flush() {
 }
 
 
@@ -4301,17 +4242,17 @@ static void gln_command_commands(const char *argument) {
 
 
 /* Glk subcommands and handler functions. */
-typedef const struct {
+struct gln_command_t {
 	const char *const command;                      /* Glk subcommand. */
 	void (* const handler)(const char *argument);   /* Subcommand handler. */
 	const int takes_argument;                       /* Argument flag. */
-} gln_command_t;
-typedef gln_command_t *gln_commandref_t;
+};
+typedef const gln_command_t *gln_commandref_t;
 
 static void gln_command_summary(const char *argument);
 static void gln_command_help(const char *argument);
 
-static gln_command_t GLN_COMMAND_TABLE[] = {
+static const gln_command_t GLN_COMMAND_TABLE[] = {
 	{"summary",        gln_command_summary,        FALSE},
 	{"script",         gln_command_script,         TRUE},
 	{"inputlog",       gln_command_inputlog,       TRUE},
@@ -4333,8 +4274,7 @@ static gln_command_t GLN_COMMAND_TABLE[] = {
  *
  * Report all current Glk settings.
  */
-static void
-gln_command_summary(const char *argument) {
+static void gln_command_summary(const char *argument) {
 	gln_commandref_t entry;
 	assert(argument);
 
@@ -4357,8 +4297,7 @@ gln_command_summary(const char *argument) {
  *
  * Document the available Glk commands.
  */
-static void
-gln_command_help(const char *command) {
+static void gln_command_help(const char *command) {
 	gln_commandref_t entry, matched;
 	assert(command);
 
@@ -4743,13 +4682,13 @@ static int gln_inside_list = FALSE;
 
 
 /* Table of single-character command abbreviations. */
-typedef const struct {
+struct gln_abbreviation_t {
 	const char abbreviation;       /* Abbreviation character. */
 	const char *const expansion;   /* Expansion string. */
-} gln_abbreviation_t;
-typedef gln_abbreviation_t *gln_abbreviationref_t;
+};
+typedef const gln_abbreviation_t *gln_abbreviationref_t;
 
-static gln_abbreviation_t GLN_ABBREVIATIONS[] = {
+static const gln_abbreviation_t GLN_ABBREVIATIONS[] = {
 	{'c', "close"},    {'g', "again"},  {'i', "inventory"},
 	{'k', "attack"},   {'l', "look"},   {'p', "open"},
 	{'q', "quit"},     {'r', "drop"},   {'t', "take"},
@@ -4814,7 +4753,7 @@ static void gln_expand_abbreviations(char *buffer, int size) {
  * The core interpreter doesn't terminate lists with a newline, so we take
  * care of that here; a fixup for input functions.
  */
-static void gln_output_endlist(void) {
+static void gln_output_endlist() {
 	if (gln_inside_list) {
 		/*
 		 * Supply the missing newline, using os_printchar() so that list output
@@ -5163,7 +5102,7 @@ char os_readchar(int millis) {
  * can't check for keypresses without blocking, so we do no checks at all,
  * and let lists always run to completion.
  */
-gln_bool os_stoplist(void) {
+gln_bool os_stoplist() {
 	static int call_count = 0;
 
 	event_t event;
@@ -5805,7 +5744,7 @@ static int gln_startup_code(int argc, char *argv[]) {
 	return TRUE;
 }
 
-static void gln_main(void) {
+static void gln_main() {
 	char *graphics_file = NULL;
 	int is_running;
 
@@ -6006,7 +5945,7 @@ static int gln_startup_called = FALSE,
  * Main entry point for Glk.  Here, all startup is done, and we call our
  * function to run the game.
  */
-void glk_main(void) {
+void glk_main() {
 	assert(gln_startup_called && !gln_main_called);
 	gln_main_called = TRUE;
 


Commit: a6b1c9085f4edd6c09d93228170971f5a7b0952e
    https://github.com/scummvm/scummvm/commit/a6b1c9085f4edd6c09d93228170971f5a7b0952e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: Safer interpreter tag generation for Quetzal save files

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


diff --git a/engines/glk/glk.cpp b/engines/glk/glk.cpp
index cffdd8b..b546299 100644
--- a/engines/glk/glk.cpp
+++ b/engines/glk/glk.cpp
@@ -204,7 +204,7 @@ Common::Error GlkEngine::loadGameState(int slot) {
 				Common::String md5 = QuetzalReader::readString(rs);
 				delete rs;
 
-				if (interpType != INTERPRETER_IDS[getInterpreterType()] ||
+				if (interpType != QuetzalBase::getInterpreterTag(getInterpreterType()) ||
 					parseLanguage(langCode) !=getLanguage() || md5 != getGameMD5())
 					errCode = Common::kReadingFailed;
 			}
diff --git a/engines/glk/quetzal.cpp b/engines/glk/quetzal.cpp
index 60f683b..7a11a57 100644
--- a/engines/glk/quetzal.cpp
+++ b/engines/glk/quetzal.cpp
@@ -30,26 +30,50 @@
 
 namespace Glk {
 
-const uint32 INTERPRETER_IDS[INTERPRETER_TADS3 + 1] = {
-	MKTAG('A', 'D', 'R', 'I'),
-	MKTAG('A', 'S', 'Y', 'S'),
-	MKTAG('A', 'G', 'I', 'L'),
-	MKTAG('A', 'L', 'N', '2'),
-	MKTAG('A', 'L', 'N', '3'),
-	MKTAG('B', 'O', 'C', 'F'),
-	MKTAG('Z', 'C', 'O', 'D'),
-	MKTAG('G', 'E', 'A', 'S'),
-	MKTAG('G', 'L', 'U', 'L'),
-	MKTAG('H', 'U', 'G', 'O'),
-	MKTAG('J', 'A', 'C', 'L'),
-	MKTAG('L', 'V', 'L', '9'),
-	MKTAG('M', 'A', 'G', 'N'),
-	MKTAG('Q', 'U', 'E', 'S'),
-	MKTAG('S', 'C', 'A', 'R'),
-	MKTAG('S', 'C', 'O', 'T'),
-	MKTAG('T', 'A', 'D', '2'),
-	MKTAG('T', 'A', 'D', '3')
-};
+/*--------------------------------------------------------------------------*/
+
+uint32 QuetzalBase::getInterpreterTag(InterpreterType interpType) {
+	switch (interpType) {
+	case INTERPRETER_ADRIFT:
+		return MKTAG('A', 'D', 'R', 'I');
+	case INTERPRETER_ADVSYS:
+		return MKTAG('A', 'S', 'Y', 'S');
+	case INTERPRETER_AGILITY:
+		return MKTAG('A', 'G', 'I', 'L');
+	case INTERPRETER_ALAN2:
+		return MKTAG('A', 'L', 'N', '2');
+	case INTERPRETER_ALAN3:
+		return MKTAG('A', 'L', 'N', '3');
+	case INTERPRETER_FROTZ:
+		return MKTAG('Z', 'C', 'O', 'D');
+	case INTERPRETER_GEAS:
+		return MKTAG('G', 'E', 'A', 'S');
+	case INTERPRETER_GLULXE:
+		return MKTAG('G', 'L', 'U', 'L');
+	case INTERPRETER_HUGO:
+		return MKTAG('H', 'U', 'G', 'O');
+	case INTERPRETER_JACL:
+		return MKTAG('J', 'A', 'C', 'L');
+	case INTERPRETER_LEVEL9:
+		return MKTAG('L', 'V', 'L', '9');
+	case INTERPRETER_MAGNETIC:
+		return MKTAG('M', 'A', 'G', 'N');
+	case INTERPRETER_QUEST:
+		return MKTAG('Q', 'U', 'E', 'S');
+	case INTERPRETER_SCARE:
+		return MKTAG('S', 'C', 'A', 'R');
+	case INTERPRETER_SCOTT:
+		return MKTAG('S', 'C', 'O', 'T');
+	case INTERPRETER_TADS2:
+		return MKTAG('T', 'A', 'D', '2');
+	case INTERPRETER_TADS3:
+		return MKTAG('T', 'A', 'D', '3');
+	default:
+		error("Invalid interpreter type");
+	}
+}
+
+/*--------------------------------------------------------------------------*/
 
 void QuetzalReader::clear() {
 	_chunks.clear();
@@ -222,7 +246,7 @@ void QuetzalWriter::addCommonChunks(const Common::String &saveName) {
 		ws.writeUint32BE(g_vm->_events->getTotalPlayTicks());
 
 		// Write out intrepreter type, language, and game Id
-		ws.writeUint32BE(INTERPRETER_IDS[g_vm->getInterpreterType()]);
+		ws.writeUint32BE(getInterpreterTag(g_vm->getInterpreterType()));
 		const char *langCode = getLanguageCode(g_vm->getLanguage());
 		ws.write(langCode, strlen(langCode) + 1);
 		Common::String md5 = g_vm->getGameMD5();
diff --git a/engines/glk/quetzal.h b/engines/glk/quetzal.h
index c9452b6..b7468a5 100644
--- a/engines/glk/quetzal.h
+++ b/engines/glk/quetzal.h
@@ -43,12 +43,15 @@ enum QueztalTag {
 	ID_SCVM = MKTAG('S', 'C', 'V', 'M')
 };
 
-extern const uint32 INTERPRETER_IDS[INTERPRETER_TADS3 + 1];
+class QuetzalBase {
+public:
+	static uint32 getInterpreterTag(InterpreterType interpType);
+};
 
 /**
  * Quetzal save file reader
  */
-class QuetzalReader {
+class QuetzalReader : public QuetzalBase {
 	struct Chunk {
 		uint32 _id;
 		size_t _offset, _size;
@@ -156,7 +159,7 @@ public:
 /**
  * Quetzal save file writer
  */
-class QuetzalWriter {
+class QuetzalWriter : public QuetzalBase {
 	/**
 	 * Chunk entry
 	 */


Commit: 0a4d64f55e03656a403b66656dbcd5dd00ee5912
    https://github.com/scummvm/scummvm/commit/0a4d64f55e03656a403b66656dbcd5dd00ee5912
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Hooking up engine class to subengine code

Changed paths:
    engines/glk/level9/level9.cpp
    engines/glk/level9/level9_main.cpp
    engines/glk/level9/os_glk.cpp


diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
index 5cae3dc..6d8baa1 100644
--- a/engines/glk/level9/level9.cpp
+++ b/engines/glk/level9/level9.cpp
@@ -27,12 +27,27 @@ namespace Level9 {
 
 Level9 *g_vm = nullptr;
 
+extern void gln_main(const char *filename);
+extern int gln_startup_code(int argc, char *argv[]);
+
 Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
 	g_vm = this;
 }
 
 void Level9::runGame() {
-	// TODO
+	initialize();
+
+	_gameFile.close();
+	gln_main(getFilename().c_str());
+
+	deinitialize();
+}
+
+bool Level9::initialize() {
+	return gln_startup_code(0, nullptr);
+}
+
+void Level9::deinitialize() {
 }
 
 Common::Error Level9::readSaveData(Common::SeekableReadStream *rs) {
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 7aa95a7..e8963c9 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -163,7 +163,7 @@ int L9V1Game = -1;
 
 
 /* Prototypes */
-L9BOOL LoadGame2(char *filename, char *picname);
+L9BOOL LoadGame2(const char *filename, char *picname);
 int getlongcode();
 L9BOOL GetWordV2(char *buff, int Word);
 L9BOOL GetWordV3(char *buff, int Word);
@@ -174,7 +174,7 @@ void show_picture(int pic);
 #define CODEFOLLOWFILE "c:\\temp\\level9.txt"
 FILE *f;
 L9UINT16 *cfvar, *cfvar2;
-const char *codes[] = {
+const char *const codes[] = {
 	"Goto",
 	"intgosub",
 	"intreturn",
@@ -208,7 +208,7 @@ const char *codes[] = {
 	"ilins",
 	"ilins",
 };
-const char *functions[] = {
+const char *const functions[] = {
 	"calldriver",
 	"L9Random",
 	"save",
@@ -216,7 +216,7 @@ const char *functions[] = {
 	"clearworkspace",
 	"clearstack"
 };
-const char *drivercalls[] = {
+const char *const drivercalls[] = {
 	"init",
 	"drivercalcchecksum",
 	"driveroswrch",
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index be63ae5..eac1616 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -1681,6 +1681,39 @@ static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
 	*y_offset = ((int) window_height - height * pixel_size) / 2;
 }
 
+/*
+ * gms_graphics_compare_layering_inverted()
+ * gln_graphics_assign_layers()
+ *
+ * Given two sets of image bitmaps, and a palette, this function will
+ * assign layers palette colors.
+ *
+ * Layers are assigned by first counting the number of vertices in the
+ * color plane, to get a measure of the complexity of shapes displayed in
+ * this color, and also the raw number of times each palette color is
+ * used.  This is then sorted, so that layers are assigned to colors, with
+ * the lowest layer being the color with the most complex shapes, and
+ * within this (or where the count of vertices is zero) the most used color.
+ *
+ * The function compares pixels in the two image bitmaps given, these
+ * being the off-screen and on-screen buffers, and generates counts only
+ * where these bitmaps differ.  This ensures that only pixels not yet
+ * painted are included in layering.
+ *
+ * As well as assigning layers, this function returns a set of layer usage
+ * flags, to help the rendering loop to terminate as early as possible.
+ *
+ * By painting lower layers first, the paint can take in larger areas if
+ * it's permitted to include not-yet-validated higher levels.  This helps
+ * minimize the amount of Glk areas fills needed to render a picture.
+ */
+struct gln_layering_t {
+	long complexity;  /* Count of vertices for this color. */
+	long usage;       /* Color usage count. */
+	int color;        /* Color index into palette. */
+};
+
+#ifndef GARGLK
 
 /*
  * gln_graphics_is_vertex()
@@ -1696,7 +1729,7 @@ static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
  * regions needed to render the complete picture.
  */
 static int gln_graphics_is_vertex(gln_byte off_screen[], gln_uint16 width, gln_uint16 height,
-		int x, int y) {
+	int x, int y) {
 	gln_byte pixel;
 	int above, below, left, right;
 	long index_row;
@@ -1727,39 +1760,6 @@ static int gln_graphics_is_vertex(gln_byte off_screen[], gln_uint16 width, gln_u
 	return ((above || below) && (left || right));
 }
 
-
-/*
- * gms_graphics_compare_layering_inverted()
- * gln_graphics_assign_layers()
- *
- * Given two sets of image bitmaps, and a palette, this function will
- * assign layers palette colors.
- *
- * Layers are assigned by first counting the number of vertices in the
- * color plane, to get a measure of the complexity of shapes displayed in
- * this color, and also the raw number of times each palette color is
- * used.  This is then sorted, so that layers are assigned to colors, with
- * the lowest layer being the color with the most complex shapes, and
- * within this (or where the count of vertices is zero) the most used color.
- *
- * The function compares pixels in the two image bitmaps given, these
- * being the off-screen and on-screen buffers, and generates counts only
- * where these bitmaps differ.  This ensures that only pixels not yet
- * painted are included in layering.
- *
- * As well as assigning layers, this function returns a set of layer usage
- * flags, to help the rendering loop to terminate as early as possible.
- *
- * By painting lower layers first, the paint can take in larger areas if
- * it's permitted to include not-yet-validated higher levels.  This helps
- * minimize the amount of Glk areas fills needed to render a picture.
- */
-struct gln_layering_t {
-	long complexity;  /* Count of vertices for this color. */
-	long usage;       /* Color usage count. */
-	int color;        /* Color index into palette. */
-};
-
 static int gln_graphics_compare_layering_inverted(const void *void_first,
         const void *void_second) {
 	const gln_layering_t *first = (const gln_layering_t *)void_first;
@@ -1833,7 +1833,6 @@ static void gln_graphics_assign_layers(gln_byte off_screen[], gln_byte on_screen
 	}
 }
 
-
 /*
  * gln_graphics_paint_region()
  *
@@ -1966,6 +1965,7 @@ break_y_max:
 		index_row += width;
 	}
 }
+#endif
 
 static void gln_graphics_paint_everything(winid_t glk_window, glui32 palette[],
 		gln_byte off_screen[], int x_offset, int y_offset, gln_uint16 width, gln_uint16 height) {
@@ -3640,10 +3640,14 @@ static void gln_standout_string(const char *message) {
 	gln_styled_string(style_Emphasized, message);
 }
 
+#ifndef GARGLK
+
 static void gln_standout_char(char c) {
 	gln_styled_char(style_Emphasized, c);
 }
 
+#endif
+
 static void gln_normal_string(const char *message) {
 	gln_styled_string(style_Normal, message);
 }
@@ -5541,8 +5545,7 @@ static const int GLN_WATCHDOG_TIMEOUT = 5,
  * The following values need to be passed between the startup_code and main
  * functions.
  */
-static const char *gln_gamefile = NULL,      /* Name of game file. */
-             *gln_game_message = NULL;  /* Error message. */
+static const char *gln_game_message = NULL;  /* Error message. */
 
 
 /*
@@ -5677,7 +5680,7 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
  * handle options.  The second is called from g_vm->glk_main(), and does the real
  * work of running the game.
  */
-static int gln_startup_code(int argc, char *argv[]) {
+int gln_startup_code(int argc, char *argv[]) {
 	int argv_index;
 
 	/* Handle command line arguments. */
@@ -5710,41 +5713,11 @@ static int gln_startup_code(int argc, char *argv[]) {
 		return FALSE;
 	}
 
-	/*
-	 * Get the name of the game file.  Since we need this in our call from
-	 * g_vm->glk_main, we need to keep it in a module static variable.  If the game
-	 * file name is omitted, then here we'll set the pointerto NULL, and
-	 * complain about it later in main.  Passing the message string around
-	 * like this is a nuisance...
-	 */
-	if (argv_index == argc - 1) {
-		gln_gamefile = argv[argv_index];
-		gln_game_message = NULL;
-#ifdef GARGLK
-		{
-			const char *s;
-			s = strrchr(gln_gamefile, '\\');
-			if (s)
-				g_vm->garglk_set_story_name(s + 1);
-			s = strrchr(gln_gamefile, '/');
-			if (s)
-				g_vm->garglk_set_story_name(s + 1);
-		}
-#endif
-	} else {
-		gln_gamefile = NULL;
-		if (argv_index < argc - 1)
-			gln_game_message = "More than one game file was given"
-			                   " on the command line.";
-		else
-			gln_game_message = "No game file was given on the command line.";
-	}
-
 	/* All startup options were handled successfully. */
 	return TRUE;
 }
 
-static void gln_main() {
+void gln_main(const char *filename) {
 	char *graphics_file = NULL;
 	int is_running;
 
@@ -5765,20 +5738,11 @@ static void gln_main() {
 	g_vm->glk_set_window(gln_main_window);
 	g_vm->glk_set_style(style_Normal);
 
-	/* If there's a problem with the game file, complain now. */
-	if (!gln_gamefile) {
-		assert(gln_game_message);
-		gln_header_string("Glk Level 9 Error\n\n");
-		gln_normal_string(gln_game_message);
-		gln_normal_char('\n');
-		g_vm->glk_exit();
-	}
-
 	/*
 	 * Given the basic game name, try to come up with a usable graphics
 	 * filenames.  The graphics file may be null.
 	 */
-	gln_establish_picture_filename(gln_gamefile, &graphics_file);
+	gln_establish_picture_filename(g_vm->getFilename().c_str(), &graphics_file);
 
 	/*
 	 * Check Glk library capabilities, and note pictures are impossible if the
@@ -5798,7 +5762,7 @@ static void gln_main() {
 
 	/* If pictures are possible, search for bitmap graphics. */
 	if (gln_graphics_possible)
-		gln_graphics_locate_bitmaps(gln_gamefile);
+		gln_graphics_locate_bitmaps(g_vm->getFilename().c_str());
 
 	/* Try to create a one-line status window.  We can live without it. */
 	/*
@@ -5820,12 +5784,12 @@ static void gln_main() {
 
 		/* Load the game, sending in any established graphics file. */
 		int errNum = 0;
-		if (!LoadGame(gln_gamefile, graphics_file)) {
+		if (!LoadGame(filename, graphics_file)) {
 			if (gln_status_window)
 				g_vm->glk_window_close(gln_status_window, NULL);
 			gln_header_string("Glk Level 9 Error\n\n");
 			gln_normal_string("Can't find, open, or load game file '");
-			gln_normal_string(gln_gamefile);
+			gln_normal_string(g_vm->getFilename().c_str());
 			gln_normal_char('\'');
 			if (errNum != 0) {
 				gln_normal_string(": ERROR");
@@ -5927,97 +5891,5 @@ static void gln_main() {
 	free(graphics_file);
 }
 
-
-/*---------------------------------------------------------------------*/
-/*  Linkage between Glk entry/exit calls and the real interpreter      */
-/*---------------------------------------------------------------------*/
-
-/*
- * Safety flags, to ensure we always get startup before main, and that
- * we only get a call to main once.
- */
-static int gln_startup_called = FALSE,
-           gln_main_called = FALSE;
-
-/*
- * g_vm->glk_main()
- *
- * Main entry point for Glk.  Here, all startup is done, and we call our
- * function to run the game.
- */
-void glk_main() {
-	assert(gln_startup_called && !gln_main_called);
-	gln_main_called = TRUE;
-
-	/* Call the interpreter main function. */
-	gln_main();
-}
-
-
-/*---------------------------------------------------------------------*/
-/*  Glk linkage relevant only to the UNIX platform                     */
-/*---------------------------------------------------------------------*/
-#ifdef UNUSED
-
-/*
- * Glk arguments for UNIX versions of the Glk interpreter.
- */
-glkunix_argumentlist_t glkunix_arguments[] = {
-	{
-		(char *) "-nc", glkunix_arg_NoValue,
-		(char *) "-nc        No local handling for Glk special commands"
-	},
-	{
-		(char *) "-na", glkunix_arg_NoValue,
-		(char *) "-na        Turn off abbreviation expansions"
-	},
-	{
-		(char *) "-ni", glkunix_arg_NoValue,
-		(char *) "-ni        No local handling for 'quit', 'restart',"
-		" 'save', and 'restore'"
-	},
-	{
-		(char *) "-np", glkunix_arg_NoValue,
-		(char *) "-np        Turn off pictures"
-	},
-	{
-		(char *) "-ne", glkunix_arg_NoValue,
-		(char *) "-ne        Turn off additional interpreter prompt"
-	},
-	{
-		(char *) "-nl", glkunix_arg_NoValue,
-		(char *) "-nl        Turn off infinite loop detection"
-	},
-	{
-		(char *) "", glkunix_arg_ValueCanFollow,
-		(char *) "filename   game to run"
-	},
-	{NULL, glkunix_arg_End, NULL}
-};
-
-
-/*
- * glkunix_startup_code()
- *
- * Startup entry point for UNIX versions of Glk interpreter.  Glk will
- * call glkunix_startup_code() to pass in arguments.  On startup, we call
- * our function to parse arguments and generally set stuff up.
- */
-int glkunix_startup_code(glkunix_startup_t *data) {
-	assert(!gln_startup_called);
-	gln_startup_called = TRUE;
-
-#ifdef GARGLK
-	garg_vm->glk_set_program_name("Level 9 5.1");
-	garg_vm->glk_set_program_info(
-	    "Level 9 5.1 by Glen Summers, David Kinder\n"
-	    "Alan Staniforth, Simon Baldwin and Dieter Baron\n"
-	    "Glk Graphics support by Tor Andersson\n");
-#endif
-
-	return gln_startup_code(data->argc, data->argv);
-}
-#endif
-
 } // End of namespace Level9
 } // End of namespace Glk


Commit: 25c682793d0034fea7e5c48425f9acb4b43bba9f
    https://github.com/scummvm/scummvm/commit/25c682793d0034fea7e5c48425f9acb4b43bba9f
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Data loading fixes and cleanup

Changed paths:
    engines/glk/level9/level9_main.cpp
    engines/glk/level9/os_glk.cpp


diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index e8963c9..9a08984 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -891,34 +891,6 @@ L9BYTE calcchecksum(L9BYTE *ptr, L9UINT32 num) {
 	return d1;
 }
 
-/*
-L9BOOL Check(L9BYTE* StartFile,L9UINT32 FileSize,L9UINT32 Offset)
-{
-    L9UINT16 d0,num;
-    int i;
-    L9BYTE* Image;
-    L9UINT32 Size=0,Min,Max;
-    L9BOOL ret,JumpKill;
-
-    for (i=0;i<12;i++)
-    {
-        d0=L9WORD (StartFile+Offset+0x12 + i*2);
-        if (d0>=0x8000+LISTAREASIZE) return FALSE;
-    }
-
-    num=L9WORD(StartFile+Offset)+1;
-    if (Offset+num>FileSize) return FALSE;
-    if (calcchecksum(StartFile+Offset,num)) return FALSE;
-
-    Image=calloc(FileSize,1);
-
-    Min=Max=Offset+d0;
-    ret=ValidateSequence(StartFile,Image,Offset+d0,Offset+d0,&Size,FileSize,&Min,&Max,FALSE,&JumpKill,NULL);
-    free(Image);
-    return ret;
-}
-*/
-
 long Scan(L9BYTE *StartFile, L9UINT32 size) {
 	L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
 	L9BYTE *Image = (L9BYTE *)calloc(size, 1);
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index eac1616..9005b6c 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -31,12 +31,7 @@ namespace Level9 {
 #define BITS_PER_CHAR 8
 
 /* File path delimiter, used to be #defined in v2 interpreter. */
-#if defined(_Windows) || defined(__MSDOS__) \
-    || defined (_WIN32) || defined (__WIN32__)
-static const char GLN_FILE_DELIM = '\\';
-#else
 static const char GLN_FILE_DELIM = '/';
-#endif
 
 /*---------------------------------------------------------------------*/
 /*  Module variables, miscellaneous other stuff                        */
@@ -3660,10 +3655,14 @@ static void gln_header_string(const char *message) {
 	gln_styled_string(style_Header, message);
 }
 
+#ifndef GARGLK
+
 static void gln_banner_string(const char *message) {
 	gln_styled_string(style_Subheader, message);
 }
 
+#endif
+
 
 /*
  * os_flush()
@@ -5227,6 +5226,8 @@ static void gln_event_wait_2(glui32 wait_type_1, glui32 wait_type_2, event_t *ev
 
 	do {
 		g_vm->glk_select(event);
+		if (g_vm->shouldQuit())
+			return;
 
 		switch (event->type) {
 		case evtype_Arrange:
@@ -5581,46 +5582,46 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
 	graphics_file = (char *)gln_malloc(strlen(base) + strlen(".___") + 1);
 
 	/* Form a candidate graphics file, using a .PIC extension. */
-	if (f.isOpen()) {
+	if (!f.isOpen()) {
 		strcpy(graphics_file, base);
 		strcat(graphics_file, ".PIC");
 		f.open(graphics_file);
 	}
 
-	if (f.isOpen()) {
+	if (!f.isOpen()) {
 		strcpy(graphics_file, base);
 		strcat(graphics_file, ".pic");
 		f.open(graphics_file);
 	}
 
 	/* Form a candidate graphics file, using a .CGA extension. */
-	if (f.isOpen()) {
+	if (!f.isOpen()) {
 		strcpy(graphics_file, base);
 		strcat(graphics_file, ".CGA");
 		f.open(graphics_file);
 	}
 
-	if (f.isOpen()) {
+	if (!f.isOpen()) {
 		strcpy(graphics_file, base);
 		strcat(graphics_file, ".cga");
 		f.open(graphics_file);
 	}
 
 	/* Form a candidate graphics file, using a .HRC extension. */
-	if (f.isOpen()) {
+	if (!f.isOpen()) {
 		strcpy(graphics_file, base);
 		strcat(graphics_file, ".HRC");
 		f.open(graphics_file);
 	}
 
-	if (f.isOpen()) {
+	if (!f.isOpen()) {
 		strcpy(graphics_file, base);
 		strcat(graphics_file, ".hrc");
 		f.open(graphics_file);
 	}
 
 	/* No access to graphics file. */
-	if (f.isOpen()) {
+	if (!f.isOpen()) {
 		free(graphics_file);
 		graphics_file = NULL;
 	}
@@ -5721,13 +5722,6 @@ void gln_main(const char *filename) {
 	char *graphics_file = NULL;
 	int is_running;
 
-	/* Ensure Level 9 internal types have the right sizes. */
-	if (!(sizeof(gln_byte) == 1
-	        && sizeof(gln_uint16) == 2 && sizeof(gln_uint32) == 4)) {
-		gln_fatal("GLK: Types sized incorrectly, recompilation is needed");
-		g_vm->glk_exit();
-	}
-
 	/* Create the main Glk window, and set its stream as current. */
 	gln_main_window = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
 	if (!gln_main_window) {
@@ -5742,7 +5736,7 @@ void gln_main(const char *filename) {
 	 * Given the basic game name, try to come up with a usable graphics
 	 * filenames.  The graphics file may be null.
 	 */
-	gln_establish_picture_filename(g_vm->getFilename().c_str(), &graphics_file);
+	gln_establish_picture_filename(filename, &graphics_file);
 
 	/*
 	 * Check Glk library capabilities, and note pictures are impossible if the
@@ -5762,7 +5756,7 @@ void gln_main(const char *filename) {
 
 	/* If pictures are possible, search for bitmap graphics. */
 	if (gln_graphics_possible)
-		gln_graphics_locate_bitmaps(g_vm->getFilename().c_str());
+		gln_graphics_locate_bitmaps(filename);
 
 	/* Try to create a one-line status window.  We can live without it. */
 	/*
@@ -5789,7 +5783,7 @@ void gln_main(const char *filename) {
 				g_vm->glk_window_close(gln_status_window, NULL);
 			gln_header_string("Glk Level 9 Error\n\n");
 			gln_normal_string("Can't find, open, or load game file '");
-			gln_normal_string(g_vm->getFilename().c_str());
+			gln_normal_string(filename);
 			gln_normal_char('\'');
 			if (errNum != 0) {
 				gln_normal_string(": ERROR");
@@ -5806,9 +5800,7 @@ void gln_main(const char *filename) {
 		}
 
 		/* Print out a short banner. */
-		gln_header_string("\nLevel 9 Interpreter, version 5.1\n");
-		gln_banner_string("Written by Glen Summers and David Kinder\n"
-		                  "Glk interface by Simon Baldwin\n\n");
+		gln_header_string("\nLevel 9 Interpreter, ScummVM version\n");
 
 		/*
 		 * Set the stop reason indicator to none.  A game will then exit with a


Commit: 0ecae64fdc00a7c874cc02a5cb4a1c37249687ef
    https://github.com/scummvm/scummvm/commit/0ecae64fdc00a7c874cc02a5cb4a1c37249687ef
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Cleaning up initialization

Changed paths:
  A engines/glk/level9/os_glk.h
    engines/glk/level9/level9.cpp
    engines/glk/level9/level9_main.cpp
    engines/glk/level9/level9_main.h
    engines/glk/level9/os_glk.cpp


diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
index 6d8baa1..be4ac22 100644
--- a/engines/glk/level9/level9.cpp
+++ b/engines/glk/level9/level9.cpp
@@ -21,15 +21,14 @@
  */
 
 #include "glk/level9/level9.h"
+#include "glk/level9/level9_main.h"
+#include "glk/level9/os_glk.h"
 
 namespace Glk {
 namespace Level9 {
 
 Level9 *g_vm = nullptr;
 
-extern void gln_main(const char *filename);
-extern int gln_startup_code(int argc, char *argv[]);
-
 Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
 	g_vm = this;
 }
@@ -37,6 +36,7 @@ Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst,
 void Level9::runGame() {
 	initialize();
 
+
 	_gameFile.close();
 	gln_main(getFilename().c_str());
 
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 9a08984..043266a 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -57,10 +57,10 @@ namespace Level9 {
 #define FIRSTLINESIZE 96
 
 /* Typedefs */
-typedef struct {
+struct SaveStruct {
 	L9UINT16 vartable[256];
 	L9BYTE listarea[LISTAREASIZE];
-} SaveStruct;
+};
 
 /* Enumerations */
 enum L9GameTypes { L9_V1, L9_V2, L9_V3, L9_V4 };
@@ -76,7 +76,7 @@ enum L9MsgTypes { MSGT_V1, MSGT_V2 };
 enum L9GfxTypes { GFX_V2, GFX_V3A, GFX_V3B, GFX_V3C };
 
 /* Global Variables */
-L9BYTE *startfile = NULL, *pictureaddress = NULL, *picturedata = NULL;
+L9BYTE *startfile, *pictureaddress, *picturedata;
 L9BYTE *startdata;
 L9UINT32 FileSize, picturesize;
 
@@ -95,43 +95,39 @@ int L9GameType;
 int L9MsgType;
 char LastGame[MAX_PATH];
 char FirstLine[FIRSTLINESIZE];
-int FirstLinePos = 0;
-int FirstPicture = -1;
+int FirstLinePos;
+int FirstPicture;
 
-#if defined(AMIGA) && defined(_DCC)
-__far SaveStruct ramsavearea[RAMSAVESLOTS];
-#else
 SaveStruct ramsavearea[RAMSAVESLOTS];
-#endif
 
 GameState workspace;
 
 L9UINT16 randomseed;
-L9UINT16 constseed = 0;
+L9UINT16 constseed;
 L9BOOL Running;
 
 char ibuff[IBUFFSIZE];
 L9BYTE *ibuffptr;
 char obuff[34];
-Common::SeekableReadStream *scriptfile = NULL;
+Common::SeekableReadStream *scriptfile;
 
-L9BOOL Cheating = FALSE;
+L9BOOL Cheating;
 int CheatWord;
 GameState CheatWorkspace;
 
 int reflectflag, scale, gintcolour, option;
-int l9textmode = 0, drawx = 0, drawy = 0, screencalled = 0, showtitle = 1;
-L9BYTE *gfxa5 = NULL;
-Bitmap *bitmap = NULL;
-int gfx_mode = GFX_V2;
+int l9textmode, drawx, drawy, screencalled, showtitle;
+L9BYTE *gfxa5;
+Bitmap *bitmap;
+int gfx_mode;
 
 L9BYTE *GfxA5Stack[GFXSTACKSIZE];
-int GfxA5StackPos = 0;
+int GfxA5StackPos;
 int GfxScaleStack[GFXSTACKSIZE];
-int GfxScaleStackPos = 0;
+int GfxScaleStackPos;
 
-char lastchar = '.';
-char lastactualchar = 0;
+char lastchar;
+char lastactualchar;
 int d5;
 
 L9BYTE *codeptr; /* instruction codes */
@@ -141,7 +137,7 @@ L9BYTE *list9ptr;
 
 int unpackd3;
 
-L9BYTE exitreversaltable[16] = {0x00, 0x04, 0x06, 0x07, 0x01, 0x08, 0x02, 0x03, 0x05, 0x0a, 0x09, 0x0c, 0x0b, 0xff, 0xff, 0x0f};
+const L9BYTE exitreversaltable[16] = {0x00, 0x04, 0x06, 0x07, 0x01, 0x08, 0x02, 0x03, 0x05, 0x0a, 0x09, 0x0c, 0x0b, 0xff, 0xff, 0x0f};
 
 L9UINT16 gnostack[128];
 L9BYTE gnoscratch[32];
@@ -159,8 +155,7 @@ const L9V1GameInfo L9V1Games[] = {
 	0x15, 0x5d, 252, -0x3e70, 0x0000, -0x3d30, -0x3ca0, 0x0100, 0x4120, -0x3b9d, 0x3988, /* Lords of Time */
 	0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
 };
-int L9V1Game = -1;
-
+int L9V1Game;
 
 /* Prototypes */
 L9BOOL LoadGame2(const char *filename, char *picname);
@@ -255,6 +250,30 @@ const char *const drivercalls[] = {
 };
 #endif
 
+void level9_initialize() {
+	FirstLinePos = 0;
+	FirstPicture = -1;
+	constseed = 0;
+	scriptfile = nullptr;
+	Cheating = FALSE;
+	l9textmode = 0;
+	drawx = 0;
+	drawy = 0;
+	screencalled = 0;
+	showtitle = 1;
+	gfxa5 = nullptr;
+	gfx_mode = GFX_V2;
+
+	GfxA5StackPos = 0;
+	GfxScaleStackPos = 0;
+
+	lastchar = '.';
+	lastactualchar = 0;
+	L9V1Game = -1;
+
+	startfile = pictureaddress = picturedata = nullptr;
+}
+
 void initdict(L9BYTE *ptr) {
 	dictptr = ptr;
 	unpackcount = 8;
@@ -621,7 +640,7 @@ void printmessageV2(int Msg) {
 void L9Allocate(L9BYTE **ptr, L9UINT32 Size) {
 	if (*ptr) free(*ptr);
 	*ptr = (L9BYTE *)malloc(Size);
-	if (*ptr == NULL) {
+	if (*ptr == nullptr) {
 		error("Unable to allocate memory for the game! Exiting...");
 	}
 }
@@ -629,11 +648,11 @@ void L9Allocate(L9BYTE **ptr, L9UINT32 Size) {
 void FreeMemory() {
 	if (startfile) {
 		free(startfile);
-		startfile = NULL;
+		startfile = nullptr;
 	}
 	if (pictureaddress) {
 		free(pictureaddress);
-		pictureaddress = NULL;
+		pictureaddress = nullptr;
 	}
 	if (bitmap) {
 		free(bitmap);
@@ -641,11 +660,11 @@ void FreeMemory() {
 	}
 	if (scriptfile) {
 		delete scriptfile;
-		scriptfile = NULL;
+		scriptfile = nullptr;
 	}
-	picturedata = NULL;
+	picturedata = nullptr;
 	picturesize = 0;
-	gfxa5 = NULL;
+	gfxa5 = nullptr;
 }
 
 L9BOOL load(const char *filename) {
@@ -901,7 +920,7 @@ long Scan(L9BYTE *StartFile, L9UINT32 size) {
 	long Offset = -1;
 	L9BOOL JumpKill, DriverV4;
 
-	if ((Chk == NULL) || (Image == NULL)) {
+	if ((Chk == nullptr) || (Image == nullptr)) {
 		error("Unable to allocate memory for game scan! Exiting...");
 	}
 
@@ -967,7 +986,7 @@ long ScanV2(L9BYTE *StartFile, L9UINT32 size) {
 	long Offset = -1;
 	L9BOOL JumpKill;
 
-	if ((Chk == NULL) || (Image == NULL)) {
+	if ((Chk == nullptr) || (Image == nullptr)) {
 		error("Unable to allocate memory for game scan! Exiting...");
 	}
 
@@ -992,7 +1011,7 @@ long ScanV2(L9BYTE *StartFile, L9UINT32 size) {
 
 			Size = 0;
 			Min = Max = i + d0;
-			if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+			if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
 #ifdef L9DEBUG
 				printf("Found valid V2 header at %ld, code size %ld", i, Size);
 #endif
@@ -1021,7 +1040,7 @@ long ScanV1(L9BYTE *StartFile, L9UINT32 size) {
 	int dictOff1 = 0, dictOff2 = 0;
 	L9BYTE dictVal1 = 0xff, dictVal2 = 0xff;
 
-	if (Image == NULL) {
+	if (Image == nullptr) {
 		error("Unable to allocate memory for game scan! Exiting...");
 	}
 
@@ -1030,7 +1049,7 @@ long ScanV1(L9BYTE *StartFile, L9UINT32 size) {
 			Size = 0;
 			Min = Max = i;
 			Replace = 0;
-			if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+			if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
 				if (Size > MaxCount && Size > 100 && Size < 10000) {
 					MaxCount = Size;
 					//MaxMin = Min;
@@ -1109,7 +1128,7 @@ void FullScan(L9BYTE *StartFile, L9UINT32 size) {
 		Size = 0;
 		Min = Max = i;
 		Replace = 0;
-		if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, NULL)) {
+		if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
 			if (Size > MaxCount) {
 				MaxCount = Size;
 				MaxMin = Min;
@@ -1223,11 +1242,11 @@ L9BOOL intinitialise(const char *filename, char *picname) {
 
 	if (pictureaddress) {
 		free(pictureaddress);
-		pictureaddress = NULL;
+		pictureaddress = nullptr;
 	}
-	picturedata = NULL;
+	picturedata = nullptr;
 	picturesize = 0;
-	gfxa5 = NULL;
+	gfxa5 = nullptr;
 
 	if (!load(filename)) {
 		error("\rUnable to load: %s\r", filename);
@@ -1241,7 +1260,7 @@ L9BOOL intinitialise(const char *filename, char *picname) {
 			L9Allocate(&pictureaddress, picturesize);
 			if (f.read(pictureaddress, picturesize) != picturesize) {
 				free(pictureaddress);
-				pictureaddress = NULL;
+				pictureaddress = nullptr;
 				picturesize = 0;
 			}
 			f.close();
@@ -1356,13 +1375,13 @@ L9BOOL intinitialise(const char *filename, char *picname) {
 	/* If there was no graphics file, look in the game data */
 	if (pictureaddress) {
 		if (!findsubs(pictureaddress, picturesize, &picturedata, &picturesize)) {
-			picturedata = NULL;
+			picturedata = nullptr;
 			picturesize = 0;
 		}
 	} else {
 		if (!findsubs(startdata, FileSize, &picturedata, &picturesize)
 		        && !findsubs(startfile, startdata - startfile, &picturedata, &picturesize)) {
-			picturedata = NULL;
+			picturedata = nullptr;
 			picturesize = 0;
 		}
 	}
@@ -1712,7 +1731,7 @@ void calldriver() {
 		} else {
 			os_set_filenumber(NewName, MAX_PATH, *a6);
 		}
-		LoadGame2(NewName, NULL);
+		LoadGame2(NewName, nullptr);
 	} else driver(d0, a6);
 }
 
@@ -1859,10 +1878,10 @@ void l9_fgets(char *s, int n, Common::SeekableReadStream *f) {
 }
 
 L9BOOL scriptinput(char *buffer, int size) {
-	while (scriptfile != NULL) {
+	while (scriptfile != nullptr) {
 		if (scriptfile->eos()) {
 			delete scriptfile;
-			scriptfile = NULL;
+			scriptfile = nullptr;
 		} else {
 			char *p = buffer;
 			*p = '\0';
@@ -2150,7 +2169,7 @@ L9BOOL corruptinginput() {
 
 	list9ptr = list9startptr;
 
-	if (ibuffptr == NULL) {
+	if (ibuffptr == nullptr) {
 		if (Cheating) NextCheat();
 		else {
 			/* flush */
@@ -2183,7 +2202,7 @@ L9BOOL corruptinginput() {
 	while (TRUE) {
 		d0 = *a6++;
 		if (d0 == 0) {
-			ibuffptr = NULL;
+			ibuffptr = nullptr;
 			L9SETWORD(list9ptr, 0);
 			return TRUE;
 		}
@@ -3107,7 +3126,7 @@ void show_picture(int pic) {
 		GfxScaleStackPos = 0;
 		absrunsub(0);
 		if (!findsub(pic, &gfxa5))
-			gfxa5 = NULL;
+			gfxa5 = nullptr;
 	}
 }
 
@@ -3117,14 +3136,14 @@ void picture() {
 
 void GetPictureSize(int *width, int *height) {
 	if (L9GameType == L9_V4) {
-		if (width != NULL)
+		if (width != nullptr)
 			*width = 0;
-		if (height != NULL)
+		if (height != nullptr)
 			*height = 0;
 	} else {
-		if (width != NULL)
+		if (width != nullptr)
 			*width = (gfx_mode != GFX_V3C) ? 160 : 320;
-		if (height != NULL)
+		if (height != nullptr)
 			*height = (gfx_mode == GFX_V2) ? 128 : 96;
 	}
 }
@@ -3132,7 +3151,7 @@ void GetPictureSize(int *width, int *height) {
 L9BOOL RunGraphics() {
 	if (gfxa5) {
 		if (!getinstruction(&gfxa5))
-			gfxa5 = NULL;
+			gfxa5 = nullptr;
 		return TRUE;
 	}
 	return FALSE;
@@ -3506,7 +3525,7 @@ L9BOOL LoadGame2(const char *filename, char *picname) {
 
 	/* may be already running a game, maybe in input routine */
 	Running = FALSE;
-	ibuffptr = NULL;
+	ibuffptr = nullptr;
 
 	/* intstart */
 	if (!intinitialise(filename, picname)) return FALSE;
diff --git a/engines/glk/level9/level9_main.h b/engines/glk/level9/level9_main.h
index ab30242..ec04db3 100644
--- a/engines/glk/level9/level9_main.h
+++ b/engines/glk/level9/level9_main.h
@@ -86,32 +86,34 @@ struct Bitmap {
 #define L9SETWORD(x,val) WRITE_LE_UINT16(x, val)
 #define L9SETDWORD(x,val) WRITE_LE_UINT32(x, val)
 
+extern void level9_initialize();
+
 /* routines provided by os dependent code */
-void os_printchar(char c);
-L9BOOL os_input(char *ibuff, int size);
-char os_readchar(int millis);
-L9BOOL os_stoplist(void);
-void os_flush(void);
-L9BOOL os_save_file(L9BYTE *Ptr, int Bytes);
-L9BOOL os_load_file(L9BYTE *Ptr, int *Bytes, int Max);
-L9BOOL os_get_game_file(char *NewName, int Size);
-void os_set_filenumber(char *NewName, int Size, int n);
-void os_graphics(int mode);
-void os_cleargraphics(void);
-void os_setcolour(int colour, int index);
-void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2);
-void os_fill(int x, int y, int colour1, int colour2);
-void os_show_bitmap(int pic, int x, int y);
-Common::SeekableReadStream *os_open_script_file(void);
+extern void os_printchar(char c);
+extern L9BOOL os_input(char *ibuff, int size);
+extern char os_readchar(int millis);
+extern L9BOOL os_stoplist(void);
+extern void os_flush(void);
+extern L9BOOL os_save_file(L9BYTE *Ptr, int Bytes);
+extern L9BOOL os_load_file(L9BYTE *Ptr, int *Bytes, int Max);
+extern L9BOOL os_get_game_file(char *NewName, int Size);
+extern void os_set_filenumber(char *NewName, int Size, int n);
+extern void os_graphics(int mode);
+extern void os_cleargraphics(void);
+extern void os_setcolour(int colour, int index);
+extern void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2);
+extern void os_fill(int x, int y, int colour1, int colour2);
+extern void os_show_bitmap(int pic, int x, int y);
+extern Common::SeekableReadStream *os_open_script_file(void);
 
 /* routines provided by level9 interpreter */
-L9BOOL LoadGame(const char *filename, char *picname);
-L9BOOL RunGame(void);
-void StopGame(void);
-void RestoreGame(char *filename);
-void FreeMemory(void);
-void GetPictureSize(int *width, int *height);
-L9BOOL RunGraphics(void);
+extern L9BOOL LoadGame(const char *filename, char *picname);
+extern L9BOOL RunGame(void);
+extern void StopGame(void);
+extern void RestoreGame(char *filename);
+extern void FreeMemory(void);
+extern void GetPictureSize(int *width, int *height);
+extern L9BOOL RunGraphics(void);
 
 /* bitmap routines provided by level9 interpreter */
 BitmapType DetectBitmaps(char *dir);
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 9005b6c..1b65ef1 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -30,9 +30,6 @@ namespace Level9 {
 #define BYTE_MAX 0xff
 #define BITS_PER_CHAR 8
 
-/* File path delimiter, used to be #defined in v2 interpreter. */
-static const char GLN_FILE_DELIM = '/';
-
 /*---------------------------------------------------------------------*/
 /*  Module variables, miscellaneous other stuff                        */
 /*---------------------------------------------------------------------*/
@@ -43,39 +40,32 @@ static const glui32 GLN_PORT_VERSION = 0x00020201;
 /*
  * We use a maximum of three Glk windows, one for status, one for pictures,
  * and one for everything else.  The status and pictures windows may be
- * NULL, depending on user selections and the capabilities of the Glk
+ * nullptr, depending on user selections and the capabilities of the Glk
  * library.
  */
-static winid_t gln_main_window = NULL,
-               gln_status_window = NULL,
-               gln_graphics_window = NULL;
+static winid_t gln_main_window, gln_status_window, gln_graphics_window;
 
 /*
- * Transcript stream and input log.  These are NULL if there is no current
+ * Transcript stream and input log.  These are nullptr if there is no current
  * collection of these strings.
  */
-static strid_t gln_transcript_stream = NULL,
-               gln_inputlog_stream = NULL;
+static strid_t gln_transcript_stream, gln_inputlog_stream;
 
 /* Input read log stream, for reading back an input log. */
-static strid_t gln_readlog_stream = NULL;
+static strid_t gln_readlog_stream;
 
 /* Note about whether graphics is possible, or not. */
-static int gln_graphics_possible = TRUE;
+bool gln_graphics_possible;
 
 /* Options that may be turned off by command line flags. */
-static int gln_graphics_enabled = TRUE,
-           gln_intercept_enabled = TRUE,
-           gln_prompt_enabled = TRUE,
-           gln_loopcheck_enabled = TRUE,
-           gln_abbreviations_enabled = TRUE,
-           gln_commands_enabled = TRUE;
+bool gln_graphics_enabled, gln_intercept_enabled, gln_prompt_enabled;
+bool gln_loopcheck_enabled, gln_abbreviations_enabled, gln_commands_enabled;
 
 /* Reason for stopping the game, used to detect restarts and ^C exits. */
 enum StopReason {
 	STOP_NONE, STOP_FORCE, STOP_RESTART, STOP_EXIT
 };
-static StopReason gln_stop_reason = STOP_NONE;
+static StopReason gln_stop_reason;
 
 /* Level 9 standard input prompt string. */
 static const char *const GLN_INPUT_PROMPT = "> ";
@@ -111,6 +101,23 @@ static int gln_confirm(const char *prompt);
 /*  Glk port utility functions                                         */
 /*---------------------------------------------------------------------*/
 
+void gln_initialize() {
+	gln_main_window = nullptr;
+	gln_status_window = nullptr;
+	gln_graphics_window = nullptr;
+	gln_transcript_stream = nullptr;
+	gln_inputlog_stream = nullptr;
+	gln_readlog_stream = nullptr;
+	gln_graphics_possible = TRUE;
+	gln_graphics_enabled = TRUE;
+	gln_intercept_enabled = TRUE;
+	gln_prompt_enabled = TRUE;
+	gln_loopcheck_enabled = TRUE;
+	gln_abbreviations_enabled = TRUE;
+	gln_commands_enabled = TRUE;
+	gln_stop_reason = STOP_NONE;
+}
+
 /*
  * gln_fatal()
  *
@@ -128,7 +135,7 @@ static void gln_fatal(const char *string) {
 	}
 
 	/* Cancel all possible pending window input events. */
-	g_vm->glk_cancel_line_event(gln_main_window, NULL);
+	g_vm->glk_cancel_line_event(gln_main_window, nullptr);
 	g_vm->glk_cancel_char_event(gln_main_window);
 
 	/* Print a message indicating the error. */
@@ -279,7 +286,7 @@ static gln_uint16 gln_get_buffer_crc(const void *void_buffer, size_t length, siz
  * and may be re-requested when, say, the game changes, perhaps by moving to
  * the next part of a multipart game.
  */
-static const char *gln_gameid_game_name = NULL;
+static const char *gln_gameid_game_name = nullptr;
 
 
 /*
@@ -701,7 +708,7 @@ static const gln_game_table_t GLN_GAME_TABLE[] = {
 
 	{0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
 
-	{0x0000, 0x00, 0x0000, NULL}
+	{0x0000, 0x00, 0x0000, nullptr}
 };
 
 
@@ -1080,7 +1087,7 @@ static const gln_patch_table_t GLN_PATCH_TABLE[] = {
  * gln_gameid_lookup_patch()
  *
  * Look up and return game table and patch table entries given a game's
- * length, checksum, and CRC.  Returns the entry, or NULL if not found.
+ * length, checksum, and CRC.  Returns the entry, or nullptr if not found.
  */
 static gln_game_tableref_t gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
 		gln_uint16 crc, int ignore_crc) {
@@ -1092,7 +1099,7 @@ static gln_game_tableref_t gln_gameid_lookup_game(gln_uint16 length, gln_byte ch
 			break;
 	}
 
-	return game->length ? game : NULL;
+	return game->length ? game : nullptr;
 }
 
 static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
@@ -1105,7 +1112,7 @@ static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte
 			break;
 	}
 
-	return patch->length ? patch : NULL;
+	return patch->length ? patch : nullptr;
 }
 
 
@@ -1113,7 +1120,7 @@ static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte
  * gln_gameid_identify_game()
  *
  * Identify a game from its data length, checksum, and CRC.  Returns the
- * entry of the game in the game table, or NULL if not found.
+ * entry of the game in the game table, or nullptr if not found.
  *
  * This function uses startdata and FileSize from the core interpreter.
  * These aren't advertised symbols, so be warned.
@@ -1127,7 +1134,7 @@ static gln_game_tableref_t gln_gameid_identify_game() {
 
 	/* If the data file appears too short for a header, give up now. */
 	if (FileSize < 30)
-		return NULL;
+		return nullptr;
 
 	/*
 	 * Find the version of the game, and the length of game data.  This logic
@@ -1144,7 +1151,7 @@ static gln_game_tableref_t gln_gameid_identify_game() {
 	         ? startdata[28] | startdata[29] << BITS_PER_CHAR
 	         : startdata[0] | startdata[1] << BITS_PER_CHAR;
 	if (length >= FileSize)
-		return NULL;
+		return nullptr;
 
 	/* Calculate or retrieve the checksum, in a version specific way. */
 	if (is_version2) {
@@ -1184,7 +1191,7 @@ static gln_game_tableref_t gln_gameid_identify_game() {
 /*
  * gln_gameid_get_game_name()
  *
- * Return the name of the game, or NULL if not identifiable.
+ * Return the name of the game, or nullptr if not identifiable.
  *
  * This function uses startdata from the core interpreter.  This isn't an
  * advertised symbol, so be warned.
@@ -1200,20 +1207,20 @@ static const char *gln_gameid_get_game_name() {
 		gln_game_tableref_t game;
 
 		/*
-		 * If the interpreter hasn't yet loaded a game, startdata is NULL
-		 * (uninitialized, global).  In this case, we return NULL, allowing
+		 * If the interpreter hasn't yet loaded a game, startdata is nullptr
+		 * (uninitialized, global).  In this case, we return nullptr, allowing
 		 * for retries until a game is loaded.
 		 */
 		if (!startdata)
-			return NULL;
+			return nullptr;
 
 		game = gln_gameid_identify_game();
 		gln_gameid_game_name = game ? game->name : "";
 	}
 
-	/* Return the game's name, or NULL if it was unidentifiable. */
+	/* Return the game's name, or nullptr if it was unidentifiable. */
 	assert(gln_gameid_game_name);
-	return strlen(gln_gameid_game_name) > 0 ? gln_gameid_game_name : NULL;
+	return strlen(gln_gameid_game_name) > 0 ? gln_gameid_game_name : nullptr;
 }
 
 
@@ -1225,7 +1232,7 @@ static const char *gln_gameid_get_game_name() {
  * change game file, for example os_set_filenumber().
  */
 static void gln_gameid_game_name_reset() {
-	gln_gameid_game_name = NULL;
+	gln_gameid_game_name = nullptr;
 }
 
 
@@ -1307,12 +1314,12 @@ static const int GLN_GRAPHICS_BORDER = 1,
 static const int GLN_GRAPHICS_UNUSED_PIXEL = 0xff;
 
 /* Graphics file directory, and type of graphics found in it. */
-static char *gln_graphics_bitmap_directory = NULL;
+static char *gln_graphics_bitmap_directory = nullptr;
 static BitmapType gln_graphics_bitmap_type = NO_BITMAPS;
 
 /* The current picture id being displayed. */
 enum { GLN_PALETTE_SIZE = 32 };
-static gln_byte *gln_graphics_bitmap = NULL;
+static gln_byte *gln_graphics_bitmap = nullptr;
 static gln_uint16 gln_graphics_width = 0,
                   gln_graphics_height = 0;
 static Colour gln_graphics_palette[GLN_PALETTE_SIZE]; /* = { 0, ... }; */
@@ -1343,8 +1350,8 @@ static GraphicsState gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
  * of pixels, and the other tracking on-screen data.  These are temporary
  * graphics malloc'ed memory, and should be free'd on exit.
  */
-static gln_byte *gln_graphics_off_screen = NULL,
-                 *gln_graphics_on_screen = NULL;
+static gln_byte *gln_graphics_off_screen = nullptr,
+                 *gln_graphics_on_screen = nullptr;
 
 /*
  * The number of colors used in the palette by the current picture.  Because
@@ -1371,19 +1378,19 @@ static int gln_graphics_open() {
 		                      wintype_Graphics, 0);
 	}
 
-	return gln_graphics_window != NULL;
+	return gln_graphics_window != nullptr;
 }
 
 
 /*
  * gln_graphics_close()
  *
- * If open, close the graphics window and set back to NULL.
+ * If open, close the graphics window and set back to nullptr.
  */
 static void gln_graphics_close() {
 	if (gln_graphics_window) {
-		g_vm->glk_window_close(gln_graphics_window, NULL);
-		gln_graphics_window = NULL;
+		g_vm->glk_window_close(gln_graphics_window, nullptr);
+		gln_graphics_window = nullptr;
 	}
 }
 
@@ -1424,7 +1431,7 @@ static void gln_graphics_stop() {
  * Return TRUE if graphics are currently being displayed, FALSE otherwise.
  */
 static int gln_graphics_are_displayed() {
-	return gln_graphics_window != NULL;
+	return gln_graphics_window != nullptr;
 }
 
 
@@ -2263,8 +2270,7 @@ static void gln_graphics_locate_bitmaps(const char *gamefile) {
 	BitmapType bitmap_type;
 
 	/* Find the start of the last element of the filename passed in. */
-	basename = strrchr(gamefile, GLN_FILE_DELIM);
-	basename = basename ? basename + 1 : gamefile;
+	basename = gamefile;
 
 	/* Take a copy of the directory part of the filename. */
 	dirname = (char *)gln_malloc(basename - gamefile + 1);
@@ -2438,7 +2444,7 @@ void os_show_bitmap(int picture, int x, int y) {
  * FALSE if there is no picture available to display.
  */
 static int gln_graphics_picture_is_available() {
-	return gln_graphics_bitmap != NULL;
+	return gln_graphics_bitmap != nullptr;
 }
 
 
@@ -2482,7 +2488,7 @@ static int gln_graphics_get_rendering_details(const char **bitmap_type,
 	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
 		/*
 		 * Convert the detected bitmap type into a string and return it.
-		 * A NULL bitmap string implies no bitmaps.
+		 * A nullptr bitmap string implies no bitmaps.
 		 */
 		if (bitmap_type) {
 			const char *return_type;
@@ -2517,7 +2523,7 @@ static int gln_graphics_get_rendering_details(const char **bitmap_type,
 				break;
 			case NO_BITMAPS:
 			default:
-				return_type = NULL;
+				return_type = nullptr;
 				break;
 			}
 
@@ -2562,13 +2568,13 @@ static int gln_graphics_interpreter_enabled() {
  */
 static void gln_graphics_cleanup() {
 	free(gln_graphics_bitmap);
-	gln_graphics_bitmap = NULL;
+	gln_graphics_bitmap = nullptr;
 	free(gln_graphics_off_screen);
-	gln_graphics_off_screen = NULL;
+	gln_graphics_off_screen = nullptr;
 	free(gln_graphics_on_screen);
-	gln_graphics_on_screen = NULL;
+	gln_graphics_on_screen = nullptr;
 	free(gln_graphics_bitmap_directory);
-	gln_graphics_bitmap_directory = NULL;
+	gln_graphics_bitmap_directory = nullptr;
 
 	gln_graphics_bitmap_type = NO_BITMAPS;
 	gln_graphics_picture = -1;
@@ -2607,7 +2613,7 @@ struct gln_linegraphics_segment_t {
 	int dy;  /* Segment y delta */
 };
 
-static gln_linegraphics_segment_t *gln_linegraphics_fill_segments = NULL;
+static gln_linegraphics_segment_t *gln_linegraphics_fill_segments = nullptr;
 static int gln_linegraphics_fill_segments_allocation = 0,
            gln_linegraphics_fill_segments_length = 0;
 
@@ -3001,7 +3007,7 @@ static void gln_linegraphics_process() {
  */
 static void gln_linegraphics_cleanup() {
 	free(gln_linegraphics_fill_segments);
-	gln_linegraphics_fill_segments = NULL;
+	gln_linegraphics_fill_segments = nullptr;
 
 	gln_linegraphics_fill_segments_allocation = 0;
 	gln_linegraphics_fill_segments_length = 0;
@@ -3383,7 +3389,7 @@ static void gln_status_redraw() {
 		 */
 		parent = g_vm->glk_window_get_parent(gln_status_window);
 		g_vm->glk_window_set_arrangement(parent,
-		                                 winmethod_Above | winmethod_Fixed, 1, NULL);
+		                                 winmethod_Above | winmethod_Fixed, 1, nullptr);
 
 		gln_status_update();
 	}
@@ -3406,7 +3412,7 @@ static int gln_help_requested = FALSE,
  * more efficient for everyone if we buffer them, and output a complete
  * string on a flush call.
  */
-static char *gln_output_buffer = NULL;
+static char *gln_output_buffer = nullptr;
 static int gln_output_allocation = 0,
            gln_output_length = 0;
 
@@ -3532,7 +3538,7 @@ static void gln_detect_game_prompt() {
  */
 static void gln_output_delete() {
 	free(gln_output_buffer);
-	gln_output_buffer = NULL;
+	gln_output_buffer = nullptr;
 	gln_output_allocation = gln_output_length = 0;
 }
 
@@ -3727,10 +3733,10 @@ static void gln_command_script(const char *argument) {
 			return;
 		}
 
-		g_vm->glk_stream_close(gln_transcript_stream, NULL);
-		gln_transcript_stream = NULL;
+		g_vm->glk_stream_close(gln_transcript_stream, nullptr);
+		gln_transcript_stream = nullptr;
 
-		g_vm->glk_window_set_echo_stream(gln_main_window, NULL);
+		g_vm->glk_window_set_echo_stream(gln_main_window, nullptr);
 
 		gln_normal_string("Glk transcript is now off.\n");
 	}
@@ -3792,8 +3798,8 @@ static void gln_command_inputlog(const char *argument) {
 			return;
 		}
 
-		g_vm->glk_stream_close(gln_inputlog_stream, NULL);
-		gln_inputlog_stream = NULL;
+		g_vm->glk_stream_close(gln_inputlog_stream, nullptr);
+		gln_inputlog_stream = nullptr;
 
 		gln_normal_string("Glk input log is now off.\n");
 	}
@@ -3860,8 +3866,8 @@ static void gln_command_readlog(const char *argument) {
 			return;
 		}
 
-		g_vm->glk_stream_close(gln_readlog_stream, NULL);
-		gln_readlog_stream = NULL;
+		g_vm->glk_stream_close(gln_readlog_stream, nullptr);
+		gln_readlog_stream = nullptr;
 
 		gln_normal_string("Glk read log is now off.\n");
 	}
@@ -4268,7 +4274,7 @@ static const gln_command_t GLN_COMMAND_TABLE[] = {
 	{"version",        gln_command_version,        FALSE},
 	{"commands",       gln_command_commands,       TRUE},
 	{"help",           gln_command_help,           TRUE},
-	{NULL, NULL, FALSE}
+	{nullptr, nullptr, FALSE}
 };
 
 
@@ -4323,7 +4329,7 @@ static void gln_command_help(const char *command) {
 		return;
 	}
 
-	matched = NULL;
+	matched = nullptr;
 	for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
 		if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
 			if (matched) {
@@ -4531,7 +4537,7 @@ static int gln_command_escape(const char *string) {
 		 * the command passed in.
 		 */
 		matches = 0;
-		matched = NULL;
+		matched = nullptr;
 		for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
 			if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
 				matches++;
@@ -4696,7 +4702,7 @@ static const gln_abbreviation_t GLN_ABBREVIATIONS[] = {
 	{'k', "attack"},   {'l', "look"},   {'p', "open"},
 	{'q', "quit"},     {'r', "drop"},   {'t', "take"},
 	{'x', "examine"},  {'y', "yes"},    {'z', "wait"},
-	{'\0', NULL}
+	{'\0', nullptr}
 };
 
 
@@ -4720,7 +4726,7 @@ static void gln_expand_abbreviations(char *buffer, int size) {
 
 	/* Scan the abbreviations table for a match. */
 	abbreviation = g_vm->glk_char_to_lower((unsigned char) command[0]);
-	expansion = NULL;
+	expansion = nullptr;
 	for (entry = GLN_ABBREVIATIONS; entry->expansion; entry++) {
 		if (entry->abbreviation == abbreviation) {
 			expansion = entry->expansion;
@@ -4828,8 +4834,8 @@ gln_bool os_input(char *buffer, int size) {
 		 * We're at the end of the log stream.  Close it, and then continue
 		 * on to request a line from Glk.
 		 */
-		g_vm->glk_stream_close(gln_readlog_stream, NULL);
-		gln_readlog_stream = NULL;
+		g_vm->glk_stream_close(gln_readlog_stream, nullptr);
+		gln_readlog_stream = nullptr;
 	}
 
 	/*
@@ -5289,7 +5295,7 @@ gln_bool os_save_file(gln_byte *ptr, int bytes) {
 	/* Write game state. */
 	g_vm->glk_put_buffer_stream(stream, (const char *)ptr, bytes);
 
-	g_vm->glk_stream_close(stream, NULL);
+	g_vm->glk_stream_close(stream, nullptr);
 	g_vm->glk_fileref_destroy(fileref);
 
 	gln_watchdog_tick();
@@ -5331,7 +5337,7 @@ gln_bool os_load_file(gln_byte *ptr, int *bytes, int max) {
 	/* Restore saved game data. */
 	*bytes = g_vm->glk_get_buffer_stream(stream, (char *)ptr, max);
 
-	g_vm->glk_stream_close(stream, NULL);
+	g_vm->glk_stream_close(stream, nullptr);
 	g_vm->glk_fileref_destroy(fileref);
 
 	gln_watchdog_tick();
@@ -5372,9 +5378,7 @@ gln_bool os_get_game_file(char *newname, int size) {
 	Common::File f;
 	assert(newname);
 
-	/* Find the last element of the filename passed in. */
-	basename = strrchr(newname, GLN_FILE_DELIM);
-	basename = basename ? basename + 1 : newname;
+	basename = newname;
 
 	/* Search for the last numeric character in the basename. */
 	digit = -1;
@@ -5453,9 +5457,7 @@ void os_set_filenumber(char *newname, int size, int file_number) {
 		return;
 	}
 
-	/* Find the last element of the new filename. */
-	basename = strrchr(newname, GLN_FILE_DELIM);
-	basename = basename ? basename + 1 : newname;
+	basename = newname;
 
 	/* Search for the last numeric character in the basename. */
 	digit = -1;
@@ -5493,7 +5495,7 @@ void os_set_filenumber(char *newname, int size, int file_number) {
  * own way of handling scripts, this function is a stub.
  */
 Common::SeekableReadStream *os_open_script_file() {
-	return NULL;
+	return nullptr;
 }
 
 
@@ -5546,8 +5548,7 @@ static const int GLN_WATCHDOG_TIMEOUT = 5,
  * The following values need to be passed between the startup_code and main
  * functions.
  */
-static const char *gln_game_message = NULL;  /* Error message. */
-
+static const char *gln_game_message = nullptr;  /* Error message. */
 
 /*
  * gln_establish_picture_filename()
@@ -5557,7 +5558,7 @@ static const char *gln_game_message = NULL;  /* Error message. */
  * PICTURE.DAT or picture.dat in the same directory as X.  If the input file
  * already ends with a three-letter extension, it's stripped first.
  *
- * The function returns NULL if a graphics file is not available.  It's not
+ * The function returns nullptr if a graphics file is not available.  It's not
  * fatal for this to be the case.  Filenames are malloc'ed, and need to be
  * freed by the caller.
  *
@@ -5565,7 +5566,7 @@ static const char *gln_game_message = NULL;  /* Error message. */
  * standard function, and access() isn't.
  */
 static void gln_establish_picture_filename(const char *name, char **graphics) {
-	char *base, *directory_end, *graphics_file;
+	char *base, *graphics_file;
 	Common::File f;
 	assert(name && graphics);
 
@@ -5623,7 +5624,7 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
 	/* No access to graphics file. */
 	if (!f.isOpen()) {
 		free(graphics_file);
-		graphics_file = NULL;
+		graphics_file = nullptr;
 	}
 
 	f.close();
@@ -5635,11 +5636,6 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
 		return;
 	}
 
-	/* Retry with base set to the game file directory part only. */
-	directory_end = strrchr(base, GLN_FILE_DELIM);
-	directory_end = directory_end ? directory_end + 1 : base;
-	base[directory_end - base] = '\0';
-
 	/* Again, allocate space for the return graphics file. */
 	graphics_file = (char *)gln_malloc(strlen(base) + strlen("PICTURE.DAT") + 1);
 
@@ -5654,24 +5650,23 @@ static void gln_establish_picture_filename(const char *name, char **graphics) {
 		if (!f.open(graphics_file)) {
 			/*
 			 * No access to this graphics file.  In this case, free memory
-			 * and reset graphics file to NULL.
+			 * and reset graphics file to nullptr.
 			 */
 			free(graphics_file);
-			graphics_file = NULL;
+			graphics_file = nullptr;
 		}
 	}
 
 	f.close();
 
 	/*
-	 * Return whatever we found for the graphics file (NULL if none found),
+	 * Return whatever we found for the graphics file (nullptr if none found),
 	 * and free base.
 	 */
 	*graphics = graphics_file;
 	free(base);
 }
 
-
 /*
  * gln_startup_code()
  * gln_main()
@@ -5719,7 +5714,7 @@ int gln_startup_code(int argc, char *argv[]) {
 }
 
 void gln_main(const char *filename) {
-	char *graphics_file = NULL;
+	char *graphics_file = nullptr;
 	int is_running;
 
 	/* Create the main Glk window, and set its stream as current. */
@@ -5780,7 +5775,7 @@ void gln_main(const char *filename) {
 		int errNum = 0;
 		if (!LoadGame(filename, graphics_file)) {
 			if (gln_status_window)
-				g_vm->glk_window_close(gln_status_window, NULL);
+				g_vm->glk_window_close(gln_status_window, nullptr);
 			gln_header_string("Glk Level 9 Error\n\n");
 			gln_normal_string("Can't find, open, or load game file '");
 			gln_normal_string(filename);
@@ -5867,16 +5862,16 @@ void gln_main(const char *filename) {
 
 	/* Close any open transcript, input log, and/or read log. */
 	if (gln_transcript_stream) {
-		g_vm->glk_stream_close(gln_transcript_stream, NULL);
-		gln_transcript_stream = NULL;
+		g_vm->glk_stream_close(gln_transcript_stream, nullptr);
+		gln_transcript_stream = nullptr;
 	}
 	if (gln_inputlog_stream) {
-		g_vm->glk_stream_close(gln_inputlog_stream, NULL);
-		gln_inputlog_stream = NULL;
+		g_vm->glk_stream_close(gln_inputlog_stream, nullptr);
+		gln_inputlog_stream = nullptr;
 	}
 	if (gln_readlog_stream) {
-		g_vm->glk_stream_close(gln_readlog_stream, NULL);
-		gln_readlog_stream = NULL;
+		g_vm->glk_stream_close(gln_readlog_stream, nullptr);
+		gln_readlog_stream = nullptr;
 	}
 
 	/* Free any graphics file path. */
diff --git a/engines/glk/level9/os_glk.h b/engines/glk/level9/os_glk.h
new file mode 100644
index 0000000..a10a640
--- /dev/null
+++ b/engines/glk/level9/os_glk.h
@@ -0,0 +1,39 @@
+/* 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_LEVEL9_OS_GLK
+#define GLK_LEVEL9_OS_GLK
+
+namespace Glk {
+namespace Level9 {
+
+extern bool gln_graphics_enabled;
+extern bool gln_graphics_possible;
+
+extern void gln_initialize();
+extern void gln_main(const char *filename);
+extern int gln_startup_code(int argc, char *argv[]);
+
+} // End of namespace Alan2
+} // End of namespace Glk
+
+#endif


Commit: d923ed3c5fbb1aad03c82f29cf93412215ae662e
    https://github.com/scummvm/scummvm/commit/d923ed3c5fbb1aad03c82f29cf93412215ae662e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Fixes for exiting game by closing window

Changed paths:
    engines/glk/level9/level9_main.cpp
    engines/glk/level9/os_glk.cpp


diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 043266a..2ce60a5 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -3559,6 +3559,10 @@ L9BOOL RunGame() {
 	code = *codeptr++;
 	/*  printf("%d",code); */
 	executeinstruction();
+
+	if (g_vm->shouldQuit())
+		Running = false;
+
 	return Running;
 }
 
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 1b65ef1..7722507 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -4844,6 +4844,11 @@ gln_bool os_input(char *buffer, int size) {
 	 */
 	g_vm->glk_request_line_event(gln_main_window, buffer, size - 1, 0);
 	gln_event_wait(evtype_LineInput, &event);
+	if (g_vm->shouldQuit()) {
+		g_vm->glk_cancel_line_event(gln_main_window, &event);
+		gln_stop_reason = STOP_EXIT;
+		return FALSE;
+	}
 
 	/* Terminate the input line with a NUL. */
 	assert((int)event.val1 <= size - 1);


Commit: fa323c6187ac2a698805c07f6dd2b906e9c117ee
    https://github.com/scummvm/scummvm/commit/fa323c6187ac2a698805c07f6dd2b906e9c117ee
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Further variable initialization

Changed paths:
    engines/glk/level9/os_glk.cpp


diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 7722507..8c97c5c 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -96,6 +96,56 @@ static void gln_standout_string(const char *message);
 
 static int gln_confirm(const char *prompt);
 
+/* Picture variables */
+/* Graphics file directory, and type of graphics found in it. */
+static char *gln_graphics_bitmap_directory = nullptr;
+static BitmapType gln_graphics_bitmap_type = NO_BITMAPS;
+
+/* The current picture id being displayed. */
+enum { GLN_PALETTE_SIZE = 32 };
+static gln_byte *gln_graphics_bitmap = nullptr;
+static gln_uint16 gln_graphics_width = 0,
+gln_graphics_height = 0;
+static Colour gln_graphics_palette[GLN_PALETTE_SIZE]; /* = { 0, ... }; */
+static int gln_graphics_picture = -1;
+
+/*
+ * Flags set on new picture, and on resize or arrange events, and a flag
+ * to indicate whether background repaint is stopped or active.
+ */
+static int gln_graphics_new_picture = FALSE,
+gln_graphics_repaint = FALSE,
+gln_graphics_active = FALSE;
+
+/*
+ * State to monitor the state of interpreter graphics.  The values of the
+ * enumerations match the modes supplied by os_graphics().
+ */
+enum GraphicsState {
+	GLN_GRAPHICS_OFF = 0,
+	GLN_GRAPHICS_LINE_MODE = 1,
+	GLN_GRAPHICS_BITMAP_MODE = 2
+};
+static GraphicsState gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
+
+
+/*
+ * Pointer to the two graphics buffers, one the off-screen representation
+ * of pixels, and the other tracking on-screen data.  These are temporary
+ * graphics malloc'ed memory, and should be free'd on exit.
+ */
+static gln_byte *gln_graphics_off_screen = nullptr,
+*gln_graphics_on_screen = nullptr;
+
+/*
+ * The number of colors used in the palette by the current picture.  Because
+ * of the way it's queried, we risk a race, with admittedly a very low
+ * probability, with the updater.  So, it's initialized instead to the
+ * largest possible value.  The real value in use is inserted on the first
+ * picture update timeout call for a new picture.
+ */
+static int gln_graphics_color_count = GLN_PALETTE_SIZE;
+
 
 /*---------------------------------------------------------------------*/
 /*  Glk port utility functions                                         */
@@ -116,6 +166,20 @@ void gln_initialize() {
 	gln_abbreviations_enabled = TRUE;
 	gln_commands_enabled = TRUE;
 	gln_stop_reason = STOP_NONE;
+
+	gln_graphics_bitmap_directory = nullptr;
+	gln_graphics_bitmap_type = NO_BITMAPS;
+	gln_graphics_bitmap = nullptr;
+	gln_graphics_width = 0;
+	gln_graphics_height = 0;
+	gln_graphics_picture = -1;
+	gln_graphics_new_picture = FALSE;
+	gln_graphics_repaint = FALSE;
+	gln_graphics_active = FALSE;
+	gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
+	gln_graphics_off_screen = nullptr;
+	gln_graphics_on_screen = nullptr;
+	gln_graphics_color_count = GLN_PALETTE_SIZE;
 }
 
 /*
@@ -1313,55 +1377,6 @@ static const int GLN_GRAPHICS_BORDER = 1,
  */
 static const int GLN_GRAPHICS_UNUSED_PIXEL = 0xff;
 
-/* Graphics file directory, and type of graphics found in it. */
-static char *gln_graphics_bitmap_directory = nullptr;
-static BitmapType gln_graphics_bitmap_type = NO_BITMAPS;
-
-/* The current picture id being displayed. */
-enum { GLN_PALETTE_SIZE = 32 };
-static gln_byte *gln_graphics_bitmap = nullptr;
-static gln_uint16 gln_graphics_width = 0,
-                  gln_graphics_height = 0;
-static Colour gln_graphics_palette[GLN_PALETTE_SIZE]; /* = { 0, ... }; */
-static int gln_graphics_picture = -1;
-
-/*
- * Flags set on new picture, and on resize or arrange events, and a flag
- * to indicate whether background repaint is stopped or active.
- */
-static int gln_graphics_new_picture = FALSE,
-           gln_graphics_repaint = FALSE,
-           gln_graphics_active = FALSE;
-
-/*
- * State to monitor the state of interpreter graphics.  The values of the
- * enumerations match the modes supplied by os_graphics().
- */
-enum GraphicsState {
-	GLN_GRAPHICS_OFF = 0,
-	GLN_GRAPHICS_LINE_MODE = 1,
-	GLN_GRAPHICS_BITMAP_MODE = 2
-};
-static GraphicsState gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
-
-
-/*
- * Pointer to the two graphics buffers, one the off-screen representation
- * of pixels, and the other tracking on-screen data.  These are temporary
- * graphics malloc'ed memory, and should be free'd on exit.
- */
-static gln_byte *gln_graphics_off_screen = nullptr,
-                 *gln_graphics_on_screen = nullptr;
-
-/*
- * The number of colors used in the palette by the current picture.  Because
- * of the way it's queried, we risk a race, with admittedly a very low
- * probability, with the updater.  So, it's initialized instead to the
- * largest possible value.  The real value in use is inserted on the first
- * picture update timeout call for a new picture.
- */
-static int gln_graphics_color_count = GLN_PALETTE_SIZE;
-
 
 /*
  * gln_graphics_open()


Commit: 07c8437e268b12c0633495579f56c4e08e8ef7d2
    https://github.com/scummvm/scummvm/commit/07c8437e268b12c0633495579f56c4e08e8ef7d2
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Title screen graphics now showing

Changed paths:
    engines/glk/glk_api.cpp
    engines/glk/glk_api.h
    engines/glk/level9/level9.cpp
    engines/glk/level9/os_glk.cpp
    engines/glk/window_graphics.cpp
    engines/glk/window_graphics.h


diff --git a/engines/glk/glk_api.cpp b/engines/glk/glk_api.cpp
index d86bd26..6d34e3a 100644
--- a/engines/glk/glk_api.cpp
+++ b/engines/glk/glk_api.cpp
@@ -911,6 +911,39 @@ bool GlkAPI::glk_image_draw_scaled(winid_t win, uint image, int val1, int val2,
 	return false;
 }
 
+bool GlkAPI::glk_image_draw(winid_t win, const Graphics::Surface &image, uint transColor,
+		int xp, int yp) {
+	if (!win) {
+		warning("image_draw: invalid ref");
+	} else if (g_conf->_graphics) {
+		GraphicsWindow *gfxWin = dynamic_cast<GraphicsWindow *>(win);
+
+		if (gfxWin)
+			gfxWin->drawPicture(image, 0xff, xp, yp, 0, 0);
+	}
+
+	return true;
+}
+
+bool GlkAPI::glk_image_draw_scaled(winid_t win, const Graphics::Surface &image, uint transColor,
+		int xp, int yp, uint width, uint height) {
+	if (!win) {
+		warning("image_draw_scaled: invalid ref");
+	} else if (g_conf->_graphics) {
+		GraphicsWindow *gfxWin = dynamic_cast<GraphicsWindow *>(win);
+
+		Graphics::ManagedSurface s(width, height);
+		s.clear(transColor);
+		s.transBlitFrom(image, Common::Rect(0, 0, image.w, image.h),
+			Common::Rect(0, 0, width, height), transColor);
+
+		if (gfxWin)
+			gfxWin->drawPicture(s, transColor, xp, yp, s.w, s.h);
+	}
+
+	return true;
+}
+
 bool GlkAPI::glk_image_get_info(uint image, uint *width, uint *height) {
 	if (!g_conf->_graphics)
 		return false;
diff --git a/engines/glk/glk_api.h b/engines/glk/glk_api.h
index 6ddeb47..a0efb30 100644
--- a/engines/glk/glk_api.h
+++ b/engines/glk/glk_api.h
@@ -198,7 +198,12 @@ public:
 
 	bool glk_image_draw(winid_t win, uint image, int val1, int val2);
 	bool glk_image_draw_scaled(winid_t win, uint image,
-	                             int val1, int val2, uint width, uint height);
+		int val1, int val2, uint width, uint height);
+	bool glk_image_draw(winid_t win, const Graphics::Surface &image, uint transColor = (uint)-1,
+		int xp = 0, int yp = 0);
+	bool glk_image_draw_scaled(winid_t win, const Graphics::Surface &image, uint transColor,
+		int xp, int yp, uint width, uint height);
+
 	bool glk_image_get_info(uint image, uint *width, uint *height);
 
 	void glk_window_flow_break(winid_t win);
diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
index be4ac22..7fe85ee 100644
--- a/engines/glk/level9/level9.cpp
+++ b/engines/glk/level9/level9.cpp
@@ -36,7 +36,6 @@ Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst,
 void Level9::runGame() {
 	initialize();
 
-
 	_gameFile.close();
 	gln_main(getFilename().c_str());
 
@@ -44,6 +43,7 @@ void Level9::runGame() {
 }
 
 bool Level9::initialize() {
+	gln_initialize();
 	return gln_startup_code(0, nullptr);
 }
 
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 8c97c5c..b2ac4ac 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -1984,21 +1984,23 @@ break_y_max:
 }
 #endif
 
-static void gln_graphics_paint_everything(winid_t glk_window, glui32 palette[],
+static void gln_graphics_paint_everything(winid_t glk_window, Colour palette[],
 		gln_byte off_screen[], int x_offset, int y_offset, gln_uint16 width, gln_uint16 height) {
-	gln_byte        pixel;          /* Reference pixel color */
-	int     x, y;
-
-	for (y = 0; y < height; y++) {
-		for (x = 0; x < width; x ++) {
-			pixel = off_screen[ y * width + x ];
-			g_vm->glk_window_fill_rect(glk_window,
-			                           palette[ pixel ],
-			                           x * GLN_GRAPHICS_PIXEL + x_offset,
-			                           y * GLN_GRAPHICS_PIXEL + y_offset,
-			                           GLN_GRAPHICS_PIXEL, GLN_GRAPHICS_PIXEL);
+	Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 0);
+	Graphics::ManagedSurface s(width, height, format);
+
+	for (int y = 0; y < height; ++y) {
+		uint32 *lineP = (uint32 *)s.getBasePtr(0, y);
+		for (int x = 0; x < width; ++x, ++lineP) {
+			byte pixel = off_screen[y * width + x];
+			assert(pixel < GLN_PALETTE_SIZE);
+			const Colour &col = palette[pixel];
+
+			*lineP = format.RGBToColor(col.red, col.green, col.blue);
 		}
 	}
+
+	g_vm->glk_image_draw(glk_window, s, (uint)-1, x_offset, y_offset);
 }
 
 /*
@@ -2260,12 +2262,8 @@ static void gln_graphics_timeout() {
 	total_regions += regions;
 
 #else
-	gln_graphics_paint_everything
-	(gln_graphics_window,
-	 palette, off_screen,
-	 x_offset, y_offset,
-	 gln_graphics_width,
-	 gln_graphics_height);
+	gln_graphics_paint_everything(gln_graphics_window, gln_graphics_palette, off_screen,
+		x_offset, y_offset, gln_graphics_width, gln_graphics_height);
 #endif
 
 	/* Stop graphics; there's no more to be done until something restarts us. */
diff --git a/engines/glk/window_graphics.cpp b/engines/glk/window_graphics.cpp
index 572d8d9..f9fd43a 100644
--- a/engines/glk/window_graphics.cpp
+++ b/engines/glk/window_graphics.cpp
@@ -96,7 +96,7 @@ void GraphicsWindow::redraw() {
 	}
 }
 
-uint GraphicsWindow::drawPicture(uint image, int xpos, int ypos, int scale,
+bool GraphicsWindow::drawPicture(uint image, int xpos, int ypos, bool scale,
                                    uint imagewidth, uint imageheight) {
 	Picture *pic = g_vm->_pictures->load(image);
 	uint hyperlink = _attr.hyper;
@@ -177,26 +177,31 @@ void GraphicsWindow::fillRect(uint color, const Rect &box) {
 	touch();
 }
 
-void GraphicsWindow::drawPicture(Picture *src,  int x0, int y0, int width, int height, uint linkval) {
-	int dx1, dy1, x1, y1, sx0, sy0, sx1, sy1;
-	int hx0, hx1, hy0, hy1;
-	int w, h;
-
+void GraphicsWindow::drawPicture(Picture *src, int x0, int y0, int width, int height, uint linkval) {
 	if (width != src->w || height != src->h) {
 		src = g_vm->_pictures->scale(src, width, height);
 		if (!src)
 			return;
 	}
 
+	drawPicture(*src, src->getTransparentColor(), x0, y0, width, height, linkval);
+}
+
+void GraphicsWindow::drawPicture(const Graphics::Surface &image, uint transColor, int x0, int y0,
+		int width, int height, uint linkval) {
+	int dx1, dy1, x1, y1, sx0, sy0, sx1, sy1;
+	int hx0, hx1, hy0, hy1;
+	int w, h;
+
 	sx0 = 0;
 	sy0 = 0;
-	sx1 = src->w;
-	sy1 = src->h;
+	sx1 = image.w;
+	sy1 = image.h;
 	dx1 = _w;
 	dy1 = _h;
 
-	x1 = x0 + src->w;
-	y1 = y0 + src->h;
+	x1 = x0 + image.w;
+	y1 = y0 + image.h;
 
 	if (x1 <= 0 || x0 >= dx1) return;
 	if (y1 <= 0 || y0 >= dy1) return;
@@ -228,7 +233,7 @@ void GraphicsWindow::drawPicture(Picture *src,  int x0, int y0, int width, int h
 	w = sx1 - sx0;
 	h = sy1 - sy0;
 
-	_surface->transBlitFrom(*src, Rect(sx0, sy0, sx0 + w, sy0 + h), Point(x0, y0), src->getTransparentColor());
+	_surface->transBlitFrom(image, Rect(sx0, sy0, sx0 + w, sy0 + h), Point(x0, y0), transColor);
 }
 
 void GraphicsWindow::getSize(uint *width, uint *height) const {
diff --git a/engines/glk/window_graphics.h b/engines/glk/window_graphics.h
index a3fe6db..59b1ce4 100644
--- a/engines/glk/window_graphics.h
+++ b/engines/glk/window_graphics.h
@@ -52,8 +52,10 @@ public:
 	 */
 	virtual ~GraphicsWindow();
 
-	uint drawPicture(uint image, int xpos, int ypos, int scale,
+	bool drawPicture(uint image, int xpos, int ypos, bool scale,
 	                   uint imagewidth, uint imageheight);
+	void drawPicture(const Graphics::Surface &image, uint transColor, int x0, int y0,
+		int width, int height, uint linkval = 0);
 
 	/**
 	 * Rearranges the window


Commit: 4332df2bda8d8f3942d81db3051f780d2f1bf02a
    https://github.com/scummvm/scummvm/commit/4332df2bda8d8f3942d81db3051f780d2f1bf02a
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Allow for future graphic scaling

Changed paths:
    engines/glk/glk_api.cpp
    engines/glk/level9/os_glk.cpp


diff --git a/engines/glk/glk_api.cpp b/engines/glk/glk_api.cpp
index 6d34e3a..ba05988 100644
--- a/engines/glk/glk_api.cpp
+++ b/engines/glk/glk_api.cpp
@@ -930,15 +930,19 @@ bool GlkAPI::glk_image_draw_scaled(winid_t win, const Graphics::Surface &image,
 	if (!win) {
 		warning("image_draw_scaled: invalid ref");
 	} else if (g_conf->_graphics) {
-		GraphicsWindow *gfxWin = dynamic_cast<GraphicsWindow *>(win);
+		if (image.w == width && image.h == height) {
+			return glk_image_draw(win, image, transColor, xp, yp);
 
-		Graphics::ManagedSurface s(width, height);
-		s.clear(transColor);
-		s.transBlitFrom(image, Common::Rect(0, 0, image.w, image.h),
-			Common::Rect(0, 0, width, height), transColor);
+		} else {
+			GraphicsWindow *gfxWin = dynamic_cast<GraphicsWindow *>(win);
 
-		if (gfxWin)
-			gfxWin->drawPicture(s, transColor, xp, yp, s.w, s.h);
+			Graphics::ManagedSurface s(width, height, image.format);
+			s.transBlitFrom(image, Common::Rect(0, 0, image.w, image.h),
+				Common::Rect(0, 0, width, height));
+
+			if (gfxWin)
+				gfxWin->drawPicture(s, transColor, xp, yp, s.w, s.h);
+		}
 	}
 
 	return true;
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index b2ac4ac..586b808 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -2000,7 +2000,8 @@ static void gln_graphics_paint_everything(winid_t glk_window, Colour palette[],
 		}
 	}
 
-	g_vm->glk_image_draw(glk_window, s, (uint)-1, x_offset, y_offset);
+	g_vm->glk_image_draw_scaled(glk_window, s, (uint)-1, x_offset, y_offset,
+		width * GLN_GRAPHICS_PIXEL, height * GLN_GRAPHICS_PIXEL);
 }
 
 /*


Commit: 0facfc2b2497cc61e86582f72dbab1dad1b1b83b
    https://github.com/scummvm/scummvm/commit/0facfc2b2497cc61e86582f72dbab1dad1b1b83b
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:29-07:00

Commit Message:
GLK: LEVEL9: Moved pre-existing detection code into separate class

Changed paths:
    engines/glk/level9/detection.cpp
    engines/glk/level9/detection.h
    engines/glk/level9/detection_tables.h
    engines/glk/level9/level9.cpp
    engines/glk/level9/level9.h
    engines/glk/level9/level9_main.cpp
    engines/glk/level9/level9_main.h
    engines/glk/level9/os_glk.cpp
    engines/glk/level9/os_glk.h


diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
index 9904091..2648db8 100644
--- a/engines/glk/level9/detection.cpp
+++ b/engines/glk/level9/detection.cpp
@@ -22,6 +22,7 @@
 
 #include "glk/level9/detection.h"
 #include "glk/level9/detection_tables.h"
+#include "glk/level9/os_glk.h"
 #include "glk/blorb.h"
 #include "common/debug.h"
 #include "common/file.h"
@@ -31,6 +32,176 @@
 namespace Glk {
 namespace Level9 {
 
+GameDetection::GameDetection(byte *&startData, size_t &fileSize) :
+		_startData(startData), _fileSize(fileSize), _crcInitialized(false), _gameName(nullptr) {
+	Common::fill(&_crcTable[0], &_crcTable[256], 0);
+}
+
+gln_game_tableref_t GameDetection::gln_gameid_identify_game() {
+	uint16 length, crc;
+	byte checksum;
+	int is_version2;
+	gln_game_tableref_t game;
+	gln_patch_tableref_t patch;
+
+	/* If the data file appears too short for a header, give up now. */
+	if (_fileSize < 30)
+		return nullptr;
+
+	/*
+	 * Find the version of the game, and the length of game data.  This logic
+	 * is taken from L9cut, with calcword() replaced by simple byte comparisons.
+	 * If the length exceeds the available data, fail.
+	 */
+	assert(_startData);
+	is_version2 = _startData[4] == 0x20 && _startData[5] == 0x00
+		&& _startData[10] == 0x00 && _startData[11] == 0x80
+		&& _startData[20] == _startData[22]
+		&& _startData[21] == _startData[23];
+
+	length = is_version2
+		? _startData[28] | _startData[29] << BITS_PER_CHAR
+		: _startData[0] | _startData[1] << BITS_PER_CHAR;
+	if (length >= _fileSize)
+		return nullptr;
+
+	/* Calculate or retrieve the checksum, in a version specific way. */
+	if (is_version2) {
+		int index;
+
+		checksum = 0;
+		for (index = 0; index < length + 1; index++)
+			checksum += _startData[index];
+	}
+	else
+		checksum = _startData[length];
+
+	/*
+	 * Generate a CRC for this data.  When L9cut calculates a CRC, it's using a
+	 * copy taken up to length + 1 and then padded with two NUL bytes, so we
+	 * mimic that here.
+	 */
+	crc = gln_get_buffer_crc(_startData, length + 1, 2);
+
+	/*
+	 * See if this is a patched file.  If it is, look up the game based on the
+	 * original CRC and checksum.  If not, use the current CRC and checksum.
+	 */
+	patch = gln_gameid_lookup_patch(length, checksum, crc);
+	game = gln_gameid_lookup_game(length,
+		patch ? patch->orig_checksum : checksum,
+		patch ? patch->orig_crc : crc,
+		false);
+
+	/* If no game identified, retry without the CRC.  This is guesswork. */
+	if (!game)
+		game = gln_gameid_lookup_game(length, checksum, crc, true);
+
+	return game;
+}
+
+// CRC table initialization polynomial
+static const uint16 GLN_CRC_POLYNOMIAL = 0xa001;
+
+uint16 GameDetection::gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding) {
+
+	const char *buffer = (const char *)void_buffer;
+	uint16 crc;
+	size_t index;
+
+	/* Build the static CRC lookup table on first call. */
+	if (!_crcInitialized) {
+		for (index = 0; index < BYTE_MAX + 1; index++) {
+			int bit;
+
+			crc = (uint16)index;
+			for (bit = 0; bit < BITS_PER_CHAR; bit++)
+				crc = crc & 1 ? GLN_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
+
+			_crcTable[index] = crc;
+		}
+
+		_crcInitialized = true;
+
+		/* CRC lookup table self-test, after is_initialized set -- recursion. */
+		assert(gln_get_buffer_crc("123456789", 9, 0) == 0xbb3d);
+	}
+
+	/* Start with zero in the crc, then update using table entries. */
+	crc = 0;
+	for (index = 0; index < length; index++)
+		crc = _crcTable[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
+
+	/* Add in any requested NUL padding bytes. */
+	for (index = 0; index < padding; index++)
+		crc = _crcTable[crc & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
+
+	return crc;
+}
+
+gln_game_tableref_t GameDetection::gln_gameid_lookup_game(uint16 length, byte checksum, uint16 crc, int ignore_crc) const {
+	gln_game_tableref_t game;
+
+	for (game = GLN_GAME_TABLE; game->length; game++) {
+		if (game->length == length && game->checksum == checksum
+			&& (ignore_crc || game->crc == crc))
+			break;
+	}
+
+	return game->length ? game : nullptr;
+}
+
+gln_patch_tableref_t GameDetection::gln_gameid_lookup_patch(uint16 length, byte checksum, uint16 crc) const {
+	gln_patch_tableref_t patch;
+
+	for (patch = GLN_PATCH_TABLE; patch->length; patch++) {
+		if (patch->length == length && patch->patch_checksum == checksum
+			&& patch->patch_crc == crc)
+			break;
+	}
+
+	return patch->length ? patch : nullptr;
+}
+
+const char *GameDetection::gln_gameid_get_game_name() {
+	/*
+	 * If no game name yet known, attempt to identify the game.  If it can't
+	 * be identified, set the cached game name to "" -- this special value
+	 * indicates that the game is an unknown one, but suppresses repeated
+	 * attempts to identify it on successive calls.
+	 */
+	if (!_gameName) {
+		gln_game_tableref_t game;
+
+		/*
+		 * If the interpreter hasn't yet loaded a game, startdata is nullptr
+		 * (uninitialized, global).  In this case, we return nullptr, allowing
+		 * for retries until a game is loaded.
+		 */
+		if (!_startData)
+			return nullptr;
+
+		game = gln_gameid_identify_game();
+		_gameName = game ? game->name : "";
+	}
+
+	/* Return the game's name, or nullptr if it was unidentifiable. */
+	assert(_gameName);
+	return strlen(_gameName) > 0 ? _gameName : nullptr;
+}
+
+/**
+ * Clear the saved game name, forcing a new lookup when next queried.  This
+ * function should be called by actions that may cause the interpreter to
+ * change game file, for example os_set_filenumber().
+ */
+void GameDetection::gln_gameid_game_name_reset() {
+	_gameName = nullptr;
+}
+
+
+/*----------------------------------------------------------------------*/
+
 void Level9MetaEngine::getSupportedGames(PlainGameList &games) {
 	for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
 		games.push_back(*pd);
@@ -62,7 +233,7 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
 			continue;
 
 		Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
-		size_t filesize = gameFile.size();
+		size_t _fileSize = gameFile.size();
 		gameFile.seek(0);
 		bool isBlorb = Blorb::isBlorb(gameFile, ID_ADRI);
 		gameFile.close();
@@ -72,12 +243,12 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
 
 		// Check for known games
 		const GlkDetectionEntry *p = LEVEL9_GAMES;
-		while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize))
+		while (p->_gameId && (md5 != p->_md5 || _fileSize != p->_filesize))
 			++p;
 
 		if (!p->_gameId) {
 			const PlainGameDescriptor &desc = LEVEL9_GAME_LIST[0];
-			gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
+			gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, _fileSize));
 		} else {
 			PlainGameDescriptor gameDesc = findGame(p->_gameId);
 			gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, p->_extra, filename, p->_language));
@@ -88,11 +259,7 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
 }
 
 void Level9MetaEngine::detectClashes(Common::StringMap &map) {
-	for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
-		if (map.contains(pd->gameId))
-			error("Duplicate game Id found - %s", pd->gameId);
-		map[pd->gameId] = "";
-	}
+	// No implementation
 }
 
 } // End of namespace Level9
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
index 386e475..9e8401b 100644
--- a/engines/glk/level9/detection.h
+++ b/engines/glk/level9/detection.h
@@ -31,6 +31,86 @@
 namespace Glk {
 namespace Level9 {
 
+struct gln_game_table_t {
+	const size_t length;        ///< Datafile length in bytes
+	const byte checksum;        ///< 8-bit checksum, last datafile byte
+	const uint16 crc;           ///< 16-bit CRC, L9cut-internal
+	const char *const name;     ///< Game title and platform
+};
+typedef const gln_game_table_t *gln_game_tableref_t;
+
+struct gln_patch_table_t {
+	const size_t length;        ///< Datafile length in bytes
+	const byte orig_checksum;   ///< 8-bit checksum, last datafile byte
+	const uint16 orig_crc;      ///< 16-bit CRC, L9cut-internal
+	const byte patch_checksum;  ///< 8-bit checksum, last datafile byte
+	const uint16 patch_crc;     ///< 16-bit CRC, L9cut-internal
+};
+typedef const gln_patch_table_t *gln_patch_tableref_t;
+
+/**
+ * Detection manager for specific games
+ */
+class GameDetection {
+private:
+	byte *&_startData;
+	size_t &_fileSize;
+	bool _crcInitialized;
+	uint16 _crcTable[256];
+public:
+	const char *_gameName;
+public:
+	/**
+	 * Constructor
+	 */
+	GameDetection(byte *&startData, size_t &fileSize);
+
+	/**
+	 * Identify a game from its data length, checksum, and CRC.  Returns the
+	 * entry of the game in the game table, or nullptr if not found.
+	 *
+	 * This function uses startdata and FileSize from the core interpreter.
+	 * These aren't advertised symbols, so be warned.
+	 */
+	gln_game_tableref_t gln_gameid_identify_game();
+
+	/**
+	 * Return the CRC of the bytes buffer[0..length-1].
+	 *
+	 * This algorithm is selected to match the CRCs used in L9cut.  Because of
+	 * the odd way CRCs are padded when L9cut calculates the CRC, this function
+	 * allows a count of NUL padding bytes to be included within the return CRC.
+	 */
+	uint16 gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding = 0);
+
+	/*
+	 * Look up and return game table and patch table entries given a game's
+	 * length, checksum, and CRC.  Returns the entry, or nullptr if not found.
+	 */
+	gln_game_tableref_t gln_gameid_lookup_game(uint16 length, byte checksum, uint16 crc, int ignore_crc) const;
+
+	/**
+	 * Look up and return patch table entries given a game's length, checksum, and CRC.
+	 * Returns the entry, or nullptr if not found
+	 */
+	gln_patch_tableref_t gln_gameid_lookup_patch(uint16 length, byte checksum, uint16 crc) const;
+
+	/**
+	 * Return the name of the game, or nullptr if not identifiable.
+	 *
+	 * This function uses startdata from the core interpreter.  This isn't an
+	 * advertised symbol, so be warned.
+	 */
+	const char *gln_gameid_get_game_name();
+
+	/**
+	 * Clear the saved game name, forcing a new lookup when next queried.  This
+	 * function should be called by actions that may cause the interpreter to
+	 * change game file, for example os_set_filenumber().
+	 */
+	void gln_gameid_game_name_reset();
+};
+
 /**
  * Meta engine for Level 9 interpreter
  */
diff --git a/engines/glk/level9/detection_tables.h b/engines/glk/level9/detection_tables.h
index 12b3b0e..dda4ce4 100644
--- a/engines/glk/level9/detection_tables.h
+++ b/engines/glk/level9/detection_tables.h
@@ -21,12 +21,781 @@
  */
 
 #include "engines/game.h"
-#include "common/gui_options.h"
-#include "common/language.h"
+
+/**
+ * Unlike other ScummVM GLK subengines, Level9 has a detection table that was already
+ * included in the Level9 interpreter. So it's used instead of a standard MD5-based list
+ */
 
 namespace Glk {
 namespace Level9 {
 
+/**
+ * The following game database is obtained from L9cut's l9data_d.h, and
+ * lets us find a game's name from its data CRC.  Entries marked "WANTED" in
+ * l9data_d.h, and file commentary, have been removed for brevity, and the
+ * file has been reformatted (patchlevel data removed).
+ *
+ * The version of l9data_d.h used is 050 (22 Oct 2002).
+ */
+static const gln_game_table_t GLN_GAME_TABLE[] = {
+	{0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
+
+	{0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
+	{0x630e, 0xbe, 0x3374, "Dungeon Adventure (MSX)"},
+
+	{0x5eb9, 0x30, 0xe99a, "Lords of Time (Amstrad CPC)"},
+	{0x5eb9, 0x5d, 0xc098, "Lords of Time (MSX)"},
+	{0x5eb9, 0x6e, 0xc689, "Lords of Time (Spectrum)"},
+
+	{0x5fab, 0x5c, 0xa309, "Snowball (Amstrad CPC)"},
+	{0x5fab, 0x2f, 0x8aa2, "Snowball (MSX)"},
+
+	{0x60c4, 0x28, 0x0154, "Return to Eden (Amstrad CPC/Commodore 64[v1])"},
+	{0x6064, 0x01, 0x5b3c, "Return to Eden (BBC[v1])"},
+	{0x6064, 0x95, 0x510c, "Return to Eden (Commodore 64[v2])"},
+	{0x6064, 0xda, 0xe610, "Return to Eden (Commodore 64[v2] *corrupt*)"},
+	{0x6064, 0xbd, 0x73ec, "Return to Eden (Atari *corrupt*)"},
+	{0x6047, 0x6c, 0x17ab, "Return to Eden (BBC[v2])"},
+	{0x5ca1, 0x33, 0x1c43, "Return to Eden (Spectrum[v1])"},
+	{0x5cb7, 0x64, 0x0790, "Return to Eden (Spectrum[v2])"},
+	{0x5cb7, 0xfe, 0x3533, "Return to Eden (MSX)"},
+
+	{0x34b3, 0x20, 0xccda, "Erik the Viking (BBC/Commodore 64)"},
+	{0x34b3, 0x53, 0x8f00, "Erik the Viking (Spectrum)"},
+	{0x34b3, 0xc7, 0x9058, "Erik the Viking (Amstrad CPC)"},
+
+	{0x63be, 0xd6, 0xcf5d, "Emerald Isle (Atari/Commodore 64/Amstrad CPC/Spectrum)"},
+	{0x63be, 0x0a, 0x21ed, "Emerald Isle (MSX *corrupt*)"},
+	{0x378c, 0x8d, 0x3a21, "Emerald Isle (BBC)"},
+
+	{0x506c, 0xf0, 0xba72, "Red Moon (BBC/Commodore 64/Amstrad CPC/MSX)"},
+	{0x505d, 0x32, 0x2dcf, "Red Moon (Spectrum)"},
+
+	{0x772b, 0xcd, 0xa503, "Worm in Paradise (Spectrum 128)"},
+	{0x546c, 0xb7, 0x9420, "Worm in Paradise (Spectrum 48)"},
+	{0x6d84, 0xf9, 0x49ae, "Worm in Paradise (Commodore 64 *corrupt*)"},
+	{0x6d84, 0xc8, 0x943f, "Worm in Paradise (Commodore 64 *fixed*)"},
+	{0x6030, 0x47, 0x46ad, "Worm in Paradise (Amstrad CPC)"},
+	{0x5828, 0xbd, 0xe7cb, "Worm in Paradise (BBC)"},
+
+	{0x7410, 0x5e, 0x60be, "Price of Magik (Spectrum 128)"},
+	{0x5aa4, 0xc1, 0x10a0, "Price of Magik (Spectrum 48[v1])"},
+	{0x5aa4, 0xc1, 0xeda4, "Price of Magik (Spectrum 48[v2])"},
+	{0x6fc6, 0x14, 0xf9b6, "Price of Magik (Commodore 64)"},
+	{0x5aa4, 0xc1, 0xbbf4, "Price of Magik (Amstrad CPC)"},
+	{0x5671, 0xbc, 0xff35, "Price of Magik (BBC)"},
+
+	{0x76f4, 0x5e, 0x1fe5, "Colossal Adventure /JoD (Amiga/PC)"},
+	{0x76f4, 0x5a, 0xcf4b, "Colossal Adventure /JoD (ST)"},
+	{0x6e60, 0x83, 0x18e0, "Adventure Quest /JoD (Amiga/PC)"},
+	{0x6e5c, 0xf6, 0xd356, "Adventure Quest /JoD (ST)"},
+	{0x6f0c, 0x95, 0x1f64, "Dungeon Adventure /JoD (Amiga/PC/ST)"},
+
+	{0x6f70, 0x40, 0xbd91, "Colossal Adventure /JoD (MSX)"},
+
+	{0x6f6e, 0x78, 0x28cd, "Colossal Adventure /JoD (Spectrum 128)"},
+	{0x6970, 0xd6, 0xa820, "Adventure Quest /JoD (Spectrum 128)"},
+	{0x6de8, 0x4c, 0xd795, "Dungeon Adventure /JoD (Spectrum 128)"},
+
+	{0x6f4d, 0xcb, 0xe8f2, "Colossal Adventure /JoD (Amstrad CPC128[v1]/Spectrum +3)"},
+	{0x6f6a, 0xa5, 0x8dd2, "Colossal Adventure /JoD (Amstrad CPC128[v2])"},
+	{0x6968, 0x32, 0x0c01, "Adventure Quest /JoD (Amstrad CPC128/Spectrum +3)"},
+	{0x6dc0, 0x63, 0x5d95, "Dungeon Adventure /JoD (Amstrad CPC128/Spectrum +3)"},
+
+	{0x5e31, 0x7c, 0xaa54, "Colossal Adventure /JoD (Amstrad CPC64)"},
+	{0x5b50, 0x66, 0x1800, "Adventure Quest /JoD (Amstrad CPC64)"},
+	{0x58a6, 0x24, 0xb50f, "Dungeon Adventure /JoD (Amstrad CPC64)"},
+
+	{0x6c8e, 0xb6, 0x9be3, "Colossal Adventure /JoD (Commodore 64)"},
+	{0x63b6, 0x2e, 0xef38, "Adventure Quest /JoD (Commodore 64)"},
+	{0x6bd2, 0x65, 0xa41f, "Dungeon Adventure /JoD (Commodore 64)"},
+
+	{0x5b16, 0x3b, 0xe2aa, "Colossal Adventure /JoD (Atari)"},
+	{0x5b58, 0x50, 0x332e, "Adventure Quest /JoD (Atari)"},
+	{0x593a, 0x80, 0x7a34, "Dungeon Adventure /JoD (Atari)"},
+
+	{0x5a8e, 0xf2, 0x7cca, "Colossal Adventure /JoD (Spectrum 48)"},
+	{0x5ace, 0x11, 0xdc12, "Adventure Quest /JoD (Spectrum 48)"},
+	{0x58a3, 0x38, 0x8ce4, "Dungeon Adventure /JoD (Spectrum 48)"},
+
+	{0x7b31, 0x6e, 0x2e2b, "Snowball /SD (Amiga/ST)"},
+	{0x7d16, 0xe6, 0x5438, "Return to Eden /SD (Amiga/ST)"},
+	{0x7cd9, 0x0c, 0x4df1, "Worm in Paradise /SD (Amiga/ST)"},
+
+	{0x7b2f, 0x70, 0x6955, "Snowball /SD (Mac/PC/Spectrum 128)"},
+	{0x7b2f, 0x70, 0x6f6c, "Snowball /SD (Amstrad CPC/Spectrum +3)"},
+	{0x7d14, 0xe8, 0xfbab, "Return to Eden /SD (PC)"},
+	{0x7cff, 0xf8, 0x6044, "Return to Eden /SD (Amstrad CPC/Spectrum +3)"},
+	{0x7cf8, 0x24, 0x9c1c, "Return to Eden /SD (Mac)"},
+	{0x7c55, 0x18, 0xdaee, "Return to Eden /SD (Spectrum 128)"},
+	{0x7cd7, 0x0e, 0x4feb, "Worm in Paradise /SD (Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3)"},
+
+	{0x7363, 0x65, 0xa0ab, "Snowball /SD (Commodore 64)"},
+	{0x772f, 0xca, 0x8602, "Return to Eden /SD (Commodore 64)"},
+	{0x788d, 0x72, 0x888a, "Worm in Paradise /SD (Commodore 64)"},
+
+	{0x6bf8, 0x3f, 0xc9f7, "Snowball /SD (Atari)"},
+	{0x60f7, 0x68, 0xc2bc, "Return to Eden /SD (Atari)"},
+	{0x6161, 0xf3, 0xe6d7, "Worm in Paradise /SD (Atari)"},
+
+	{0x67a3, 0x9d, 0x1d05, "Snowball /SD (Apple ][)"},
+	{0x639c, 0x8b, 0x06e2, "Return to Eden /SD (Apple ][)"},
+	{0x60dd, 0xf2, 0x5bb8, "Worm in Paradise /SD (Apple ][)"},
+
+	{0x6541, 0x02, 0x2e6c, "Snowball /SD (Spectrum 48)"},
+	{0x5f43, 0xca, 0x828c, "Return to Eden /SD (Spectrum 48)"},
+	{0x5ebb, 0xf1, 0x4dec, "Worm in Paradise /SD (Spectrum 48)"},
+
+	{0x8333, 0xb7, 0xe2ac, "Adrian Mole I, pt. 1 (Commodore 64)"},
+	{0x844d, 0x50, 0x5353, "Adrian Mole I, pt. 2 (Commodore 64)"},
+	{0x8251, 0x5f, 0x862a, "Adrian Mole I, pt. 3 (Commodore 64)"},
+	{0x7a78, 0x5e, 0x6ea3, "Adrian Mole I, pt. 4 (Commodore 64)"},
+
+	{0x7c6f, 0x0f, 0xba24, "Adrian Mole I, pt. 1 (Amstrad CPC)"},
+
+	{0x72fa, 0x8b, 0x6f12, "Adrian Mole I, pt. 1 (Spectrum)"},
+	{0x738e, 0x5b, 0x7e3d, "Adrian Mole I, pt. 2 (Spectrum)"},
+	{0x7375, 0xe5, 0x3f3e, "Adrian Mole I, pt. 3 (Spectrum)"},
+	{0x78d5, 0xe3, 0xcd7d, "Adrian Mole I, pt. 4 (Spectrum)"},
+
+	{0x3a31, 0xe5, 0x0bdb, "Adrian Mole I, pt. 1 (BBC)"},
+	{0x37f1, 0x77, 0xd231, "Adrian Mole I, pt. 2 (BBC)"},
+	{0x3900, 0x1c, 0x5d9a, "Adrian Mole I, pt. 3 (BBC)"},
+	{0x3910, 0xac, 0x07f9, "Adrian Mole I, pt. 4 (BBC)"},
+	{0x3ad6, 0xa7, 0x95d2, "Adrian Mole I, pt. 5 (BBC)"},
+	{0x38a5, 0x0f, 0xdefc, "Adrian Mole I, pt. 6 (BBC)"},
+	{0x361e, 0x7e, 0xfd9f, "Adrian Mole I, pt. 7 (BBC)"},
+	{0x3934, 0x75, 0xe141, "Adrian Mole I, pt. 8 (BBC)"},
+	{0x3511, 0xcc, 0xd829, "Adrian Mole I, pt. 9 (BBC)"},
+	{0x38dd, 0x31, 0x2534, "Adrian Mole I, pt. 10 (BBC)"},
+	{0x39c0, 0x44, 0x89df, "Adrian Mole I, pt. 11 (BBC)"},
+	{0x3a12, 0x8f, 0xc2bd, "Adrian Mole I, pt. 12 (BBC)"},
+
+	{0x7931, 0xb9, 0xc51b, "Adrian Mole II, pt. 1 (Commodore 64/Amstrad CPC)"},
+	{0x7cdf, 0xa5, 0x43e3, "Adrian Mole II, pt. 2 (Commodore 64/Amstrad CPC)"},
+	{0x7a0c, 0x97, 0x4bea, "Adrian Mole II, pt. 3 (Commodore 64/Amstrad CPC)"},
+	{0x7883, 0xe2, 0xee0e, "Adrian Mole II, pt. 4 (Commodore 64/Amstrad CPC)"},
+
+	{0x6841, 0x4a, 0x94e7, "Adrian Mole II, pt. 1 (Spectrum)"},
+	{0x6bc0, 0x62, 0xab3d, "Adrian Mole II, pt. 2 (Spectrum)"},
+	{0x692c, 0x21, 0x2015, "Adrian Mole II, pt. 3 (Spectrum)"},
+	{0x670a, 0x94, 0xa2a6, "Adrian Mole II, pt. 4 (Spectrum)"},
+
+	{0x593a, 0xaf, 0x30e9, "Adrian Mole II, pt. 1 (BBC)"},
+	{0x57e6, 0x8a, 0xc41a, "Adrian Mole II, pt. 2 (BBC)"},
+	{0x5819, 0xcd, 0x1ba0, "Adrian Mole II, pt. 3 (BBC)"},
+	{0x579b, 0xad, 0xa723, "Adrian Mole II, pt. 4 (BBC)"},
+
+	{0x765d, 0xcd, 0xfc02, "The Archers, pt. 1 (Commodore 64)"},
+	{0x6e58, 0x07, 0xbffc, "The Archers, pt. 2 (Commodore 64)"},
+	{0x7e98, 0x6a, 0x95e5, "The Archers, pt. 3 (Commodore 64)"},
+	{0x81e2, 0xd5, 0xb278, "The Archers, pt. 4 (Commodore 64)"},
+
+	{0x6ce5, 0x58, 0x46de, "The Archers, pt. 1 (Spectrum)"},
+	{0x68da, 0xc1, 0x3b8e, "The Archers, pt. 2 (Spectrum)"},
+	{0x6c67, 0x9a, 0x9a6a, "The Archers, pt. 3 (Spectrum)"},
+	{0x6d91, 0xb9, 0x12a7, "The Archers, pt. 4 (Spectrum)"},
+
+	{0x5834, 0x42, 0xcc9d, "The Archers, pt. 1 (BBC)"},
+	{0x56dd, 0x51, 0xe582, "The Archers, pt. 2 (BBC)"},
+	{0x5801, 0x53, 0xf2ef, "The Archers, pt. 3 (BBC)"},
+	{0x54a4, 0x01, 0xc0ab, "The Archers, pt. 4 (BBC)"},
+
+	{0x579e, 0x97, 0x9faa, "Lords of Time /T&M GD (BBC)"},
+	{0x5500, 0x50, 0xca75, "Red Moon /T&M GD (BBC)"},
+	{0x579a, 0x2a, 0x9373, "Price of Magik /T&M GD (BBC)"},
+
+	{0x4fd2, 0x9d, 0x799a, "Lancelot, pt. 1 GD (BBC)"},
+	{0x4dac, 0xa8, 0x86ed, "Lancelot, pt. 2 GD (BBC)"},
+	{0x4f96, 0x22, 0x30f8, "Lancelot, pt. 3 GD (BBC)"},
+
+	{0x55ce, 0xa1, 0xba12, "Scapeghost, pt. 1 GD (BBC)"},
+	{0x54a6, 0xa9, 0xc9f3, "Scapeghost, pt. 2 GD (BBC)"},
+	{0x51bc, 0xe3, 0x89c3, "Scapeghost, pt. 3 GD (BBC)"},
+
+	{0x46ec, 0x64, 0x2300, "Knight Orc, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x6140, 0x18, 0x4f66, "Knight Orc, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x640e, 0xc1, 0xfc69, "Knight Orc, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x5ff0, 0xf8, 0x3a13, "Gnome Ranger, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x6024, 0x01, 0xaaa9, "Gnome Ranger, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x6036, 0x3d, 0x6c6c, "Gnome Ranger, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x69fe, 0x56, 0xecfb, "Lords of Time /T&M GD (Amstrad CPC/Spectrum +3)"},
+	{0x6888, 0x8d, 0x7f6a, "Red Moon /T&M GD (Amstrad CPC/Spectrum +3)"},
+	{0x5a50, 0xa9, 0xa5fa, "Price of Magik /T&M GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x5c7a, 0x44, 0x460e, "Lancelot, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x53a2, 0x1e, 0x2fae, "Lancelot, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x5914, 0x22, 0x4a31, "Lancelot, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x5a38, 0xf7, 0x876e, "Ingrid's Back, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x531a, 0xed, 0xcf3f, "Ingrid's Back, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x57e4, 0x19, 0xb354, "Ingrid's Back, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x5cbc, 0xa5, 0x0dbe, "Scapeghost, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
+	{0x5932, 0x4e, 0xb2f5, "Scapeghost, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
+	{0x5860, 0x95, 0x3227, "Scapeghost, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
+
+	{0x74e0, 0x92, 0x885e, "Knight Orc, pt. 1 GD (Spectrum 128)"},
+	{0x6dbc, 0x97, 0x6f55, "Knight Orc, pt. 2 GD (Spectrum 128)"},
+	{0x7402, 0x07, 0x385f, "Knight Orc, pt. 3 GD (Spectrum 128)"},
+
+	{0x52aa, 0xdf, 0x7b5b, "Gnome Ranger, pt. 1 GD (Spectrum 128)"},
+	{0x6ffa, 0xdb, 0xdde2, "Gnome Ranger, pt. 2 GD (Spectrum 128)"},
+	{0x723a, 0x69, 0x039b, "Gnome Ranger, pt. 3 GD (Spectrum 128)"},
+
+	{0x6f1e, 0xda, 0x2ce0, "Lords of Time /T&M GD (Spectrum 128)"},
+	{0x6da0, 0xb8, 0x3802, "Red Moon /T&M GD (Spectrum 128)"},
+	{0x6108, 0xdd, 0xefe7, "Price of Magik /T&M GD (Spectrum 128)"},
+
+	{0x768c, 0xe8, 0x8fc6, "Lancelot, pt. 1 GD (Spectrum 128)"},
+	{0x76b0, 0x1d, 0x0fcd, "Lancelot, pt. 2 GD (Spectrum 128)"},
+	{0x765e, 0x4f, 0x3b73, "Lancelot, pt. 3 GD (Spectrum 128)"},
+
+	{0x76a0, 0x3a, 0xb803, "Ingrid's Back, pt. 1 GD (Spectrum 128)"},
+	{0x7674, 0x0b, 0xe92f, "Ingrid's Back, pt. 2 GD (Spectrum 128)"},
+	{0x765e, 0xba, 0x086d, "Ingrid's Back, pt. 3 GD (Spectrum 128)"},
+
+	{0x762e, 0x82, 0x8848, "Scapeghost, pt. 1 GD (Spectrum 128)"},
+	{0x5bd6, 0x35, 0x79ef, "Scapeghost, pt. 2 GD (Spectrum 128)"},
+	{0x6fa8, 0xa4, 0x62c2, "Scapeghost, pt. 3 GD (Spectrum 128)"},
+
+	{0xbb93, 0x36, 0x6a05, "Knight Orc, pt. 1 (Amiga)"},
+	{0xbb6e, 0xad, 0x4d40, "Knight Orc, pt. 1 (ST)"},
+	{0xc58e, 0x4a, 0x4e9d, "Knight Orc, pt. 2 (Amiga/ST)"},
+	{0xcb9a, 0x0f, 0x0804, "Knight Orc, pt. 3 (Amiga/ST)"},
+
+	{0xbb6e, 0xa6, 0x9753, "Knight Orc, pt. 1 (PC)"},
+	{0xc58e, 0x43, 0xe9ce, "Knight Orc, pt. 2 (PC)"},
+	{0xcb9a, 0x08, 0x6c36, "Knight Orc, pt. 3 (PC)"},
+
+	{0x898a, 0x43, 0xfc8b, "Knight Orc, pt. 1 (Apple ][)"},
+	{0x8b9f, 0x61, 0x7288, "Knight Orc, pt. 2 (Apple ][)"},
+	{0x8af9, 0x61, 0x7542, "Knight Orc, pt. 3 (Apple ][)"},
+
+	{0x8970, 0x6b, 0x3c7b, "Knight Orc, pt. 1 (Commodore 64 Gfx)"},
+	{0x8b90, 0x4e, 0x098c, "Knight Orc, pt. 2 (Commodore 64 Gfx)"},
+	{0x8aea, 0x4e, 0xca54, "Knight Orc, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x86d0, 0xb7, 0xadbd, "Knight Orc, pt. 1 (Spectrum 48)"},
+	{0x8885, 0x22, 0xe293, "Knight Orc, pt. 2 (Spectrum 48)"},
+	{0x87e5, 0x0e, 0xdc33, "Knight Orc, pt. 3 (Spectrum 48)"},
+
+	{0xb1a9, 0x80, 0x5fb7, "Gnome Ranger, pt. 1 (Amiga/ST)"},
+	{0xab9d, 0x31, 0xbe6d, "Gnome Ranger, pt. 2 (Amiga/ST)"},
+	{0xae28, 0x87, 0xb6b6, "Gnome Ranger, pt. 3 (Amiga/ST)"},
+
+	{0xb0ec, 0xc2, 0x0053, "Gnome Ranger, pt. 1 (ST[v1])"},
+	{0xaf82, 0x83, 0x19f7, "Gnome Ranger, pt. 2 (ST[v1])"},
+
+	{0xb1aa, 0xad, 0xaf47, "Gnome Ranger, pt. 1 (PC)"},
+	{0xb19e, 0x92, 0x8f96, "Gnome Ranger, pt. 1 (ST[v2])"},
+	{0xab8b, 0xbf, 0x31e6, "Gnome Ranger, pt. 2 (PC/ST[v2])"},
+	{0xae16, 0x81, 0x8741, "Gnome Ranger, pt. 3 (PC/ST[v2])"},
+
+	{0xad41, 0xa8, 0x42c5, "Gnome Ranger, pt. 1 (Commodore 64 TO)"},
+	{0xa735, 0xf7, 0x2e08, "Gnome Ranger, pt. 2 (Commodore 64 TO)"},
+	{0xa9c0, 0x9e, 0x0d70, "Gnome Ranger, pt. 3 (Commodore 64 TO)"},
+
+	{0x908e, 0x0d, 0x58a7, "Gnome Ranger, pt. 1 (Commodore 64 Gfx)"},
+	{0x8f6f, 0x0a, 0x411a, "Gnome Ranger, pt. 2 (Commodore 64 Gfx)"},
+	{0x9060, 0xbb, 0xe75d, "Gnome Ranger, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x8aab, 0xc0, 0xde5f, "Gnome Ranger, pt. 1 (Spectrum 48)"},
+	{0x8ac8, 0x9a, 0xc89b, "Gnome Ranger, pt. 2 (Spectrum 48)"},
+	{0x8a93, 0x4f, 0x10cc, "Gnome Ranger, pt. 3 (Spectrum 48)"},
+
+	{0xb57c, 0x44, 0x7779, "Lords of Time /T&M (PC)"},
+	{0xa69e, 0x6c, 0xb268, "Red Moon /T&M (PC)"},
+	{0xbac7, 0x7f, 0xddb2, "Price of Magik /T&M (PC)"},
+
+	{0xb579, 0x89, 0x3e89, "Lords of Time /T&M (ST)"},
+	{0xa698, 0x41, 0xcaca, "Red Moon /T&M (ST)"},
+	{0xbac4, 0x80, 0xa750, "Price of Magik /T&M (ST)"},
+
+	{0xb576, 0x2a, 0x7239, "Lords of Time /T&M (Amiga)"},
+	{0xa692, 0xd1, 0x6a99, "Red Moon /T&M (Amiga)"},
+	{0xbaca, 0x3a, 0x221b, "Price of Magik /T&M (Amiga)"},
+
+	{0xb563, 0x6a, 0x0c5c, "Lords of Time /T&M (Mac)"},
+	{0xa67c, 0xb8, 0xff41, "Red Moon /T&M (Mac)"},
+	{0xbab2, 0x87, 0x09f5, "Price of Magik /T&M (Mac)"},
+
+	{0xb38c, 0x37, 0x9f8e, "Lords of Time /T&M (Commodore 64 TO)"},
+	{0xa4e2, 0xa6, 0x016d, "Red Moon /T&M (Commodore 64 TO)"},
+	{0xb451, 0xa8, 0x2682, "Price of Magik /T&M (Commodore 64 TO)"},
+
+	{0x9070, 0x43, 0x45d4, "Lords of Time /T&M (Commodore 64 Gfx)"},
+	{0x903f, 0x6b, 0x603e, "Red Moon /T&M (Commodore 64 Gfx)"},
+	{0x8f51, 0xb2, 0x6c9a, "Price of Magik /T&M (Commodore 64 Gfx)"},
+
+	{0x8950, 0xa1, 0xbb16, "Lords of Time /T&M (Spectrum 48)"},
+	{0x8813, 0x11, 0x22de, "Red Moon /T&M (Spectrum 48)"},
+	{0x8a60, 0x2a, 0x29ed, "Price of Magik /T&M (Spectrum 48)"},
+
+	{0xb260, 0xe5, 0xc5b2, "Lords of Time /T&M (PC/ST *USA*)"},
+	{0xa3a4, 0xdf, 0x6732, "Red Moon /T&M (PC/ST *USA*)"},
+	{0xb7a0, 0x7e, 0x2226, "Price of Magik /T&M (PC/ST *USA*)"},
+
+	{0xb257, 0xf8, 0xfbd5, "Lords of Time /T&M (Amiga *USA*)"},
+	{0xa398, 0x82, 0xd031, "Red Moon /T&M (Amiga *USA*)"},
+	{0xb797, 0x1f, 0x84a9, "Price of Magik /T&M (Amiga *USA*)"},
+
+	{0x8d78, 0x3a, 0xba6e, "Lords of Time /T&M (Commodore 64 Gfx *USA*)"},
+	{0x8d56, 0xd3, 0x146a, "Red Moon /T&M (Commodore 64 Gfx *USA*)"},
+	{0x8c46, 0xf0, 0xcaf6, "Price of Magik /T&M (Commodore 64 Gfx *USA*)"},
+
+	{0xc0cf, 0x4e, 0xb7fa, "Lancelot, pt. 1 (Amiga/PC/ST)"},
+	{0xd5e9, 0x6a, 0x4192, "Lancelot, pt. 2 (Amiga/PC/ST)"},
+	{0xbb8f, 0x1a, 0x7487, "Lancelot, pt. 3 (Amiga/PC/ST)"},
+
+	{0xc0bd, 0x57, 0x6ef1, "Lancelot, pt. 1 (Mac)"},
+	{0xd5d7, 0x99, 0x770b, "Lancelot, pt. 2 (Mac)"},
+	{0xbb7d, 0x17, 0xbc42, "Lancelot, pt. 3 (Mac)"},
+
+	{0xb4c9, 0x94, 0xd784, "Lancelot, pt. 1 (Commodore 64 TO)"},
+	{0xb729, 0x51, 0x8ee5, "Lancelot, pt. 2 (Commodore 64 TO)"},
+	{0xb702, 0xe4, 0x1809, "Lancelot, pt. 3 (Commodore 64 TO)"},
+
+	{0x8feb, 0xba, 0xa800, "Lancelot, pt. 1 (Commodore 64 Gfx)"},
+	{0x8f6b, 0xfa, 0x0f7e, "Lancelot, pt. 2 (Commodore 64 Gfx)"},
+	{0x8f71, 0x2f, 0x0ddc, "Lancelot, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x8ade, 0xf2, 0xfffb, "Lancelot, pt. 1 (Spectrum 48)"},
+	{0x8b0e, 0xfb, 0x0bab, "Lancelot, pt. 2 (Spectrum 48)"},
+	{0x8ab3, 0xc1, 0xcb62, "Lancelot, pt. 3 (Spectrum 48)"},
+
+	{0xbba4, 0x94, 0x0871, "Lancelot, pt. 1 (Amiga/PC *USA*)"},
+	{0xd0c0, 0x56, 0x8c48, "Lancelot, pt. 2 (Amiga/PC *USA*)"},
+	{0xb6ac, 0xc6, 0xaea0, "Lancelot, pt. 3 (Amiga/PC *USA*)"},
+
+	{0x8afc, 0x07, 0x8321, "Lancelot, pt. 1 (Commodore 64 Gfx *USA*)"},
+	{0x8aec, 0x13, 0x6791, "Lancelot, pt. 2 (Commodore 64 Gfx *USA*)"},
+	{0x8aba, 0x0d, 0x5602, "Lancelot, pt. 3 (Commodore 64 Gfx *USA*)"},
+
+	{0xd19b, 0xad, 0x306d, "Ingrid's Back, pt. 1 (PC)"},
+	{0xc5a5, 0xfe, 0x3c98, "Ingrid's Back, pt. 2 (PC)"},
+	{0xd7ae, 0x9e, 0x1878, "Ingrid's Back, pt. 3 (PC)"},
+
+	{0xd188, 0x13, 0xdc60, "Ingrid's Back, pt. 1 (Amiga)"},
+	{0xc594, 0x03, 0xea95, "Ingrid's Back, pt. 2 (Amiga)"},
+	{0xd79f, 0xb5, 0x1661, "Ingrid's Back, pt. 3 (Amiga)"},
+
+	{0xd183, 0x83, 0xef72, "Ingrid's Back, pt. 1 (ST)"},
+	{0xc58f, 0x65, 0xf337, "Ingrid's Back, pt. 2 (ST)"},
+	{0xd79a, 0x57, 0x49c5, "Ingrid's Back, pt. 3 (ST)"},
+
+	{0xb770, 0x03, 0x9a03, "Ingrid's Back, pt. 1 (Commodore 64 TO)"},
+	{0xb741, 0xb6, 0x2aa5, "Ingrid's Back, pt. 2 (Commodore 64 TO)"},
+	{0xb791, 0xa1, 0xd065, "Ingrid's Back, pt. 3 (Commodore 64 TO)"},
+
+	{0x9089, 0xce, 0xc5e2, "Ingrid's Back, pt. 1 (Commodore 64 Gfx)"},
+	{0x908d, 0x80, 0x30c7, "Ingrid's Back, pt. 2 (Commodore 64 Gfx)"},
+	{0x909e, 0x9f, 0xdecc, "Ingrid's Back, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x8ab7, 0x68, 0xee57, "Ingrid's Back, pt. 1 (Spectrum 48)"},
+	{0x8b1e, 0x84, 0x2538, "Ingrid's Back, pt. 2 (Spectrum 48)"},
+	{0x8b1c, 0xa8, 0x9262, "Ingrid's Back, pt. 3 (Spectrum 48)"},
+
+	{0xbeab, 0x2d, 0x94d9, "Scapeghost, pt. 1 (Amiga)"},
+	{0xc132, 0x14, 0x7adc, "Scapeghost, pt. 1 (Amiga *bak*)"},
+	{0xbe94, 0xcc, 0x04b8, "Scapeghost, pt. 1 (PC/ST)"},
+	{0x99bd, 0x65, 0x032e, "Scapeghost, pt. 2 (Amiga/PC/ST)"},
+	{0xbcb6, 0x7a, 0x7d4f, "Scapeghost, pt. 3 (Amiga/PC/ST)"},
+
+	{0x9058, 0xcf, 0x9748, "Scapeghost, pt. 1 (Commodore 64 Gfx)"},
+	{0x8f43, 0xc9, 0xeefd, "Scapeghost, pt. 2 (Commodore 64 Gfx)"},
+	{0x90ac, 0x68, 0xb4a8, "Scapeghost, pt. 3 (Commodore 64 Gfx)"},
+
+	{0x8a21, 0xf4, 0xd9e4, "Scapeghost, pt. 1 (Spectrum 48)"},
+	{0x8a12, 0xe3, 0xc2ff, "Scapeghost, pt. 2 (Spectrum 48)"},
+	{0x8a16, 0xcc, 0x4f3b, "Scapeghost, pt. 3 (Spectrum 48)"},
+
+	{0x3ebb, 0x00, 0xf6dc, "Champion of the Raj (English) 1/2 GD (Amiga)"},
+	{0x0fd8, 0x00, 0xf250, "Champion of the Raj (English) 2/2 GD (Amiga)"},
+
+	{0x3e8f, 0x00, 0x5599, "Champion of the Raj (English) 1/2 GD (ST)"},
+
+	{0x3e4f, 0x00, 0xb202, "Champion of the Raj (English) 1/2 GD (PC)"},
+	{0x14a3, 0x00, 0xa288, "Champion of the Raj (English) 2/2 GD (PC)"},
+
+	{0x1929, 0x00, 0xd4b2, "Champion of the Raj (demo), 1/2 GD (ST)"},
+	{0x40e0, 0x02, 0x080d, "Champion of the Raj (demo), 2/2 GD (ST)"},
+
+	{0x4872, 0x00, 0x9515, "Champion of the Raj (German) 1/2 GD (Amiga)"},
+	{0x11f5, 0x00, 0xbf39, "Champion of the Raj (German) 2/2 GD (Amiga)"},
+
+	{0x4846, 0x00, 0xd9c1, "Champion of the Raj (German) 1/2 GD (ST)"},
+	{0x11f5, 0x00, 0x7aa4, "Champion of the Raj (German) 2/2 GD (ST)"},
+
+	{0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
+
+	{0x0000, 0x00, 0x0000, nullptr}
+};
+
+/**
+ * The following patch database is obtained from L9cut's l9data_p.h, and
+ * allows CRCs from patched games to be translated into original CRCs for
+ * lookup in the game database above.  Some file commentary has been removed
+ * for brevity, and unused patch fields deleted.
+ *
+ * The version of l9data_p.h used is 012 (22 May 2001).
+ */
+static const gln_patch_table_t GLN_PATCH_TABLE[] = {
+	/* Price of Magik (Spectrum128) */
+	{0x7410, 0x5e, 0x60be, 0x70, 0x6cef},
+
+	/* Price of Magik (Commodore 64) */
+	{0x6fc6, 0x14, 0xf9b6, 0x26, 0x3326},
+
+	/* Price of Magik (Spectrum48) */
+	{0x5aa4, 0xc1, 0xeda4, 0xd3, 0xed35},
+	{0x5aa4, 0xc1, 0xeda4, 0xc1, 0x8a65},
+
+	/* Colossal Adventure /JoD (Amiga/PC) */
+	{0x76f4, 0x5e, 0x1fe5, 0xea, 0x1305},
+	{0x76f4, 0x5e, 0x1fe5, 0xb5, 0x901f},
+	{0x76f4, 0x5e, 0x1fe5, 0x5e, 0x6ea1},
+
+	/* Colossal Adventure /JoD (ST) */
+	{0x76f4, 0x5a, 0xcf4b, 0xe6, 0x012a},
+	{0x76f4, 0x5a, 0xcf4b, 0xb1, 0x40b1},
+
+	/* Adventure Quest /JoD (Amiga/PC) */
+	{0x6e60, 0x83, 0x18e0, 0x4c, 0xcfb0},
+	{0x6e60, 0x83, 0x18e0, 0xfa, 0x9b3b},
+	{0x6e60, 0x83, 0x18e0, 0x83, 0x303d},
+
+	/* Adventure Quest /JoD (ST) */
+	{0x6e5c, 0xf6, 0xd356, 0xbf, 0xede7},
+	{0x6e5c, 0xf6, 0xd356, 0x6d, 0x662d},
+
+	/* Dungeon Adventure /JoD (Amiga/PC/ST) */
+	{0x6f0c, 0x95, 0x1f64, 0x6d, 0x2443},
+	{0x6f0c, 0x95, 0x1f64, 0x0c, 0x6066},
+	{0x6f0c, 0x95, 0x1f64, 0x96, 0xdaca},
+	{0x6f0c, 0x95, 0x1f64, 0x95, 0x848d},
+
+	/* Colossal Adventure /JoD (Spectrum128) */
+	{0x6f6e, 0x78, 0x28cd, 0xf8, 0xda5f},
+	{0x6f6e, 0x78, 0x28cd, 0x77, 0x5b4e},
+
+	/* Adventure Quest /JoD (Spectrum128) */
+	{0x6970, 0xd6, 0xa820, 0x3b, 0x1870},
+	{0x6970, 0xd6, 0xa820, 0xd5, 0x13c4},
+
+	/* Dungeon Adventure /JoD (Spectrum128) */
+	{0x6de8, 0x4c, 0xd795, 0xa2, 0x3eea},
+	{0x6de8, 0x4c, 0xd795, 0x4b, 0xad30},
+
+	/* Colossal Adventure /JoD (Amstrad CPC) */
+	{0x6f4d, 0xcb, 0xe8f2, 0x4b, 0xb384},
+	{0x6f4d, 0xcb, 0xe8f2, 0xca, 0x96e7},
+
+	/* Adventure Quest /JoD (Amstrad CPC) */
+	{0x6968, 0x32, 0x0c01, 0x97, 0xdded},
+	{0x6968, 0x32, 0x0c01, 0x31, 0xe8c2},
+
+	/* Dungeon Adventure /JoD (Amstrad CPC) */
+	{0x6dc0, 0x63, 0x5d95, 0xb9, 0xc963},
+	{0x6dc0, 0x63, 0x5d95, 0x62, 0x79f7},
+
+	/* Colossal Adventure /JoD (Commodore 64) */
+	{0x6c8e, 0xb6, 0x9be3, 0x36, 0x6971},
+	{0x6c8e, 0xb6, 0x9be3, 0xb5, 0xeba0},
+
+	/* Adventure Quest /JoD (Commodore 64) */
+	{0x63b6, 0x2e, 0xef38, 0x93, 0x4e68},
+	{0x63b6, 0x2e, 0xef38, 0x2d, 0x54dc},
+
+	/* Dungeon Adventure /JoD (Commodore 64) */
+	{0x6bd2, 0x65, 0xa41f, 0xbb, 0x4260},
+	{0x6bd2, 0x65, 0xa41f, 0x64, 0xdf5a},
+
+	/* Colossal Adventure /JoD (Spectrum48) */
+	{0x5a8e, 0xf2, 0x7cca, 0x72, 0x8e58},
+	{0x5a8e, 0xf2, 0x7cca, 0xf1, 0x0c89},
+	{0x5a8e, 0xf2, 0x7cca, 0xf2, 0x2c96},
+
+	/* Adventure Quest /JoD (Spectrum48) */
+	{0x5ace, 0x11, 0xdc12, 0x76, 0x8663},
+	{0x5ace, 0x11, 0xdc12, 0x10, 0xa757},
+	{0x5ace, 0x11, 0xdc12, 0x11, 0xf118},
+
+	/* Dungeon Adventure /JoD (Spectrum48) */
+	{0x58a3, 0x38, 0x8ce4, 0x8e, 0xb61a},
+	{0x58a3, 0x38, 0x8ce4, 0x37, 0x34c0},
+	{0x58a3, 0x38, 0x8ce4, 0x38, 0xa1ee},
+
+	/* Snowball /SD (Amiga/ST) */
+	{0x7b31, 0x6e, 0x2e2b, 0xe5, 0x6017},
+
+	/* Return to Eden /SD (Amiga/ST) */
+	{0x7d16, 0xe6, 0x5438, 0x5d, 0xc770},
+
+	/* Worm in Paradise /SD (Amiga/ST) */
+	{0x7cd9, 0x0c, 0x4df1, 0x83, 0xe997},
+
+	/* Snowball /SD (PC/Spectrum128) */
+	{0x7b2f, 0x70, 0x6955, 0xe7, 0x0af4},
+	{0x7b2f, 0x70, 0x6955, 0x70, 0x1179},
+
+	/* Return to Eden /SD (PC) */
+	{0x7d14, 0xe8, 0xfbab, 0x5f, 0xeab9},
+	{0x7d14, 0xe8, 0xfbab, 0xe8, 0xe216},
+
+	/* Return to Eden /SD (Amstrad CPC) */
+	{0x7cff, 0xf8, 0x6044, 0x6f, 0xbb57},
+
+	/* Return to Eden /SD (Spectrum128) */
+	{0x7c55, 0x18, 0xdaee, 0x8f, 0x01fd},
+
+	/* Worm in Paradise /SD (Amstrad CPC/PC/Spectrum128) */
+	{0x7cd7, 0x0e, 0x4feb, 0x85, 0x4eae},
+	{0x7cd7, 0x0e, 0x4feb, 0x0e, 0xb02c},
+
+	/* Snowball /SD (Commodore 64) */
+	{0x7363, 0x65, 0xa0ab, 0xdc, 0xca6a},
+
+	/* Return to Eden /SD (Commodore 64) */
+	{0x772f, 0xca, 0x8602, 0x41, 0x9bd0},
+
+	/* Worm in Paradise /SD (Commodore 64) */
+	{0x788d, 0x72, 0x888a, 0xe9, 0x4cce},
+
+	/* Snowball /SD (Atari) */
+	{0x6bf8, 0x3f, 0xc9f7, 0x96, 0x1908},
+
+	/* Return to Eden /SD (Atari) */
+	{0x60f7, 0x68, 0xc2bc, 0xdf, 0xd3ae},
+
+	/* Worm in Paradise /SD (Atari) */
+	{0x6161, 0xf3, 0xe6d7, 0x6a, 0xe232},
+
+	/* Snowball /SD (Spectrum48) */
+	{0x6541, 0x02, 0x2e6c, 0x79, 0xb80c},
+	{0x6541, 0x02, 0x2e6c, 0x02, 0x028a},
+
+	/* Return to Eden /SD (Spectrum48) */
+	{0x5f43, 0xca, 0x828c, 0x41, 0x9f5e},
+	{0x5f43, 0xca, 0x828c, 0xca, 0x6e1b},
+
+	/* Worm in Paradise /SD (Spectrum48) */
+	{0x5ebb, 0xf1, 0x4dec, 0x68, 0x4909},
+	{0x5ebb, 0xf1, 0x4dec, 0xf1, 0xcc1a},
+
+	/* Knight Orc, pt. 1 (Amiga) */
+	{0xbb93, 0x36, 0x6a05, 0xad, 0xe52d},
+
+	/* Knight Orc, pt. 1 (ST) */
+	{0xbb6e, 0xad, 0x4d40, 0x24, 0x3bcd},
+
+	/* Knight Orc, pt. 2 (Amiga/ST) */
+	{0xc58e, 0x4a, 0x4e9d, 0xc1, 0xe2bf},
+
+	/* Knight Orc, pt. 3 (Amiga/ST) */
+	{0xcb9a, 0x0f, 0x0804, 0x86, 0x6487},
+
+	/* Knight Orc, pt. 1 (PC) */
+	{0xbb6e, 0xa6, 0x9753, 0x1d, 0x2e7f},
+	{0xbb6e, 0xa6, 0x9753, 0xa6, 0x001d},
+
+	/* Knight Orc, pt. 2 (PC) */
+	{0xc58e, 0x43, 0xe9ce, 0xba, 0x5e4c},
+	{0xc58e, 0x43, 0xe9ce, 0x43, 0xa8f0},
+
+	/* Knight Orc, pt. 3 (PC) */
+	{0xcb9a, 0x08, 0x6c36, 0x7f, 0xf0d4},
+	{0xcb9a, 0x08, 0x6c36, 0x08, 0x2d08},
+
+	/* Knight Orc, pt. 1 (Commodore 64 Gfx) */
+	{0x8970, 0x6b, 0x3c7b, 0xe2, 0xb6f3},
+
+	/* Knight Orc, pt. 1 (Spectrum48) */
+	{0x86d0, 0xb7, 0xadbd, 0x2e, 0x43e1},
+
+	/* Gnome Ranger, pt. 1 (Amiga/ST) */
+	{0xb1a9, 0x80, 0x5fb7, 0xf7, 0x5c6c},
+
+	/* Gnome Ranger, pt. 2 (Amiga/ST) */
+	{0xab9d, 0x31, 0xbe6d, 0xa8, 0xcb96},
+
+	/* Gnome Ranger, pt. 3 (Amiga/ST) */
+	{0xae28, 0x87, 0xb6b6, 0xfe, 0x760c},
+
+	/* Gnome Ranger, pt. 1 (PC) */
+	{0xb1aa, 0xad, 0xaf47, 0x24, 0x5cfd},
+	{0xb1aa, 0xad, 0xaf47, 0xad, 0xe0ed},
+
+	/* Gnome Ranger, pt. 1 (ST-var) */
+	{0xb19e, 0x92, 0x8f96, 0x09, 0x798c},
+
+	/* Gnome Ranger, pt. 2 (PC/ST-var) */
+	{0xab8b, 0xbf, 0x31e6, 0x36, 0x811c},
+	{0xab8b, 0xbf, 0x31e6, 0xbf, 0x8ff3},
+
+	/* Gnome Ranger, pt. 3 (PC/ST-var) */
+	{0xae16, 0x81, 0x8741, 0xf8, 0x47fb},
+	{0xae16, 0x81, 0x8741, 0x81, 0xc8eb},
+
+	/* Gnome Ranger, pt. 1 (Commodore 64 TO) */
+	{0xad41, 0xa8, 0x42c5, 0x1f, 0x7d1e},
+
+	/* Gnome Ranger, pt. 2 (Commodore 64 TO) */
+	{0xa735, 0xf7, 0x2e08, 0x6e, 0x780e},
+
+	/* Gnome Ranger, pt. 3 (Commodore 64 TO) */
+	{0xa9c0, 0x9e, 0x0d70, 0x15, 0x3e6b},
+
+	/* Gnome Ranger, pt. 1 (Commodore 64 Gfx) */
+	{0x908e, 0x0d, 0x58a7, 0x84, 0xab1d},
+
+	/* Gnome Ranger, pt. 2 (Commodore 64 Gfx) */
+	{0x8f6f, 0x0a, 0x411a, 0x81, 0x12bc},
+
+	/* Gnome Ranger, pt. 3 (Commodore 64 Gfx) */
+	{0x9060, 0xbb, 0xe75d, 0x32, 0x14e7},
+
+	/* Lords of Time /T&M (PC) */
+	{0xb57c, 0x44, 0x7779, 0xbb, 0x31a6},
+	{0xb57c, 0x44, 0x7779, 0x44, 0xea72},
+
+	/* Red Moon /T&M (PC) */
+	{0xa69e, 0x6c, 0xb268, 0xe3, 0x4cef},
+	{0xa69e, 0x6c, 0xb268, 0x6c, 0x3799},
+
+	/* Price of Magik /T&M (PC) */
+	{0xbac7, 0x7f, 0xddb2, 0xf6, 0x6ab3},
+	{0xbac7, 0x7f, 0xddb2, 0x7f, 0x905c},
+
+	/* Lords of Time /T&M (ST) */
+	{0xb579, 0x89, 0x3e89, 0x00, 0xa2b7},
+
+	/* Red Moon /T&M (ST) */
+	{0xa698, 0x41, 0xcaca, 0xb8, 0xeeac},
+
+	/* Price of Magik /T&M (ST) */
+	{0xbac4, 0x80, 0xa750, 0xf7, 0xe030},
+
+	/* Lords of Time /T&M (Amiga) */
+	{0xb576, 0x2a, 0x7239, 0xa1, 0x2ea6},
+
+	/* Red Moon /T&M (Amiga) */
+	{0xa692, 0xd1, 0x6a99, 0x48, 0x50ff},
+
+	/* Price of Magik /T&M (Amiga) */
+	{0xbaca, 0x3a, 0x221b, 0xb1, 0x55bb},
+
+	/* Lords of Time /T&M (Commodore 64 TO) */
+	{0xb38c, 0x37, 0x9f8e, 0xae, 0xc6b1},
+
+	/* Red Moon /T&M (Commodore 64 TO) */
+	{0xa4e2, 0xa6, 0x016d, 0x1d, 0x31ab},
+
+	/* Price of Magik /T&M (Commodore 64 TO) */
+	{0xb451, 0xa8, 0x2682, 0x1f, 0x5de2},
+
+	/* Lords of Time /T&M (Commodore 64 Gfx) */
+	{0x9070, 0x43, 0x45d4, 0xba, 0x02eb},
+
+	/* Red Moon /T&M (Commodore 64 Gfx) */
+	{0x903f, 0x6b, 0x603e, 0xe2, 0x9f59},
+
+	/* Price of Magik /T&M (Commodore 64 Gfx) */
+	{0x8f51, 0xb2, 0x6c9a, 0x29, 0xde3b},
+
+	/* Lords of Time /T&M (Spectrum48) */
+	{0x8950, 0xa1, 0xbb16, 0x18, 0x2828},
+	{0x8950, 0xa1, 0xbb16, 0xa1, 0x1ea2},
+
+	/* Red Moon /T&M (Spectrum48) */
+	{0x8813, 0x11, 0x22de, 0x88, 0x18b8},
+	{0x8813, 0x11, 0x22de, 0x11, 0xd0cd},
+
+	/* Price of Magik /T&M (Spectrum48) */
+	{0x8a60, 0x2a, 0x29ed, 0xa1, 0x5e4d},
+
+	/* Lancelot, pt. 1 (Amiga/PC/ST) */
+	{0xc0cf, 0x4e, 0xb7fa, 0xc5, 0x4400},
+
+	/* Lancelot, pt. 2 (Amiga/PC/ST) */
+	{0xd5e9, 0x6a, 0x4192, 0xe1, 0x3b1e},
+
+	/* Lancelot, pt. 3 (Amiga/PC/ST) */
+	{0xbb8f, 0x1a, 0x7487, 0x91, 0x877d},
+
+	/* Lancelot, pt. 1 (Commodore 64 TO) */
+	{0xb4c9, 0x94, 0xd784, 0x0b, 0x203e},
+
+	/* Lancelot, pt. 2 (Commodore 64 TO) */
+	{0xb729, 0x51, 0x8ee5, 0xc8, 0xf1c9},
+
+	/* Lancelot, pt. 3 (Commodore 64 TO) */
+	{0xb702, 0xe4, 0x1809, 0x5b, 0x25b2},
+
+	/* Lancelot, pt. 1 (Commodore 64 Gfx) */
+	{0x8feb, 0xba, 0xa800, 0x31, 0x5bfa},
+
+	/* Lancelot, pt. 2 (Commodore 64 Gfx) */
+	{0x8f6b, 0xfa, 0x0f7e, 0x71, 0x75f2},
+
+	/* Lancelot, pt. 3 (Commodore 64 Gfx) */
+	{0x8f71, 0x2f, 0x0ddc, 0xa6, 0x3e87},
+
+	/* Ingrid's Back, pt. 1 (PC) */
+	{0xd19b, 0xad, 0x306d, 0x24, 0x4504},
+	{0xd19b, 0xad, 0x306d, 0xad, 0x878e},
+
+	/* Ingrid's Back, pt. 2 (PC) */
+	{0xc5a5, 0xfe, 0x3c98, 0x75, 0x8950},
+	{0xc5a5, 0xfe, 0x3c98, 0xfe, 0x8b7b},
+
+	/* Ingrid's Back, pt. 3 (PC) */
+	{0xd7ae, 0x9e, 0x1878, 0x15, 0xadb0},
+	{0xd7ae, 0x9e, 0x1878, 0x9e, 0xaf9b},
+
+	/* Ingrid's Back, pt. 1 (Amiga) */
+	{0xd188, 0x13, 0xdc60, 0x8a, 0x755c},
+
+	/* Ingrid's Back, pt. 2 (Amiga) */
+	{0xc594, 0x03, 0xea95, 0x7a, 0xb5a8},
+
+	/* Ingrid's Back, pt. 3 (Amiga) */
+	{0xd79f, 0xb5, 0x1661, 0x2c, 0xbf5d},
+
+	/* Ingrid's Back, pt. 1 (ST) */
+	{0xd183, 0x83, 0xef72, 0xfa, 0xb04f},
+
+	/* Ingrid's Back, pt. 2 (ST) */
+	{0xc58f, 0x65, 0xf337, 0xdc, 0x900a},
+
+	/* Ingrid's Back, pt. 3 (ST) */
+	{0xd79a, 0x57, 0x49c5, 0xce, 0xe0f9},
+
+	/* Ingrid's Back, pt. 1 (Commodore 64 TO) */
+	{0xb770, 0x03, 0x9a03, 0x7a, 0xdc6a},
+
+	/* Ingrid's Back, pt. 2 (Commodore 64 TO) */
+	{0xb741, 0xb6, 0x2aa5, 0x2d, 0x5a6c},
+
+	/* Ingrid's Back, pt. 3 (Commodore 64 TO) */
+	{0xb791, 0xa1, 0xd065, 0x18, 0xaa0c},
+
+	/* Ingrid's Back, pt. 1 (Commodore 64 Gfx) */
+	{0x9089, 0xce, 0xc5e2, 0x44, 0xeff4},
+
+	/* Ingrid's Back, pt. 2 (Commodore 64 Gfx) */
+	{0x908d, 0x80, 0x30c7, 0xf6, 0x2a11},
+
+	/* Ingrid's Back, pt. 3 (Commodore 64 Gfx) */
+	{0x909e, 0x9f, 0xdecc, 0x15, 0xf4da},
+
+	{0x0000, 0x00, 0x0000, 0x00, 0x0000},
+};
+
+
 // TODO: The list of Level 9 games and detection entries needs to be filled out
 const PlainGameDescriptor LEVEL9_GAME_LIST[] = {
 	{ "level9", "Level 9 IF Game" },
diff --git a/engines/glk/level9/level9.cpp b/engines/glk/level9/level9.cpp
index 7fe85ee..31caeed 100644
--- a/engines/glk/level9/level9.cpp
+++ b/engines/glk/level9/level9.cpp
@@ -29,7 +29,8 @@ namespace Level9 {
 
 Level9 *g_vm = nullptr;
 
-Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
+Level9::Level9(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
+		_detection(startdata, FileSize) {
 	g_vm = this;
 }
 
diff --git a/engines/glk/level9/level9.h b/engines/glk/level9/level9.h
index 90c441e..ee7dbce 100644
--- a/engines/glk/level9/level9.h
+++ b/engines/glk/level9/level9.h
@@ -27,6 +27,7 @@
 #include "common/serializer.h"
 #include "common/stack.h"
 #include "glk/glk_api.h"
+#include "glk/level9/detection.h"
 
 namespace Glk {
 namespace Level9 {
@@ -46,6 +47,8 @@ private:
 	 */
 	void deinitialize();
 public:
+	GameDetection _detection;
+public:
 	/**
 	 * Constructor
 	 */
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index 2ce60a5..b4ab1a8 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -77,8 +77,8 @@ enum L9GfxTypes { GFX_V2, GFX_V3A, GFX_V3B, GFX_V3C };
 
 /* Global Variables */
 L9BYTE *startfile, *pictureaddress, *picturedata;
-L9BYTE *startdata;
-L9UINT32 FileSize, picturesize;
+byte *startdata;
+size_t FileSize, picturesize;
 
 L9BYTE *L9Pointers[12];
 L9BYTE *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
diff --git a/engines/glk/level9/level9_main.h b/engines/glk/level9/level9_main.h
index ec04db3..95d9f18 100644
--- a/engines/glk/level9/level9_main.h
+++ b/engines/glk/level9/level9_main.h
@@ -86,6 +86,9 @@ struct Bitmap {
 #define L9SETWORD(x,val) WRITE_LE_UINT16(x, val)
 #define L9SETDWORD(x,val) WRITE_LE_UINT32(x, val)
 
+extern byte *startdata;
+extern size_t FileSize;
+
 extern void level9_initialize();
 
 /* routines provided by os dependent code */
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index 586b808..ab171c3 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -20,6 +20,7 @@
  *
  */
 
+#include "glk/level9/os_glk.h"
 #include "glk/level9/level9_main.h"
 #include "glk/level9/level9.h"
 #include "common/textconsole.h"
@@ -27,9 +28,6 @@
 namespace Glk {
 namespace Level9 {
 
-#define BYTE_MAX 0xff
-#define BITS_PER_CHAR 8
-
 /*---------------------------------------------------------------------*/
 /*  Module variables, miscellaneous other stuff                        */
 /*---------------------------------------------------------------------*/
@@ -83,7 +81,6 @@ typedef L9UINT32 gln_uint32;
 extern void save();
 extern void restore();
 extern gln_bool Cheating;
-extern gln_byte *startdata;
 extern gln_uint32 FileSize;
 
 /* Forward declarations of event wait and other miscellaneous functions. */
@@ -285,1022 +282,6 @@ int gln_strcasecmp(const char *s1, const char *s2) {
 }
 
 /*---------------------------------------------------------------------*/
-/*  Glk port CRC functions                                             */
-/*---------------------------------------------------------------------*/
-
-/* CRC table initialization polynomial. */
-static const gln_uint16 GLN_CRC_POLYNOMIAL = 0xa001;
-
-
-/*
- * gln_get_buffer_crc()
- *
- * Return the CRC of the bytes buffer[0..length-1].
- *
- * This algorithm is selected to match the CRCs used in L9cut.  Because of
- * the odd way CRCs are padded when L9cut calculates the CRC, this function
- * allows a count of NUL padding bytes to be included within the return CRC.
- */
-static gln_uint16 gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding) {
-	static int is_initialized = FALSE;
-	static gln_uint16 crc_table[BYTE_MAX + 1];
-
-	const char *buffer = (const char *) void_buffer;
-	gln_uint16 crc;
-	size_t index;
-
-	/* Build the static CRC lookup table on first call. */
-	if (!is_initialized) {
-		for (index = 0; index < BYTE_MAX + 1; index++) {
-			int bit;
-
-			crc = (gln_uint16) index;
-			for (bit = 0; bit < BITS_PER_CHAR; bit++)
-				crc = crc & 1 ? GLN_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
-
-			crc_table[index] = crc;
-		}
-
-		is_initialized = TRUE;
-
-		/* CRC lookup table self-test, after is_initialized set -- recursion. */
-		assert(gln_get_buffer_crc("123456789", 9, 0) == 0xbb3d);
-	}
-
-	/* Start with zero in the crc, then update using table entries. */
-	crc = 0;
-	for (index = 0; index < length; index++)
-		crc = crc_table[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
-
-	/* Add in any requested NUL padding bytes. */
-	for (index = 0; index < padding; index++)
-		crc = crc_table[crc & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
-
-	return crc;
-}
-
-
-/*---------------------------------------------------------------------*/
-/*  Glk port game identification data and identification functions     */
-/*---------------------------------------------------------------------*/
-
-/*
- * The game's name, suitable for printing out on a status line, or other
- * location where game information is relevant.  It's generated on demand,
- * and may be re-requested when, say, the game changes, perhaps by moving to
- * the next part of a multipart game.
- */
-static const char *gln_gameid_game_name = nullptr;
-
-
-/*
- * The following game database is obtained from L9cut's l9data_d.h, and
- * lets us find a game's name from its data CRC.  Entries marked "WANTED" in
- * l9data_d.h, and file commentary, have been removed for brevity, and the
- * file has been reformatted (patchlevel data removed).
- *
- * The version of l9data_d.h used is 050 (22 Oct 2002).
- */
-struct gln_game_table_t {
-	const gln_uint16 length;    /* Datafile length in bytes */
-	const gln_byte checksum;    /* 8-bit checksum, last datafile byte */
-	const gln_uint16 crc;       /* 16-bit CRC, L9cut-internal */
-	const char *const name;     /* Game title and platform */
-};
-typedef const gln_game_table_t *gln_game_tableref_t;
-
-static const gln_game_table_t GLN_GAME_TABLE[] = {
-	{0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
-
-	{0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
-	{0x630e, 0xbe, 0x3374, "Dungeon Adventure (MSX)"},
-
-	{0x5eb9, 0x30, 0xe99a, "Lords of Time (Amstrad CPC)"},
-	{0x5eb9, 0x5d, 0xc098, "Lords of Time (MSX)"},
-	{0x5eb9, 0x6e, 0xc689, "Lords of Time (Spectrum)"},
-
-	{0x5fab, 0x5c, 0xa309, "Snowball (Amstrad CPC)"},
-	{0x5fab, 0x2f, 0x8aa2, "Snowball (MSX)"},
-
-	{0x60c4, 0x28, 0x0154, "Return to Eden (Amstrad CPC/Commodore 64[v1])"},
-	{0x6064, 0x01, 0x5b3c, "Return to Eden (BBC[v1])"},
-	{0x6064, 0x95, 0x510c, "Return to Eden (Commodore 64[v2])"},
-	{0x6064, 0xda, 0xe610, "Return to Eden (Commodore 64[v2] *corrupt*)"},
-	{0x6064, 0xbd, 0x73ec, "Return to Eden (Atari *corrupt*)"},
-	{0x6047, 0x6c, 0x17ab, "Return to Eden (BBC[v2])"},
-	{0x5ca1, 0x33, 0x1c43, "Return to Eden (Spectrum[v1])"},
-	{0x5cb7, 0x64, 0x0790, "Return to Eden (Spectrum[v2])"},
-	{0x5cb7, 0xfe, 0x3533, "Return to Eden (MSX)"},
-
-	{0x34b3, 0x20, 0xccda, "Erik the Viking (BBC/Commodore 64)"},
-	{0x34b3, 0x53, 0x8f00, "Erik the Viking (Spectrum)"},
-	{0x34b3, 0xc7, 0x9058, "Erik the Viking (Amstrad CPC)"},
-
-	{
-		0x63be, 0xd6, 0xcf5d,
-		"Emerald Isle (Atari/Commodore 64/Amstrad CPC/Spectrum)"
-	},
-	{0x63be, 0x0a, 0x21ed, "Emerald Isle (MSX *corrupt*)"},
-	{0x378c, 0x8d, 0x3a21, "Emerald Isle (BBC)"},
-
-	{0x506c, 0xf0, 0xba72, "Red Moon (BBC/Commodore 64/Amstrad CPC/MSX)"},
-	{0x505d, 0x32, 0x2dcf, "Red Moon (Spectrum)"},
-
-	{0x772b, 0xcd, 0xa503, "Worm in Paradise (Spectrum 128)"},
-	{0x546c, 0xb7, 0x9420, "Worm in Paradise (Spectrum 48)"},
-	{0x6d84, 0xf9, 0x49ae, "Worm in Paradise (Commodore 64 *corrupt*)"},
-	{0x6d84, 0xc8, 0x943f, "Worm in Paradise (Commodore 64 *fixed*)"},
-	{0x6030, 0x47, 0x46ad, "Worm in Paradise (Amstrad CPC)"},
-	{0x5828, 0xbd, 0xe7cb, "Worm in Paradise (BBC)"},
-
-	{0x7410, 0x5e, 0x60be, "Price of Magik (Spectrum 128)"},
-	{0x5aa4, 0xc1, 0x10a0, "Price of Magik (Spectrum 48[v1])"},
-	{0x5aa4, 0xc1, 0xeda4, "Price of Magik (Spectrum 48[v2])"},
-	{0x6fc6, 0x14, 0xf9b6, "Price of Magik (Commodore 64)"},
-	{0x5aa4, 0xc1, 0xbbf4, "Price of Magik (Amstrad CPC)"},
-	{0x5671, 0xbc, 0xff35, "Price of Magik (BBC)"},
-
-	{0x76f4, 0x5e, 0x1fe5, "Colossal Adventure /JoD (Amiga/PC)"},
-	{0x76f4, 0x5a, 0xcf4b, "Colossal Adventure /JoD (ST)"},
-	{0x6e60, 0x83, 0x18e0, "Adventure Quest /JoD (Amiga/PC)"},
-	{0x6e5c, 0xf6, 0xd356, "Adventure Quest /JoD (ST)"},
-	{0x6f0c, 0x95, 0x1f64, "Dungeon Adventure /JoD (Amiga/PC/ST)"},
-
-	{0x6f70, 0x40, 0xbd91, "Colossal Adventure /JoD (MSX)"},
-
-	{0x6f6e, 0x78, 0x28cd, "Colossal Adventure /JoD (Spectrum 128)"},
-	{0x6970, 0xd6, 0xa820, "Adventure Quest /JoD (Spectrum 128)"},
-	{0x6de8, 0x4c, 0xd795, "Dungeon Adventure /JoD (Spectrum 128)"},
-
-	{
-		0x6f4d, 0xcb, 0xe8f2,
-		"Colossal Adventure /JoD (Amstrad CPC128[v1]/Spectrum +3)"
-	},
-	{0x6f6a, 0xa5, 0x8dd2, "Colossal Adventure /JoD (Amstrad CPC128[v2])"},
-	{0x6968, 0x32, 0x0c01, "Adventure Quest /JoD (Amstrad CPC128/Spectrum +3)"},
-	{0x6dc0, 0x63, 0x5d95, "Dungeon Adventure /JoD (Amstrad CPC128/Spectrum +3)"},
-
-	{0x5e31, 0x7c, 0xaa54, "Colossal Adventure /JoD (Amstrad CPC64)"},
-	{0x5b50, 0x66, 0x1800, "Adventure Quest /JoD (Amstrad CPC64)"},
-	{0x58a6, 0x24, 0xb50f, "Dungeon Adventure /JoD (Amstrad CPC64)"},
-
-	{0x6c8e, 0xb6, 0x9be3, "Colossal Adventure /JoD (Commodore 64)"},
-	{0x63b6, 0x2e, 0xef38, "Adventure Quest /JoD (Commodore 64)"},
-	{0x6bd2, 0x65, 0xa41f, "Dungeon Adventure /JoD (Commodore 64)"},
-
-	{0x5b16, 0x3b, 0xe2aa, "Colossal Adventure /JoD (Atari)"},
-	{0x5b58, 0x50, 0x332e, "Adventure Quest /JoD (Atari)"},
-	{0x593a, 0x80, 0x7a34, "Dungeon Adventure /JoD (Atari)"},
-
-	{0x5a8e, 0xf2, 0x7cca, "Colossal Adventure /JoD (Spectrum 48)"},
-	{0x5ace, 0x11, 0xdc12, "Adventure Quest /JoD (Spectrum 48)"},
-	{0x58a3, 0x38, 0x8ce4, "Dungeon Adventure /JoD (Spectrum 48)"},
-
-	{0x7b31, 0x6e, 0x2e2b, "Snowball /SD (Amiga/ST)"},
-	{0x7d16, 0xe6, 0x5438, "Return to Eden /SD (Amiga/ST)"},
-	{0x7cd9, 0x0c, 0x4df1, "Worm in Paradise /SD (Amiga/ST)"},
-
-	{0x7b2f, 0x70, 0x6955, "Snowball /SD (Mac/PC/Spectrum 128)"},
-	{0x7b2f, 0x70, 0x6f6c, "Snowball /SD (Amstrad CPC/Spectrum +3)"},
-	{0x7d14, 0xe8, 0xfbab, "Return to Eden /SD (PC)"},
-	{0x7cff, 0xf8, 0x6044, "Return to Eden /SD (Amstrad CPC/Spectrum +3)"},
-	{0x7cf8, 0x24, 0x9c1c, "Return to Eden /SD (Mac)"},
-	{0x7c55, 0x18, 0xdaee, "Return to Eden /SD (Spectrum 128)"},
-	{
-		0x7cd7, 0x0e, 0x4feb,
-		"Worm in Paradise /SD (Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3)"
-	},
-
-	{0x7363, 0x65, 0xa0ab, "Snowball /SD (Commodore 64)"},
-	{0x772f, 0xca, 0x8602, "Return to Eden /SD (Commodore 64)"},
-	{0x788d, 0x72, 0x888a, "Worm in Paradise /SD (Commodore 64)"},
-
-	{0x6bf8, 0x3f, 0xc9f7, "Snowball /SD (Atari)"},
-	{0x60f7, 0x68, 0xc2bc, "Return to Eden /SD (Atari)"},
-	{0x6161, 0xf3, 0xe6d7, "Worm in Paradise /SD (Atari)"},
-
-	{0x67a3, 0x9d, 0x1d05, "Snowball /SD (Apple ][)"},
-	{0x639c, 0x8b, 0x06e2, "Return to Eden /SD (Apple ][)"},
-	{0x60dd, 0xf2, 0x5bb8, "Worm in Paradise /SD (Apple ][)"},
-
-	{0x6541, 0x02, 0x2e6c, "Snowball /SD (Spectrum 48)"},
-	{0x5f43, 0xca, 0x828c, "Return to Eden /SD (Spectrum 48)"},
-	{0x5ebb, 0xf1, 0x4dec, "Worm in Paradise /SD (Spectrum 48)"},
-
-	{0x8333, 0xb7, 0xe2ac, "Adrian Mole I, pt. 1 (Commodore 64)"},
-	{0x844d, 0x50, 0x5353, "Adrian Mole I, pt. 2 (Commodore 64)"},
-	{0x8251, 0x5f, 0x862a, "Adrian Mole I, pt. 3 (Commodore 64)"},
-	{0x7a78, 0x5e, 0x6ea3, "Adrian Mole I, pt. 4 (Commodore 64)"},
-
-	{0x7c6f, 0x0f, 0xba24, "Adrian Mole I, pt. 1 (Amstrad CPC)"},
-
-	{0x72fa, 0x8b, 0x6f12, "Adrian Mole I, pt. 1 (Spectrum)"},
-	{0x738e, 0x5b, 0x7e3d, "Adrian Mole I, pt. 2 (Spectrum)"},
-	{0x7375, 0xe5, 0x3f3e, "Adrian Mole I, pt. 3 (Spectrum)"},
-	{0x78d5, 0xe3, 0xcd7d, "Adrian Mole I, pt. 4 (Spectrum)"},
-
-	{0x3a31, 0xe5, 0x0bdb, "Adrian Mole I, pt. 1 (BBC)"},
-	{0x37f1, 0x77, 0xd231, "Adrian Mole I, pt. 2 (BBC)"},
-	{0x3900, 0x1c, 0x5d9a, "Adrian Mole I, pt. 3 (BBC)"},
-	{0x3910, 0xac, 0x07f9, "Adrian Mole I, pt. 4 (BBC)"},
-	{0x3ad6, 0xa7, 0x95d2, "Adrian Mole I, pt. 5 (BBC)"},
-	{0x38a5, 0x0f, 0xdefc, "Adrian Mole I, pt. 6 (BBC)"},
-	{0x361e, 0x7e, 0xfd9f, "Adrian Mole I, pt. 7 (BBC)"},
-	{0x3934, 0x75, 0xe141, "Adrian Mole I, pt. 8 (BBC)"},
-	{0x3511, 0xcc, 0xd829, "Adrian Mole I, pt. 9 (BBC)"},
-	{0x38dd, 0x31, 0x2534, "Adrian Mole I, pt. 10 (BBC)"},
-	{0x39c0, 0x44, 0x89df, "Adrian Mole I, pt. 11 (BBC)"},
-	{0x3a12, 0x8f, 0xc2bd, "Adrian Mole I, pt. 12 (BBC)"},
-
-	{0x7931, 0xb9, 0xc51b, "Adrian Mole II, pt. 1 (Commodore 64/Amstrad CPC)"},
-	{0x7cdf, 0xa5, 0x43e3, "Adrian Mole II, pt. 2 (Commodore 64/Amstrad CPC)"},
-	{0x7a0c, 0x97, 0x4bea, "Adrian Mole II, pt. 3 (Commodore 64/Amstrad CPC)"},
-	{0x7883, 0xe2, 0xee0e, "Adrian Mole II, pt. 4 (Commodore 64/Amstrad CPC)"},
-
-	{0x6841, 0x4a, 0x94e7, "Adrian Mole II, pt. 1 (Spectrum)"},
-	{0x6bc0, 0x62, 0xab3d, "Adrian Mole II, pt. 2 (Spectrum)"},
-	{0x692c, 0x21, 0x2015, "Adrian Mole II, pt. 3 (Spectrum)"},
-	{0x670a, 0x94, 0xa2a6, "Adrian Mole II, pt. 4 (Spectrum)"},
-
-	{0x593a, 0xaf, 0x30e9, "Adrian Mole II, pt. 1 (BBC)"},
-	{0x57e6, 0x8a, 0xc41a, "Adrian Mole II, pt. 2 (BBC)"},
-	{0x5819, 0xcd, 0x1ba0, "Adrian Mole II, pt. 3 (BBC)"},
-	{0x579b, 0xad, 0xa723, "Adrian Mole II, pt. 4 (BBC)"},
-
-	{0x765d, 0xcd, 0xfc02, "The Archers, pt. 1 (Commodore 64)"},
-	{0x6e58, 0x07, 0xbffc, "The Archers, pt. 2 (Commodore 64)"},
-	{0x7e98, 0x6a, 0x95e5, "The Archers, pt. 3 (Commodore 64)"},
-	{0x81e2, 0xd5, 0xb278, "The Archers, pt. 4 (Commodore 64)"},
-
-	{0x6ce5, 0x58, 0x46de, "The Archers, pt. 1 (Spectrum)"},
-	{0x68da, 0xc1, 0x3b8e, "The Archers, pt. 2 (Spectrum)"},
-	{0x6c67, 0x9a, 0x9a6a, "The Archers, pt. 3 (Spectrum)"},
-	{0x6d91, 0xb9, 0x12a7, "The Archers, pt. 4 (Spectrum)"},
-
-	{0x5834, 0x42, 0xcc9d, "The Archers, pt. 1 (BBC)"},
-	{0x56dd, 0x51, 0xe582, "The Archers, pt. 2 (BBC)"},
-	{0x5801, 0x53, 0xf2ef, "The Archers, pt. 3 (BBC)"},
-	{0x54a4, 0x01, 0xc0ab, "The Archers, pt. 4 (BBC)"},
-
-	{0x579e, 0x97, 0x9faa, "Lords of Time /T&M GD (BBC)"},
-	{0x5500, 0x50, 0xca75, "Red Moon /T&M GD (BBC)"},
-	{0x579a, 0x2a, 0x9373, "Price of Magik /T&M GD (BBC)"},
-
-	{0x4fd2, 0x9d, 0x799a, "Lancelot, pt. 1 GD (BBC)"},
-	{0x4dac, 0xa8, 0x86ed, "Lancelot, pt. 2 GD (BBC)"},
-	{0x4f96, 0x22, 0x30f8, "Lancelot, pt. 3 GD (BBC)"},
-
-	{0x55ce, 0xa1, 0xba12, "Scapeghost, pt. 1 GD (BBC)"},
-	{0x54a6, 0xa9, 0xc9f3, "Scapeghost, pt. 2 GD (BBC)"},
-	{0x51bc, 0xe3, 0x89c3, "Scapeghost, pt. 3 GD (BBC)"},
-
-	{0x46ec, 0x64, 0x2300, "Knight Orc, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x6140, 0x18, 0x4f66, "Knight Orc, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x640e, 0xc1, 0xfc69, "Knight Orc, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x5ff0, 0xf8, 0x3a13, "Gnome Ranger, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x6024, 0x01, 0xaaa9, "Gnome Ranger, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x6036, 0x3d, 0x6c6c, "Gnome Ranger, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x69fe, 0x56, 0xecfb, "Lords of Time /T&M GD (Amstrad CPC/Spectrum +3)"},
-	{0x6888, 0x8d, 0x7f6a, "Red Moon /T&M GD (Amstrad CPC/Spectrum +3)"},
-	{0x5a50, 0xa9, 0xa5fa, "Price of Magik /T&M GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x5c7a, 0x44, 0x460e, "Lancelot, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x53a2, 0x1e, 0x2fae, "Lancelot, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x5914, 0x22, 0x4a31, "Lancelot, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x5a38, 0xf7, 0x876e, "Ingrid's Back, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x531a, 0xed, 0xcf3f, "Ingrid's Back, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x57e4, 0x19, 0xb354, "Ingrid's Back, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x5cbc, 0xa5, 0x0dbe, "Scapeghost, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x5932, 0x4e, 0xb2f5, "Scapeghost, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x5860, 0x95, 0x3227, "Scapeghost, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x74e0, 0x92, 0x885e, "Knight Orc, pt. 1 GD (Spectrum 128)"},
-	{0x6dbc, 0x97, 0x6f55, "Knight Orc, pt. 2 GD (Spectrum 128)"},
-	{0x7402, 0x07, 0x385f, "Knight Orc, pt. 3 GD (Spectrum 128)"},
-
-	{0x52aa, 0xdf, 0x7b5b, "Gnome Ranger, pt. 1 GD (Spectrum 128)"},
-	{0x6ffa, 0xdb, 0xdde2, "Gnome Ranger, pt. 2 GD (Spectrum 128)"},
-	{0x723a, 0x69, 0x039b, "Gnome Ranger, pt. 3 GD (Spectrum 128)"},
-
-	{0x6f1e, 0xda, 0x2ce0, "Lords of Time /T&M GD (Spectrum 128)"},
-	{0x6da0, 0xb8, 0x3802, "Red Moon /T&M GD (Spectrum 128)"},
-	{0x6108, 0xdd, 0xefe7, "Price of Magik /T&M GD (Spectrum 128)"},
-
-	{0x768c, 0xe8, 0x8fc6, "Lancelot, pt. 1 GD (Spectrum 128)"},
-	{0x76b0, 0x1d, 0x0fcd, "Lancelot, pt. 2 GD (Spectrum 128)"},
-	{0x765e, 0x4f, 0x3b73, "Lancelot, pt. 3 GD (Spectrum 128)"},
-
-	{0x76a0, 0x3a, 0xb803, "Ingrid's Back, pt. 1 GD (Spectrum 128)"},
-	{0x7674, 0x0b, 0xe92f, "Ingrid's Back, pt. 2 GD (Spectrum 128)"},
-	{0x765e, 0xba, 0x086d, "Ingrid's Back, pt. 3 GD (Spectrum 128)"},
-
-	{0x762e, 0x82, 0x8848, "Scapeghost, pt. 1 GD (Spectrum 128)"},
-	{0x5bd6, 0x35, 0x79ef, "Scapeghost, pt. 2 GD (Spectrum 128)"},
-	{0x6fa8, 0xa4, 0x62c2, "Scapeghost, pt. 3 GD (Spectrum 128)"},
-
-	{0xbb93, 0x36, 0x6a05, "Knight Orc, pt. 1 (Amiga)"},
-	{0xbb6e, 0xad, 0x4d40, "Knight Orc, pt. 1 (ST)"},
-	{0xc58e, 0x4a, 0x4e9d, "Knight Orc, pt. 2 (Amiga/ST)"},
-	{0xcb9a, 0x0f, 0x0804, "Knight Orc, pt. 3 (Amiga/ST)"},
-
-	{0xbb6e, 0xa6, 0x9753, "Knight Orc, pt. 1 (PC)"},
-	{0xc58e, 0x43, 0xe9ce, "Knight Orc, pt. 2 (PC)"},
-	{0xcb9a, 0x08, 0x6c36, "Knight Orc, pt. 3 (PC)"},
-
-	{0x898a, 0x43, 0xfc8b, "Knight Orc, pt. 1 (Apple ][)"},
-	{0x8b9f, 0x61, 0x7288, "Knight Orc, pt. 2 (Apple ][)"},
-	{0x8af9, 0x61, 0x7542, "Knight Orc, pt. 3 (Apple ][)"},
-
-	{0x8970, 0x6b, 0x3c7b, "Knight Orc, pt. 1 (Commodore 64 Gfx)"},
-	{0x8b90, 0x4e, 0x098c, "Knight Orc, pt. 2 (Commodore 64 Gfx)"},
-	{0x8aea, 0x4e, 0xca54, "Knight Orc, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x86d0, 0xb7, 0xadbd, "Knight Orc, pt. 1 (Spectrum 48)"},
-	{0x8885, 0x22, 0xe293, "Knight Orc, pt. 2 (Spectrum 48)"},
-	{0x87e5, 0x0e, 0xdc33, "Knight Orc, pt. 3 (Spectrum 48)"},
-
-	{0xb1a9, 0x80, 0x5fb7, "Gnome Ranger, pt. 1 (Amiga/ST)"},
-	{0xab9d, 0x31, 0xbe6d, "Gnome Ranger, pt. 2 (Amiga/ST)"},
-	{0xae28, 0x87, 0xb6b6, "Gnome Ranger, pt. 3 (Amiga/ST)"},
-
-	{0xb0ec, 0xc2, 0x0053, "Gnome Ranger, pt. 1 (ST[v1])"},
-	{0xaf82, 0x83, 0x19f7, "Gnome Ranger, pt. 2 (ST[v1])"},
-
-	{0xb1aa, 0xad, 0xaf47, "Gnome Ranger, pt. 1 (PC)"},
-	{0xb19e, 0x92, 0x8f96, "Gnome Ranger, pt. 1 (ST[v2])"},
-	{0xab8b, 0xbf, 0x31e6, "Gnome Ranger, pt. 2 (PC/ST[v2])"},
-	{0xae16, 0x81, 0x8741, "Gnome Ranger, pt. 3 (PC/ST[v2])"},
-
-	{0xad41, 0xa8, 0x42c5, "Gnome Ranger, pt. 1 (Commodore 64 TO)"},
-	{0xa735, 0xf7, 0x2e08, "Gnome Ranger, pt. 2 (Commodore 64 TO)"},
-	{0xa9c0, 0x9e, 0x0d70, "Gnome Ranger, pt. 3 (Commodore 64 TO)"},
-
-	{0x908e, 0x0d, 0x58a7, "Gnome Ranger, pt. 1 (Commodore 64 Gfx)"},
-	{0x8f6f, 0x0a, 0x411a, "Gnome Ranger, pt. 2 (Commodore 64 Gfx)"},
-	{0x9060, 0xbb, 0xe75d, "Gnome Ranger, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x8aab, 0xc0, 0xde5f, "Gnome Ranger, pt. 1 (Spectrum 48)"},
-	{0x8ac8, 0x9a, 0xc89b, "Gnome Ranger, pt. 2 (Spectrum 48)"},
-	{0x8a93, 0x4f, 0x10cc, "Gnome Ranger, pt. 3 (Spectrum 48)"},
-
-	{0xb57c, 0x44, 0x7779, "Lords of Time /T&M (PC)"},
-	{0xa69e, 0x6c, 0xb268, "Red Moon /T&M (PC)"},
-	{0xbac7, 0x7f, 0xddb2, "Price of Magik /T&M (PC)"},
-
-	{0xb579, 0x89, 0x3e89, "Lords of Time /T&M (ST)"},
-	{0xa698, 0x41, 0xcaca, "Red Moon /T&M (ST)"},
-	{0xbac4, 0x80, 0xa750, "Price of Magik /T&M (ST)"},
-
-	{0xb576, 0x2a, 0x7239, "Lords of Time /T&M (Amiga)"},
-	{0xa692, 0xd1, 0x6a99, "Red Moon /T&M (Amiga)"},
-	{0xbaca, 0x3a, 0x221b, "Price of Magik /T&M (Amiga)"},
-
-	{0xb563, 0x6a, 0x0c5c, "Lords of Time /T&M (Mac)"},
-	{0xa67c, 0xb8, 0xff41, "Red Moon /T&M (Mac)"},
-	{0xbab2, 0x87, 0x09f5, "Price of Magik /T&M (Mac)"},
-
-	{0xb38c, 0x37, 0x9f8e, "Lords of Time /T&M (Commodore 64 TO)"},
-	{0xa4e2, 0xa6, 0x016d, "Red Moon /T&M (Commodore 64 TO)"},
-	{0xb451, 0xa8, 0x2682, "Price of Magik /T&M (Commodore 64 TO)"},
-
-	{0x9070, 0x43, 0x45d4, "Lords of Time /T&M (Commodore 64 Gfx)"},
-	{0x903f, 0x6b, 0x603e, "Red Moon /T&M (Commodore 64 Gfx)"},
-	{0x8f51, 0xb2, 0x6c9a, "Price of Magik /T&M (Commodore 64 Gfx)"},
-
-	{0x8950, 0xa1, 0xbb16, "Lords of Time /T&M (Spectrum 48)"},
-	{0x8813, 0x11, 0x22de, "Red Moon /T&M (Spectrum 48)"},
-	{0x8a60, 0x2a, 0x29ed, "Price of Magik /T&M (Spectrum 48)"},
-
-	{0xb260, 0xe5, 0xc5b2, "Lords of Time /T&M (PC/ST *USA*)"},
-	{0xa3a4, 0xdf, 0x6732, "Red Moon /T&M (PC/ST *USA*)"},
-	{0xb7a0, 0x7e, 0x2226, "Price of Magik /T&M (PC/ST *USA*)"},
-
-	{0xb257, 0xf8, 0xfbd5, "Lords of Time /T&M (Amiga *USA*)"},
-	{0xa398, 0x82, 0xd031, "Red Moon /T&M (Amiga *USA*)"},
-	{0xb797, 0x1f, 0x84a9, "Price of Magik /T&M (Amiga *USA*)"},
-
-	{0x8d78, 0x3a, 0xba6e, "Lords of Time /T&M (Commodore 64 Gfx *USA*)"},
-	{0x8d56, 0xd3, 0x146a, "Red Moon /T&M (Commodore 64 Gfx *USA*)"},
-	{0x8c46, 0xf0, 0xcaf6, "Price of Magik /T&M (Commodore 64 Gfx *USA*)"},
-
-	{0xc0cf, 0x4e, 0xb7fa, "Lancelot, pt. 1 (Amiga/PC/ST)"},
-	{0xd5e9, 0x6a, 0x4192, "Lancelot, pt. 2 (Amiga/PC/ST)"},
-	{0xbb8f, 0x1a, 0x7487, "Lancelot, pt. 3 (Amiga/PC/ST)"},
-
-	{0xc0bd, 0x57, 0x6ef1, "Lancelot, pt. 1 (Mac)"},
-	{0xd5d7, 0x99, 0x770b, "Lancelot, pt. 2 (Mac)"},
-	{0xbb7d, 0x17, 0xbc42, "Lancelot, pt. 3 (Mac)"},
-
-	{0xb4c9, 0x94, 0xd784, "Lancelot, pt. 1 (Commodore 64 TO)"},
-	{0xb729, 0x51, 0x8ee5, "Lancelot, pt. 2 (Commodore 64 TO)"},
-	{0xb702, 0xe4, 0x1809, "Lancelot, pt. 3 (Commodore 64 TO)"},
-
-	{0x8feb, 0xba, 0xa800, "Lancelot, pt. 1 (Commodore 64 Gfx)"},
-	{0x8f6b, 0xfa, 0x0f7e, "Lancelot, pt. 2 (Commodore 64 Gfx)"},
-	{0x8f71, 0x2f, 0x0ddc, "Lancelot, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x8ade, 0xf2, 0xfffb, "Lancelot, pt. 1 (Spectrum 48)"},
-	{0x8b0e, 0xfb, 0x0bab, "Lancelot, pt. 2 (Spectrum 48)"},
-	{0x8ab3, 0xc1, 0xcb62, "Lancelot, pt. 3 (Spectrum 48)"},
-
-	{0xbba4, 0x94, 0x0871, "Lancelot, pt. 1 (Amiga/PC *USA*)"},
-	{0xd0c0, 0x56, 0x8c48, "Lancelot, pt. 2 (Amiga/PC *USA*)"},
-	{0xb6ac, 0xc6, 0xaea0, "Lancelot, pt. 3 (Amiga/PC *USA*)"},
-
-	{0x8afc, 0x07, 0x8321, "Lancelot, pt. 1 (Commodore 64 Gfx *USA*)"},
-	{0x8aec, 0x13, 0x6791, "Lancelot, pt. 2 (Commodore 64 Gfx *USA*)"},
-	{0x8aba, 0x0d, 0x5602, "Lancelot, pt. 3 (Commodore 64 Gfx *USA*)"},
-
-	{0xd19b, 0xad, 0x306d, "Ingrid's Back, pt. 1 (PC)"},
-	{0xc5a5, 0xfe, 0x3c98, "Ingrid's Back, pt. 2 (PC)"},
-	{0xd7ae, 0x9e, 0x1878, "Ingrid's Back, pt. 3 (PC)"},
-
-	{0xd188, 0x13, 0xdc60, "Ingrid's Back, pt. 1 (Amiga)"},
-	{0xc594, 0x03, 0xea95, "Ingrid's Back, pt. 2 (Amiga)"},
-	{0xd79f, 0xb5, 0x1661, "Ingrid's Back, pt. 3 (Amiga)"},
-
-	{0xd183, 0x83, 0xef72, "Ingrid's Back, pt. 1 (ST)"},
-	{0xc58f, 0x65, 0xf337, "Ingrid's Back, pt. 2 (ST)"},
-	{0xd79a, 0x57, 0x49c5, "Ingrid's Back, pt. 3 (ST)"},
-
-	{0xb770, 0x03, 0x9a03, "Ingrid's Back, pt. 1 (Commodore 64 TO)"},
-	{0xb741, 0xb6, 0x2aa5, "Ingrid's Back, pt. 2 (Commodore 64 TO)"},
-	{0xb791, 0xa1, 0xd065, "Ingrid's Back, pt. 3 (Commodore 64 TO)"},
-
-	{0x9089, 0xce, 0xc5e2, "Ingrid's Back, pt. 1 (Commodore 64 Gfx)"},
-	{0x908d, 0x80, 0x30c7, "Ingrid's Back, pt. 2 (Commodore 64 Gfx)"},
-	{0x909e, 0x9f, 0xdecc, "Ingrid's Back, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x8ab7, 0x68, 0xee57, "Ingrid's Back, pt. 1 (Spectrum 48)"},
-	{0x8b1e, 0x84, 0x2538, "Ingrid's Back, pt. 2 (Spectrum 48)"},
-	{0x8b1c, 0xa8, 0x9262, "Ingrid's Back, pt. 3 (Spectrum 48)"},
-
-	{0xbeab, 0x2d, 0x94d9, "Scapeghost, pt. 1 (Amiga)"},
-	{0xc132, 0x14, 0x7adc, "Scapeghost, pt. 1 (Amiga *bak*)"},
-	{0xbe94, 0xcc, 0x04b8, "Scapeghost, pt. 1 (PC/ST)"},
-	{0x99bd, 0x65, 0x032e, "Scapeghost, pt. 2 (Amiga/PC/ST)"},
-	{0xbcb6, 0x7a, 0x7d4f, "Scapeghost, pt. 3 (Amiga/PC/ST)"},
-
-	{0x9058, 0xcf, 0x9748, "Scapeghost, pt. 1 (Commodore 64 Gfx)"},
-	{0x8f43, 0xc9, 0xeefd, "Scapeghost, pt. 2 (Commodore 64 Gfx)"},
-	{0x90ac, 0x68, 0xb4a8, "Scapeghost, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x8a21, 0xf4, 0xd9e4, "Scapeghost, pt. 1 (Spectrum 48)"},
-	{0x8a12, 0xe3, 0xc2ff, "Scapeghost, pt. 2 (Spectrum 48)"},
-	{0x8a16, 0xcc, 0x4f3b, "Scapeghost, pt. 3 (Spectrum 48)"},
-
-	{0x3ebb, 0x00, 0xf6dc, "Champion of the Raj (English) 1/2 GD (Amiga)"},
-	{0x0fd8, 0x00, 0xf250, "Champion of the Raj (English) 2/2 GD (Amiga)"},
-
-	{0x3e8f, 0x00, 0x5599, "Champion of the Raj (English) 1/2 GD (ST)"},
-
-	{0x3e4f, 0x00, 0xb202, "Champion of the Raj (English) 1/2 GD (PC)"},
-	{0x14a3, 0x00, 0xa288, "Champion of the Raj (English) 2/2 GD (PC)"},
-
-	{0x1929, 0x00, 0xd4b2, "Champion of the Raj (demo), 1/2 GD (ST)"},
-	{0x40e0, 0x02, 0x080d, "Champion of the Raj (demo), 2/2 GD (ST)"},
-
-	{0x4872, 0x00, 0x9515, "Champion of the Raj (German) 1/2 GD (Amiga)"},
-	{0x11f5, 0x00, 0xbf39, "Champion of the Raj (German) 2/2 GD (Amiga)"},
-
-	{0x4846, 0x00, 0xd9c1, "Champion of the Raj (German) 1/2 GD (ST)"},
-	{0x11f5, 0x00, 0x7aa4, "Champion of the Raj (German) 2/2 GD (ST)"},
-
-	{0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
-
-	{0x0000, 0x00, 0x0000, nullptr}
-};
-
-
-/*
- * The following patch database is obtained from L9cut's l9data_p.h, and
- * allows CRCs from patched games to be translated into original CRCs for
- * lookup in the game database above.  Some file commentary has been removed
- * for brevity, and unused patch fields deleted.
- *
- * The version of l9data_p.h used is 012 (22 May 2001).
- */
-struct gln_patch_table_t {
-	const gln_uint16 length;        /* Datafile length in bytes */
-	const gln_byte orig_checksum;   /* 8-bit checksum, last datafile byte */
-	const gln_uint16 orig_crc;      /* 16-bit CRC, L9cut-internal */
-	const gln_byte patch_checksum;  /* 8-bit checksum, last datafile byte */
-	const gln_uint16 patch_crc;     /* 16-bit CRC, L9cut-internal */
-};
-typedef const gln_patch_table_t *gln_patch_tableref_t;
-
-static const gln_patch_table_t GLN_PATCH_TABLE[] = {
-	/* Price of Magik (Spectrum128) */
-	{0x7410, 0x5e, 0x60be, 0x70, 0x6cef},
-
-	/* Price of Magik (Commodore 64) */
-	{0x6fc6, 0x14, 0xf9b6, 0x26, 0x3326},
-
-	/* Price of Magik (Spectrum48) */
-	{0x5aa4, 0xc1, 0xeda4, 0xd3, 0xed35},
-	{0x5aa4, 0xc1, 0xeda4, 0xc1, 0x8a65},
-
-	/* Colossal Adventure /JoD (Amiga/PC) */
-	{0x76f4, 0x5e, 0x1fe5, 0xea, 0x1305},
-	{0x76f4, 0x5e, 0x1fe5, 0xb5, 0x901f},
-	{0x76f4, 0x5e, 0x1fe5, 0x5e, 0x6ea1},
-
-	/* Colossal Adventure /JoD (ST) */
-	{0x76f4, 0x5a, 0xcf4b, 0xe6, 0x012a},
-	{0x76f4, 0x5a, 0xcf4b, 0xb1, 0x40b1},
-
-	/* Adventure Quest /JoD (Amiga/PC) */
-	{0x6e60, 0x83, 0x18e0, 0x4c, 0xcfb0},
-	{0x6e60, 0x83, 0x18e0, 0xfa, 0x9b3b},
-	{0x6e60, 0x83, 0x18e0, 0x83, 0x303d},
-
-	/* Adventure Quest /JoD (ST) */
-	{0x6e5c, 0xf6, 0xd356, 0xbf, 0xede7},
-	{0x6e5c, 0xf6, 0xd356, 0x6d, 0x662d},
-
-	/* Dungeon Adventure /JoD (Amiga/PC/ST) */
-	{0x6f0c, 0x95, 0x1f64, 0x6d, 0x2443},
-	{0x6f0c, 0x95, 0x1f64, 0x0c, 0x6066},
-	{0x6f0c, 0x95, 0x1f64, 0x96, 0xdaca},
-	{0x6f0c, 0x95, 0x1f64, 0x95, 0x848d},
-
-	/* Colossal Adventure /JoD (Spectrum128) */
-	{0x6f6e, 0x78, 0x28cd, 0xf8, 0xda5f},
-	{0x6f6e, 0x78, 0x28cd, 0x77, 0x5b4e},
-
-	/* Adventure Quest /JoD (Spectrum128) */
-	{0x6970, 0xd6, 0xa820, 0x3b, 0x1870},
-	{0x6970, 0xd6, 0xa820, 0xd5, 0x13c4},
-
-	/* Dungeon Adventure /JoD (Spectrum128) */
-	{0x6de8, 0x4c, 0xd795, 0xa2, 0x3eea},
-	{0x6de8, 0x4c, 0xd795, 0x4b, 0xad30},
-
-	/* Colossal Adventure /JoD (Amstrad CPC) */
-	{0x6f4d, 0xcb, 0xe8f2, 0x4b, 0xb384},
-	{0x6f4d, 0xcb, 0xe8f2, 0xca, 0x96e7},
-
-	/* Adventure Quest /JoD (Amstrad CPC) */
-	{0x6968, 0x32, 0x0c01, 0x97, 0xdded},
-	{0x6968, 0x32, 0x0c01, 0x31, 0xe8c2},
-
-	/* Dungeon Adventure /JoD (Amstrad CPC) */
-	{0x6dc0, 0x63, 0x5d95, 0xb9, 0xc963},
-	{0x6dc0, 0x63, 0x5d95, 0x62, 0x79f7},
-
-	/* Colossal Adventure /JoD (Commodore 64) */
-	{0x6c8e, 0xb6, 0x9be3, 0x36, 0x6971},
-	{0x6c8e, 0xb6, 0x9be3, 0xb5, 0xeba0},
-
-	/* Adventure Quest /JoD (Commodore 64) */
-	{0x63b6, 0x2e, 0xef38, 0x93, 0x4e68},
-	{0x63b6, 0x2e, 0xef38, 0x2d, 0x54dc},
-
-	/* Dungeon Adventure /JoD (Commodore 64) */
-	{0x6bd2, 0x65, 0xa41f, 0xbb, 0x4260},
-	{0x6bd2, 0x65, 0xa41f, 0x64, 0xdf5a},
-
-	/* Colossal Adventure /JoD (Spectrum48) */
-	{0x5a8e, 0xf2, 0x7cca, 0x72, 0x8e58},
-	{0x5a8e, 0xf2, 0x7cca, 0xf1, 0x0c89},
-	{0x5a8e, 0xf2, 0x7cca, 0xf2, 0x2c96},
-
-	/* Adventure Quest /JoD (Spectrum48) */
-	{0x5ace, 0x11, 0xdc12, 0x76, 0x8663},
-	{0x5ace, 0x11, 0xdc12, 0x10, 0xa757},
-	{0x5ace, 0x11, 0xdc12, 0x11, 0xf118},
-
-	/* Dungeon Adventure /JoD (Spectrum48) */
-	{0x58a3, 0x38, 0x8ce4, 0x8e, 0xb61a},
-	{0x58a3, 0x38, 0x8ce4, 0x37, 0x34c0},
-	{0x58a3, 0x38, 0x8ce4, 0x38, 0xa1ee},
-
-	/* Snowball /SD (Amiga/ST) */
-	{0x7b31, 0x6e, 0x2e2b, 0xe5, 0x6017},
-
-	/* Return to Eden /SD (Amiga/ST) */
-	{0x7d16, 0xe6, 0x5438, 0x5d, 0xc770},
-
-	/* Worm in Paradise /SD (Amiga/ST) */
-	{0x7cd9, 0x0c, 0x4df1, 0x83, 0xe997},
-
-	/* Snowball /SD (PC/Spectrum128) */
-	{0x7b2f, 0x70, 0x6955, 0xe7, 0x0af4},
-	{0x7b2f, 0x70, 0x6955, 0x70, 0x1179},
-
-	/* Return to Eden /SD (PC) */
-	{0x7d14, 0xe8, 0xfbab, 0x5f, 0xeab9},
-	{0x7d14, 0xe8, 0xfbab, 0xe8, 0xe216},
-
-	/* Return to Eden /SD (Amstrad CPC) */
-	{0x7cff, 0xf8, 0x6044, 0x6f, 0xbb57},
-
-	/* Return to Eden /SD (Spectrum128) */
-	{0x7c55, 0x18, 0xdaee, 0x8f, 0x01fd},
-
-	/* Worm in Paradise /SD (Amstrad CPC/PC/Spectrum128) */
-	{0x7cd7, 0x0e, 0x4feb, 0x85, 0x4eae},
-	{0x7cd7, 0x0e, 0x4feb, 0x0e, 0xb02c},
-
-	/* Snowball /SD (Commodore 64) */
-	{0x7363, 0x65, 0xa0ab, 0xdc, 0xca6a},
-
-	/* Return to Eden /SD (Commodore 64) */
-	{0x772f, 0xca, 0x8602, 0x41, 0x9bd0},
-
-	/* Worm in Paradise /SD (Commodore 64) */
-	{0x788d, 0x72, 0x888a, 0xe9, 0x4cce},
-
-	/* Snowball /SD (Atari) */
-	{0x6bf8, 0x3f, 0xc9f7, 0x96, 0x1908},
-
-	/* Return to Eden /SD (Atari) */
-	{0x60f7, 0x68, 0xc2bc, 0xdf, 0xd3ae},
-
-	/* Worm in Paradise /SD (Atari) */
-	{0x6161, 0xf3, 0xe6d7, 0x6a, 0xe232},
-
-	/* Snowball /SD (Spectrum48) */
-	{0x6541, 0x02, 0x2e6c, 0x79, 0xb80c},
-	{0x6541, 0x02, 0x2e6c, 0x02, 0x028a},
-
-	/* Return to Eden /SD (Spectrum48) */
-	{0x5f43, 0xca, 0x828c, 0x41, 0x9f5e},
-	{0x5f43, 0xca, 0x828c, 0xca, 0x6e1b},
-
-	/* Worm in Paradise /SD (Spectrum48) */
-	{0x5ebb, 0xf1, 0x4dec, 0x68, 0x4909},
-	{0x5ebb, 0xf1, 0x4dec, 0xf1, 0xcc1a},
-
-	/* Knight Orc, pt. 1 (Amiga) */
-	{0xbb93, 0x36, 0x6a05, 0xad, 0xe52d},
-
-	/* Knight Orc, pt. 1 (ST) */
-	{0xbb6e, 0xad, 0x4d40, 0x24, 0x3bcd},
-
-	/* Knight Orc, pt. 2 (Amiga/ST) */
-	{0xc58e, 0x4a, 0x4e9d, 0xc1, 0xe2bf},
-
-	/* Knight Orc, pt. 3 (Amiga/ST) */
-	{0xcb9a, 0x0f, 0x0804, 0x86, 0x6487},
-
-	/* Knight Orc, pt. 1 (PC) */
-	{0xbb6e, 0xa6, 0x9753, 0x1d, 0x2e7f},
-	{0xbb6e, 0xa6, 0x9753, 0xa6, 0x001d},
-
-	/* Knight Orc, pt. 2 (PC) */
-	{0xc58e, 0x43, 0xe9ce, 0xba, 0x5e4c},
-	{0xc58e, 0x43, 0xe9ce, 0x43, 0xa8f0},
-
-	/* Knight Orc, pt. 3 (PC) */
-	{0xcb9a, 0x08, 0x6c36, 0x7f, 0xf0d4},
-	{0xcb9a, 0x08, 0x6c36, 0x08, 0x2d08},
-
-	/* Knight Orc, pt. 1 (Commodore 64 Gfx) */
-	{0x8970, 0x6b, 0x3c7b, 0xe2, 0xb6f3},
-
-	/* Knight Orc, pt. 1 (Spectrum48) */
-	{0x86d0, 0xb7, 0xadbd, 0x2e, 0x43e1},
-
-	/* Gnome Ranger, pt. 1 (Amiga/ST) */
-	{0xb1a9, 0x80, 0x5fb7, 0xf7, 0x5c6c},
-
-	/* Gnome Ranger, pt. 2 (Amiga/ST) */
-	{0xab9d, 0x31, 0xbe6d, 0xa8, 0xcb96},
-
-	/* Gnome Ranger, pt. 3 (Amiga/ST) */
-	{0xae28, 0x87, 0xb6b6, 0xfe, 0x760c},
-
-	/* Gnome Ranger, pt. 1 (PC) */
-	{0xb1aa, 0xad, 0xaf47, 0x24, 0x5cfd},
-	{0xb1aa, 0xad, 0xaf47, 0xad, 0xe0ed},
-
-	/* Gnome Ranger, pt. 1 (ST-var) */
-	{0xb19e, 0x92, 0x8f96, 0x09, 0x798c},
-
-	/* Gnome Ranger, pt. 2 (PC/ST-var) */
-	{0xab8b, 0xbf, 0x31e6, 0x36, 0x811c},
-	{0xab8b, 0xbf, 0x31e6, 0xbf, 0x8ff3},
-
-	/* Gnome Ranger, pt. 3 (PC/ST-var) */
-	{0xae16, 0x81, 0x8741, 0xf8, 0x47fb},
-	{0xae16, 0x81, 0x8741, 0x81, 0xc8eb},
-
-	/* Gnome Ranger, pt. 1 (Commodore 64 TO) */
-	{0xad41, 0xa8, 0x42c5, 0x1f, 0x7d1e},
-
-	/* Gnome Ranger, pt. 2 (Commodore 64 TO) */
-	{0xa735, 0xf7, 0x2e08, 0x6e, 0x780e},
-
-	/* Gnome Ranger, pt. 3 (Commodore 64 TO) */
-	{0xa9c0, 0x9e, 0x0d70, 0x15, 0x3e6b},
-
-	/* Gnome Ranger, pt. 1 (Commodore 64 Gfx) */
-	{0x908e, 0x0d, 0x58a7, 0x84, 0xab1d},
-
-	/* Gnome Ranger, pt. 2 (Commodore 64 Gfx) */
-	{0x8f6f, 0x0a, 0x411a, 0x81, 0x12bc},
-
-	/* Gnome Ranger, pt. 3 (Commodore 64 Gfx) */
-	{0x9060, 0xbb, 0xe75d, 0x32, 0x14e7},
-
-	/* Lords of Time /T&M (PC) */
-	{0xb57c, 0x44, 0x7779, 0xbb, 0x31a6},
-	{0xb57c, 0x44, 0x7779, 0x44, 0xea72},
-
-	/* Red Moon /T&M (PC) */
-	{0xa69e, 0x6c, 0xb268, 0xe3, 0x4cef},
-	{0xa69e, 0x6c, 0xb268, 0x6c, 0x3799},
-
-	/* Price of Magik /T&M (PC) */
-	{0xbac7, 0x7f, 0xddb2, 0xf6, 0x6ab3},
-	{0xbac7, 0x7f, 0xddb2, 0x7f, 0x905c},
-
-	/* Lords of Time /T&M (ST) */
-	{0xb579, 0x89, 0x3e89, 0x00, 0xa2b7},
-
-	/* Red Moon /T&M (ST) */
-	{0xa698, 0x41, 0xcaca, 0xb8, 0xeeac},
-
-	/* Price of Magik /T&M (ST) */
-	{0xbac4, 0x80, 0xa750, 0xf7, 0xe030},
-
-	/* Lords of Time /T&M (Amiga) */
-	{0xb576, 0x2a, 0x7239, 0xa1, 0x2ea6},
-
-	/* Red Moon /T&M (Amiga) */
-	{0xa692, 0xd1, 0x6a99, 0x48, 0x50ff},
-
-	/* Price of Magik /T&M (Amiga) */
-	{0xbaca, 0x3a, 0x221b, 0xb1, 0x55bb},
-
-	/* Lords of Time /T&M (Commodore 64 TO) */
-	{0xb38c, 0x37, 0x9f8e, 0xae, 0xc6b1},
-
-	/* Red Moon /T&M (Commodore 64 TO) */
-	{0xa4e2, 0xa6, 0x016d, 0x1d, 0x31ab},
-
-	/* Price of Magik /T&M (Commodore 64 TO) */
-	{0xb451, 0xa8, 0x2682, 0x1f, 0x5de2},
-
-	/* Lords of Time /T&M (Commodore 64 Gfx) */
-	{0x9070, 0x43, 0x45d4, 0xba, 0x02eb},
-
-	/* Red Moon /T&M (Commodore 64 Gfx) */
-	{0x903f, 0x6b, 0x603e, 0xe2, 0x9f59},
-
-	/* Price of Magik /T&M (Commodore 64 Gfx) */
-	{0x8f51, 0xb2, 0x6c9a, 0x29, 0xde3b},
-
-	/* Lords of Time /T&M (Spectrum48) */
-	{0x8950, 0xa1, 0xbb16, 0x18, 0x2828},
-	{0x8950, 0xa1, 0xbb16, 0xa1, 0x1ea2},
-
-	/* Red Moon /T&M (Spectrum48) */
-	{0x8813, 0x11, 0x22de, 0x88, 0x18b8},
-	{0x8813, 0x11, 0x22de, 0x11, 0xd0cd},
-
-	/* Price of Magik /T&M (Spectrum48) */
-	{0x8a60, 0x2a, 0x29ed, 0xa1, 0x5e4d},
-
-	/* Lancelot, pt. 1 (Amiga/PC/ST) */
-	{0xc0cf, 0x4e, 0xb7fa, 0xc5, 0x4400},
-
-	/* Lancelot, pt. 2 (Amiga/PC/ST) */
-	{0xd5e9, 0x6a, 0x4192, 0xe1, 0x3b1e},
-
-	/* Lancelot, pt. 3 (Amiga/PC/ST) */
-	{0xbb8f, 0x1a, 0x7487, 0x91, 0x877d},
-
-	/* Lancelot, pt. 1 (Commodore 64 TO) */
-	{0xb4c9, 0x94, 0xd784, 0x0b, 0x203e},
-
-	/* Lancelot, pt. 2 (Commodore 64 TO) */
-	{0xb729, 0x51, 0x8ee5, 0xc8, 0xf1c9},
-
-	/* Lancelot, pt. 3 (Commodore 64 TO) */
-	{0xb702, 0xe4, 0x1809, 0x5b, 0x25b2},
-
-	/* Lancelot, pt. 1 (Commodore 64 Gfx) */
-	{0x8feb, 0xba, 0xa800, 0x31, 0x5bfa},
-
-	/* Lancelot, pt. 2 (Commodore 64 Gfx) */
-	{0x8f6b, 0xfa, 0x0f7e, 0x71, 0x75f2},
-
-	/* Lancelot, pt. 3 (Commodore 64 Gfx) */
-	{0x8f71, 0x2f, 0x0ddc, 0xa6, 0x3e87},
-
-	/* Ingrid's Back, pt. 1 (PC) */
-	{0xd19b, 0xad, 0x306d, 0x24, 0x4504},
-	{0xd19b, 0xad, 0x306d, 0xad, 0x878e},
-
-	/* Ingrid's Back, pt. 2 (PC) */
-	{0xc5a5, 0xfe, 0x3c98, 0x75, 0x8950},
-	{0xc5a5, 0xfe, 0x3c98, 0xfe, 0x8b7b},
-
-	/* Ingrid's Back, pt. 3 (PC) */
-	{0xd7ae, 0x9e, 0x1878, 0x15, 0xadb0},
-	{0xd7ae, 0x9e, 0x1878, 0x9e, 0xaf9b},
-
-	/* Ingrid's Back, pt. 1 (Amiga) */
-	{0xd188, 0x13, 0xdc60, 0x8a, 0x755c},
-
-	/* Ingrid's Back, pt. 2 (Amiga) */
-	{0xc594, 0x03, 0xea95, 0x7a, 0xb5a8},
-
-	/* Ingrid's Back, pt. 3 (Amiga) */
-	{0xd79f, 0xb5, 0x1661, 0x2c, 0xbf5d},
-
-	/* Ingrid's Back, pt. 1 (ST) */
-	{0xd183, 0x83, 0xef72, 0xfa, 0xb04f},
-
-	/* Ingrid's Back, pt. 2 (ST) */
-	{0xc58f, 0x65, 0xf337, 0xdc, 0x900a},
-
-	/* Ingrid's Back, pt. 3 (ST) */
-	{0xd79a, 0x57, 0x49c5, 0xce, 0xe0f9},
-
-	/* Ingrid's Back, pt. 1 (Commodore 64 TO) */
-	{0xb770, 0x03, 0x9a03, 0x7a, 0xdc6a},
-
-	/* Ingrid's Back, pt. 2 (Commodore 64 TO) */
-	{0xb741, 0xb6, 0x2aa5, 0x2d, 0x5a6c},
-
-	/* Ingrid's Back, pt. 3 (Commodore 64 TO) */
-	{0xb791, 0xa1, 0xd065, 0x18, 0xaa0c},
-
-	/* Ingrid's Back, pt. 1 (Commodore 64 Gfx) */
-	{0x9089, 0xce, 0xc5e2, 0x44, 0xeff4},
-
-	/* Ingrid's Back, pt. 2 (Commodore 64 Gfx) */
-	{0x908d, 0x80, 0x30c7, 0xf6, 0x2a11},
-
-	/* Ingrid's Back, pt. 3 (Commodore 64 Gfx) */
-	{0x909e, 0x9f, 0xdecc, 0x15, 0xf4da},
-
-	{0x0000, 0x00, 0x0000, 0x00, 0x0000},
-};
-
-
-/*
- * gln_gameid_lookup_game()
- * gln_gameid_lookup_patch()
- *
- * Look up and return game table and patch table entries given a game's
- * length, checksum, and CRC.  Returns the entry, or nullptr if not found.
- */
-static gln_game_tableref_t gln_gameid_lookup_game(gln_uint16 length, gln_byte checksum,
-		gln_uint16 crc, int ignore_crc) {
-	gln_game_tableref_t game;
-
-	for (game = GLN_GAME_TABLE; game->length; game++) {
-		if (game->length == length && game->checksum == checksum
-				&& (ignore_crc || game->crc == crc))
-			break;
-	}
-
-	return game->length ? game : nullptr;
-}
-
-static gln_patch_tableref_t gln_gameid_lookup_patch(gln_uint16 length, gln_byte checksum,
-		gln_uint16 crc) {
-	gln_patch_tableref_t patch;
-
-	for (patch = GLN_PATCH_TABLE; patch->length; patch++) {
-		if (patch->length == length && patch->patch_checksum == checksum
-				&& patch->patch_crc == crc)
-			break;
-	}
-
-	return patch->length ? patch : nullptr;
-}
-
-
-/*
- * gln_gameid_identify_game()
- *
- * Identify a game from its data length, checksum, and CRC.  Returns the
- * entry of the game in the game table, or nullptr if not found.
- *
- * This function uses startdata and FileSize from the core interpreter.
- * These aren't advertised symbols, so be warned.
- */
-static gln_game_tableref_t gln_gameid_identify_game() {
-	gln_uint16 length, crc;
-	gln_byte checksum;
-	int is_version2;
-	gln_game_tableref_t game;
-	gln_patch_tableref_t patch;
-
-	/* If the data file appears too short for a header, give up now. */
-	if (FileSize < 30)
-		return nullptr;
-
-	/*
-	 * Find the version of the game, and the length of game data.  This logic
-	 * is taken from L9cut, with calcword() replaced by simple byte comparisons.
-	 * If the length exceeds the available data, fail.
-	 */
-	assert(startdata);
-	is_version2 = startdata[4] == 0x20 && startdata[5] == 0x00
-	              && startdata[10] == 0x00 && startdata[11] == 0x80
-	              && startdata[20] == startdata[22]
-	              && startdata[21] == startdata[23];
-
-	length = is_version2
-	         ? startdata[28] | startdata[29] << BITS_PER_CHAR
-	         : startdata[0] | startdata[1] << BITS_PER_CHAR;
-	if (length >= FileSize)
-		return nullptr;
-
-	/* Calculate or retrieve the checksum, in a version specific way. */
-	if (is_version2) {
-		int index;
-
-		checksum = 0;
-		for (index = 0; index < length + 1; index++)
-			checksum += startdata[index];
-	} else
-		checksum = startdata[length];
-
-	/*
-	 * Generate a CRC for this data.  When L9cut calculates a CRC, it's using a
-	 * copy taken up to length + 1 and then padded with two NUL bytes, so we
-	 * mimic that here.
-	 */
-	crc = gln_get_buffer_crc(startdata, length + 1, 2);
-
-	/*
-	 * See if this is a patched file.  If it is, look up the game based on the
-	 * original CRC and checksum.  If not, use the current CRC and checksum.
-	 */
-	patch = gln_gameid_lookup_patch(length, checksum, crc);
-	game = gln_gameid_lookup_game(length,
-	                              patch ? patch->orig_checksum : checksum,
-	                              patch ? patch->orig_crc : crc,
-	                              FALSE);
-
-	/* If no game identified, retry without the CRC.  This is guesswork. */
-	if (!game)
-		game = gln_gameid_lookup_game(length, checksum, crc, TRUE);
-
-	return game;
-}
-
-
-/*
- * gln_gameid_get_game_name()
- *
- * Return the name of the game, or nullptr if not identifiable.
- *
- * This function uses startdata from the core interpreter.  This isn't an
- * advertised symbol, so be warned.
- */
-static const char *gln_gameid_get_game_name() {
-	/*
-	 * If no game name yet known, attempt to identify the game.  If it can't
-	 * be identified, set the cached game name to "" -- this special value
-	 * indicates that the game is an unknown one, but suppresses repeated
-	 * attempts to identify it on successive calls.
-	 */
-	if (!gln_gameid_game_name) {
-		gln_game_tableref_t game;
-
-		/*
-		 * If the interpreter hasn't yet loaded a game, startdata is nullptr
-		 * (uninitialized, global).  In this case, we return nullptr, allowing
-		 * for retries until a game is loaded.
-		 */
-		if (!startdata)
-			return nullptr;
-
-		game = gln_gameid_identify_game();
-		gln_gameid_game_name = game ? game->name : "";
-	}
-
-	/* Return the game's name, or nullptr if it was unidentifiable. */
-	assert(gln_gameid_game_name);
-	return strlen(gln_gameid_game_name) > 0 ? gln_gameid_game_name : nullptr;
-}
-
-
-/*
- * gln_gameid_game_name_reset()
- *
- * Clear the saved game name, forcing a new lookup when next queried.  This
- * function should be called by actions that may cause the interpreter to
- * change game file, for example os_set_filenumber().
- */
-static void gln_gameid_game_name_reset() {
-	gln_gameid_game_name = nullptr;
-}
-
-
-/*---------------------------------------------------------------------*/
 /*  Glk port bitmap picture functions                                  */
 /*---------------------------------------------------------------------*/
 
@@ -3307,7 +2288,7 @@ static void gln_status_update() {
 		 * Try to establish a game identity to display; if none, use a standard
 		 * message instead.
 		 */
-		game_name = gln_gameid_get_game_name();
+		game_name = g_vm->_detection._gameName;
 		g_vm->glk_put_string(game_name ? game_name : "ScummVM GLK Level 9 Game");
 
 		g_vm->glk_set_window(gln_main_window);
@@ -3333,7 +2314,7 @@ static void gln_status_print() {
 	const char *game_name;
 
 	/* Get the current game name, and do nothing if none available. */
-	game_name = gln_gameid_get_game_name();
+	game_name = g_vm->_detection._gameName;
 	if (game_name) {
 		gln_uint16 new_crc;
 
@@ -3341,7 +2322,7 @@ static void gln_status_print() {
 		 * If not the first call and the game identity string has not changed,
 		 * again, do nothing.
 		 */
-		new_crc = gln_get_buffer_crc(game_name, strlen(game_name), 0);
+		new_crc = g_vm->_detection.gln_get_buffer_crc(game_name, strlen(game_name));
 		if (!is_initialized || new_crc != crc) {
 			int index;
 
@@ -5445,7 +4426,7 @@ gln_bool os_get_game_file(char *newname, int size) {
 	}
 
 	/* Encourage game name re-lookup, and return success. */
-	gln_gameid_game_name_reset();
+	g_vm->_detection.gln_gameid_game_name_reset();
 	gln_watchdog_tick();
 	return TRUE;
 }
@@ -5502,7 +4483,7 @@ void os_set_filenumber(char *newname, int size, int file_number) {
 	gln_standout_string("\n\n");
 
 	/* Encourage game name re-lookup, and return. */
-	gln_gameid_game_name_reset();
+	g_vm->_detection.gln_gameid_game_name_reset();
 	gln_watchdog_tick();
 }
 
@@ -5788,7 +4769,7 @@ void gln_main(const char *filename) {
 		 * of the game.  So we have to encourage a re-lookup of the game name
 		 * at this point.
 		 */
-		gln_gameid_game_name_reset();
+		g_vm->_detection.gln_gameid_game_name_reset();
 
 		/* Load the game, sending in any established graphics file. */
 		int errNum = 0;
diff --git a/engines/glk/level9/os_glk.h b/engines/glk/level9/os_glk.h
index a10a640..d617fb3 100644
--- a/engines/glk/level9/os_glk.h
+++ b/engines/glk/level9/os_glk.h
@@ -26,6 +26,9 @@
 namespace Glk {
 namespace Level9 {
 
+#define BYTE_MAX 0xff
+#define BITS_PER_CHAR 8
+
 extern bool gln_graphics_enabled;
 extern bool gln_graphics_possible;
 


Commit: 51471940a9043cfd9a0b1c0846490f556de283c0
    https://github.com/scummvm/scummvm/commit/51471940a9043cfd9a0b1c0846490f556de283c0
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:30-07:00

Commit Message:
GLK: LEVEL9: Move game scanner code to new Scanner class

Changed paths:
    engines/glk/level9/detection.cpp
    engines/glk/level9/detection.h
    engines/glk/level9/detection_tables.h
    engines/glk/level9/level9_main.cpp


diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
index 2648db8..3b3228e 100644
--- a/engines/glk/level9/detection.cpp
+++ b/engines/glk/level9/detection.cpp
@@ -22,6 +22,7 @@
 
 #include "glk/level9/detection.h"
 #include "glk/level9/detection_tables.h"
+#include "glk/level9/level9_main.h"
 #include "glk/level9/os_glk.h"
 #include "glk/blorb.h"
 #include "common/debug.h"
@@ -32,6 +33,486 @@
 namespace Glk {
 namespace Level9 {
 
+long Scanner::scanner(byte *startFile, uint32 size, byte **dictData, byte **aCodePtr) {
+	_dictData = dictData;
+	_aCodePtr = aCodePtr;
+
+#ifdef FULLSCAN
+	FullScan(startfile, FileSize);
+#endif
+
+	int offset = scan(startFile, size);
+	if (offset < 0) {
+		offset = ScanV2(startFile, size);
+		_gameType = L9_V2;
+		if (offset < 0) {
+			offset = ScanV1(startFile, size);
+			_gameType = L9_V1;
+			if (offset < 0) {
+				return -1;
+			}
+		}
+	}
+
+	return offset;
+}
+
+const L9V1GameInfo &Scanner::v1Game() const {
+	assert(_gameType == L9_V1);
+	return L9_V1_GAMES[_l9V1Game];
+}
+
+long Scanner::scan(byte *StartFile, uint32 size) {
+	byte *Chk = (byte *)malloc(size + 1);
+	byte *Image = (byte *)calloc(size, 1);
+	uint32 i, num, Size, MaxSize = 0;
+	int j;
+	uint16 d0 = 0, l9, md, ml, dd, dl;
+	uint32 Min, Max;
+	long Offset = -1;
+	bool JumpKill, DriverV4;
+
+	if ((Chk == nullptr) || (Image == nullptr)) {
+		error("Unable to allocate memory for game scan! Exiting...");
+	}
+
+	Chk[0] = 0;
+	for (i = 1; i <= size; i++)
+		Chk[i] = Chk[i - 1] + StartFile[i - 1];
+
+	for (i = 0; i < size - 33; i++) {
+		num = L9WORD(StartFile + i) + 1;
+		/*
+		        Chk[i] = 0 +...+ i-1
+		        Chk[i+n] = 0 +...+ i+n-1
+		        Chk[i+n] - Chk[i] = i + ... + i+n
+		*/
+		if (num > 0x2000 && i + num <= size && Chk[i + num] == Chk[i]) {
+			md = L9WORD(StartFile + i + 0x2);
+			ml = L9WORD(StartFile + i + 0x4);
+			dd = L9WORD(StartFile + i + 0xa);
+			dl = L9WORD(StartFile + i + 0xc);
+
+			if (ml > 0 && md > 0 && i + md + ml <= size && dd > 0 && dl > 0 && i + dd + dl * 4 <= size) {
+				/* v4 files may have acodeptr in 8000-9000, need to fix */
+				for (j = 0; j < 12; j++) {
+					d0 = L9WORD(StartFile + i + 0x12 + j * 2);
+					if (j != 11 && d0 >= 0x8000 && d0 < 0x9000) {
+						if (d0 >= 0x8000 + LISTAREASIZE) break;
+					} else if (i + d0 > size) break;
+				}
+				/* list9 ptr must be in listarea, acode ptr in data */
+				if (j < 12 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
+
+				l9 = L9WORD(StartFile + i + 0x12 + 10 * 2);
+				if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
+
+				Size = 0;
+				Min = Max = i + d0;
+				DriverV4 = 0;
+				if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, &DriverV4)) {
+					if (Size > MaxSize && Size > 100) {
+						Offset = i;
+						MaxSize = Size;
+						_gameType = DriverV4 ? L9_V4 : L9_V3;
+					}
+				}
+			}
+		}
+	}
+
+	free(Chk);
+	free(Image);
+	return Offset;
+}
+
+long Scanner::ScanV2(byte *StartFile, uint32 size) {
+	byte *Chk = (byte *)malloc(size + 1);
+	byte *Image = (byte *)calloc(size, 1);
+	uint32 i, Size, MaxSize = 0, num;
+	int j;
+	uint16 d0 = 0, l9;
+	uint32 Min, Max;
+	long Offset = -1;
+	bool JumpKill;
+
+	if ((Chk == nullptr) || (Image == nullptr)) {
+		error("Unable to allocate memory for game scan! Exiting...");
+	}
+
+	Chk[0] = 0;
+	for (i = 1; i <= size; i++)
+		Chk[i] = Chk[i - 1] + StartFile[i - 1];
+
+	for (i = 0; i < size - 28; i++) {
+		num = L9WORD(StartFile + i + 28) + 1;
+		if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == StartFile[i + 0x1e]) {
+			for (j = 0; j < 14; j++) {
+				d0 = L9WORD(StartFile + i + j * 2);
+				if (j != 13 && d0 >= 0x8000 && d0 < 0x9000) {
+					if (d0 >= 0x8000 + LISTAREASIZE) break;
+				} else if (i + d0 > size) break;
+			}
+			/* list9 ptr must be in listarea, acode ptr in data */
+			if (j < 14 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
+
+			l9 = L9WORD(StartFile + i + 6 + 9 * 2);
+			if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
+
+			Size = 0;
+			Min = Max = i + d0;
+			if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
+#ifdef L9DEBUG
+				printf("Found valid V2 header at %ld, code size %ld", i, Size);
+#endif
+				if (Size > MaxSize && Size > 100) {
+					Offset = i;
+					MaxSize = Size;
+				}
+			}
+		}
+	}
+	free(Chk);
+	free(Image);
+	return Offset;
+}
+
+long Scanner::ScanV1(byte *StartFile, uint32 size) {
+	byte *Image = (byte *)calloc(size, 1);
+	uint32 i, Size;
+	int Replace;
+	byte *ImagePtr;
+	long MaxPos = -1;
+	uint32 MaxCount = 0;
+	uint32 Min, Max; //, MaxMax, MaxMin;
+	bool JumpKill; // , MaxJK;
+
+	int dictOff1 = 0, dictOff2 = 0;
+	byte dictVal1 = 0xff, dictVal2 = 0xff;
+
+	if (Image == nullptr) {
+		error("Unable to allocate memory for game scan! Exiting...");
+	}
+
+	for (i = 0; i < size; i++) {
+		if ((StartFile[i] == 0 && StartFile[i + 1] == 6) || (StartFile[i] == 32 && StartFile[i + 1] == 4)) {
+			Size = 0;
+			Min = Max = i;
+			Replace = 0;
+			if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
+				if (Size > MaxCount && Size > 100 && Size < 10000) {
+					MaxCount = Size;
+					//MaxMin = Min;
+					//MaxMax = Max;
+
+					MaxPos = i;
+					//MaxJK = JumpKill;
+				}
+				Replace = 0;
+			}
+			for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
+				if (*ImagePtr == 2)
+					*ImagePtr = Replace;
+			}
+		}
+	}
+
+	/* V1 dictionary detection from L9Cut by Paul David Doherty */
+	for (i = 0; i < size - 20; i++) {
+		if (StartFile[i] == 'A') {
+			if (StartFile[i + 1] == 'T' && StartFile[i + 2] == 'T' && StartFile[i + 3] == 'A' && StartFile[i + 4] == 'C' && StartFile[i + 5] == 0xcb) {
+				dictOff1 = i;
+				dictVal1 = StartFile[dictOff1 + 6];
+				break;
+			}
+		}
+	}
+	for (i = dictOff1; i < size - 20; i++) {
+		if (StartFile[i] == 'B') {
+			if (StartFile[i + 1] == 'U' && StartFile[i + 2] == 'N' && StartFile[i + 3] == 'C' && StartFile[i + 4] == 0xc8) {
+				dictOff2 = i;
+				dictVal2 = StartFile[dictOff2 + 5];
+				break;
+			}
+		}
+	}
+	_l9V1Game = -1;
+	if (_dictData && (dictVal1 != 0xff || dictVal2 != 0xff)) {
+		for (i = 0; i < sizeof L9_V1_GAMES / sizeof L9_V1_GAMES[0]; i++) {
+			if ((L9_V1_GAMES[i].dictVal1 == dictVal1) && (L9_V1_GAMES[i].dictVal2 == dictVal2)) {
+				_l9V1Game = i;
+				(*_dictData) = StartFile + dictOff1 - L9_V1_GAMES[i].dictStart;
+			}
+		}
+	}
+
+	free(Image);
+
+	if (MaxPos > 0 && _aCodePtr) {
+		(*_aCodePtr) = StartFile + MaxPos;
+		return 0;
+	}
+
+	return -1;
+}
+
+bool Scanner::ValidateSequence(byte *Base, byte *Image, uint32 iPos, uint32 acode, uint32 *Size, uint32 size, uint32 *Min, uint32 *Max, bool Rts, bool *JumpKill, bool *DriverV4) {
+	uint32 Pos;
+	bool Finished = false, Valid;
+	uint32 Strange = 0;
+	int ScanCodeMask;
+	int Code;
+	*JumpKill = false;
+
+	if (iPos >= size)
+		return false;
+	Pos = iPos;
+	if (Pos < *Min) *Min = Pos;
+
+	if (Image[Pos]) return true; /* hit valid code */
+
+	do {
+		Code = Base[Pos];
+		Valid = true;
+		if (Image[Pos]) break; /* converged to found code */
+		Image[Pos++] = 2;
+		if (Pos > *Max) *Max = Pos;
+
+		ScanCodeMask = 0x9f;
+		if (Code & 0x80) {
+			ScanCodeMask = 0xff;
+			if ((Code & 0x1f) > 0xa)
+				Valid = false;
+			Pos += 2;
+		}
+		else switch (Code & 0x1f) {
+		case 0: { /* goto */
+			uint32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+			Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, true/*Rts*/, JumpKill, DriverV4);
+			Finished = true;
+			break;
+		}
+		case 1: { /* intgosub */
+			uint32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+			Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, true, JumpKill, DriverV4);
+			break;
+		}
+		case 2: /* intreturn */
+			Valid = Rts;
+			Finished = true;
+			break;
+		case 3: /* printnumber */
+			Pos++;
+			break;
+		case 4: /* messagev */
+			Pos++;
+			break;
+		case 5: /* messagec */
+			scangetcon(Code, &Pos, &ScanCodeMask);
+			break;
+		case 6: /* function */
+			switch (Base[Pos++]) {
+			case 2:/* random */
+				Pos++;
+				break;
+			case 1:/* calldriver */
+				if (DriverV4) {
+					if (CheckCallDriverV4(Base, Pos - 2))
+						*DriverV4 = true;
+				}
+				break;
+			case 3:/* save */
+			case 4:/* restore */
+			case 5:/* clearworkspace */
+			case 6:/* clear stack */
+				break;
+			case 250: /* printstr */
+				while (Base[Pos++]);
+				break;
+
+			default:
+				Valid = false;
+				break;
+			}
+			break;
+		case 7: /* input */
+			Pos += 4;
+			break;
+		case 8: /* varcon */
+			scangetcon(Code, &Pos, &ScanCodeMask);
+			Pos++;
+			break;
+		case 9: /* varvar */
+			Pos += 2;
+			break;
+		case 10: /* _add */
+			Pos += 2;
+			break;
+		case 11: /* _sub */
+			Pos += 2;
+			break;
+		case 14: /* jump */
+			*JumpKill = true;
+			Finished = true;
+			break;
+		case 15: /* exit */
+			Pos += 4;
+			break;
+		case 16: /* ifeqvt */
+		case 17: /* ifnevt */
+		case 18: /* ifltvt */
+		case 19: { /* ifgtvt */
+			uint32 Val;
+			Pos += 2;
+			Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+			Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
+			break;
+		}
+		case 20: /* screen */
+			if (Base[Pos++]) Pos++;
+			break;
+		case 21: /* cleartg */
+			Pos++;
+			break;
+		case 22: /* picture */
+			Pos++;
+			break;
+		case 23: /* getnextobject */
+			Pos += 6;
+			break;
+		case 24: /* ifeqct */
+		case 25: /* ifnect */
+		case 26: /* ifltct */
+		case 27: { /* ifgtct */
+			uint32 Val;
+			Pos++;
+			scangetcon(Code, &Pos, &ScanCodeMask);
+			Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
+			Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
+			break;
+		}
+		case 28: /* printinput */
+			break;
+		case 12: /* ilins */
+		case 13: /* ilins */
+		case 29: /* ilins */
+		case 30: /* ilins */
+		case 31: /* ilins */
+			Valid = false;
+			break;
+		}
+		if (Valid && (Code & ~ScanCodeMask))
+			Strange++;
+	} while (Valid && !Finished && Pos < size); /* && Strange==0); */
+	(*Size) += Pos - iPos;
+	return Valid; /* && Strange==0; */
+}
+
+uint16 Scanner::scanmovewa5d0(byte *Base, uint32 *Pos) {
+	uint16 ret = L9WORD(Base + *Pos);
+	(*Pos) += 2;
+	return ret;
+}
+
+uint32 Scanner::scangetaddr(int Code, byte *Base, uint32 *Pos, uint32 acode, int *Mask) {
+	(*Mask) |= 0x20;
+	if (Code & 0x20) {
+		/* getaddrshort */
+		signed char diff = Base[*Pos];
+		(*Pos)++;
+		return (*Pos) + diff - 1;
+	} else {
+		return acode + scanmovewa5d0(Base, Pos);
+	}
+}
+
+void Scanner::scangetcon(int Code, uint32 *Pos, int *Mask) {
+	(*Pos)++;
+	if (!(Code & 64))(*Pos)++;
+	(*Mask) |= 0x40;
+}
+
+bool Scanner::CheckCallDriverV4(byte *Base, uint32 Pos) {
+	int i, j;
+
+	// Look back for an assignment from a variable to list9[0], which is used
+	// to specify the driver call.
+	for (i = 0; i < 2; i++) {
+		int x = Pos - ((i + 1) * 3);
+		if ((Base[x] == 0x89) && (Base[x + 1] == 0x00)) {
+			// Get the variable being copied to list9[0]
+			int var = Base[x + 2];
+
+			// Look back for an assignment to the variable
+			for (j = 0; j < 2; j++) {
+				int y = x - ((j + 1) * 3);
+				if ((Base[y] == 0x48) && (Base[y + 2] == var)) {
+					// If this a V4 driver call?
+					switch (Base[y + 1]) {
+					case 0x0E:
+					case 0x20:
+					case 0x22:
+						return TRUE;
+					}
+					return FALSE;
+				}
+			}
+		}
+	}
+	return FALSE;
+}
+
+#ifdef FULLSCAN
+void Scanner::fullScan(byte *StartFile, uint32 size) {
+	byte *Image = (byte *)calloc(size, 1);
+	uint32 i, Size;
+	int Replace;
+	byte *ImagePtr;
+	uint32 MaxPos = 0;
+	uint32 MaxCount = 0;
+	uint32 Min, Max, MaxMin, MaxMax;
+	int Offset;
+	bool JumpKill, MaxJK;
+	for (i = 0; i < size; i++) {
+		Size = 0;
+		Min = Max = i;
+		Replace = 0;
+		if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
+			if (Size > MaxCount) {
+				MaxCount = Size;
+				MaxMin = Min;
+				MaxMax = Max;
+
+				MaxPos = i;
+				MaxJK = JumpKill;
+			}
+			Replace = 0;
+		}
+		for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
+			if (*ImagePtr == 2)
+				*ImagePtr = Replace;
+		}
+	}
+	printf("%ld %ld %ld %ld %s", MaxPos, MaxCount, MaxMin, MaxMax, MaxJK ? "jmp killed" : "");
+	/* search for reference to MaxPos */
+	Offset = 0x12 + 11 * 2;
+	for (i = 0; i < size - Offset - 1; i++) {
+		if ((L9WORD(StartFile + i + Offset)) + i == MaxPos) {
+			printf("possible v3,4 Code reference at : %ld", i);
+			/* startdata=StartFile+i; */
+		}
+	}
+	Offset = 13 * 2;
+	for (i = 0; i < size - Offset - 1; i++) {
+		if ((L9WORD(StartFile + i + Offset)) + i == MaxPos)
+			printf("possible v2 Code reference at : %ld", i);
+	}
+	free(Image);
+}
+#endif
+
+/*----------------------------------------------------------------------*/
+
 GameDetection::GameDetection(byte *&startData, size_t &fileSize) :
 		_startData(startData), _fileSize(fileSize), _crcInitialized(false), _gameName(nullptr) {
 	Common::fill(&_crcTable[0], &_crcTable[256], 0);
@@ -199,7 +680,6 @@ void GameDetection::gln_gameid_game_name_reset() {
 	_gameName = nullptr;
 }
 
-
 /*----------------------------------------------------------------------*/
 
 void Level9MetaEngine::getSupportedGames(PlainGameList &games) {
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
index 9e8401b..89bd854 100644
--- a/engines/glk/level9/detection.h
+++ b/engines/glk/level9/detection.h
@@ -31,6 +31,8 @@
 namespace Glk {
 namespace Level9 {
 
+enum L9GameTypes { L9_V1, L9_V2, L9_V3, L9_V4 };
+
 struct gln_game_table_t {
 	const size_t length;        ///< Datafile length in bytes
 	const byte checksum;        ///< 8-bit checksum, last datafile byte
@@ -48,10 +50,53 @@ struct gln_patch_table_t {
 };
 typedef const gln_patch_table_t *gln_patch_tableref_t;
 
+struct L9V1GameInfo {
+	byte dictVal1, dictVal2;
+	int dictStart, L9Ptrs[5], absData, msgStart, msgLen;
+};
+
+/**
+ * Scanner for game data
+ */
+class Scanner {
+private:
+	long scan(byte *StartFile, uint32 size);
+	long ScanV2(byte *StartFile, uint32 size);
+	long ScanV1(byte *StartFile, uint32 size);
+
+	bool ValidateSequence(byte *Base, byte *Image, uint32 iPos, uint32 acode, uint32 *Size, uint32 size, uint32 *Min, uint32 *Max, bool Rts, bool *JumpKill, bool *DriverV4);
+	uint16 scanmovewa5d0(byte *Base, uint32 *Pos);
+	uint32 scangetaddr(int Code, byte *Base, uint32 *Pos, uint32 acode, int *Mask);
+	void scangetcon(int Code, uint32 *Pos, int *Mask);
+	bool CheckCallDriverV4(byte *Base, uint32 Pos);
+#ifdef FULLSCAN
+	void Scanner::fullScan(byte *StartFile, uint32 size);
+#endif
+private:
+	byte **_dictData;
+	byte **_aCodePtr;
+public:
+	L9GameTypes _gameType;
+	int _l9V1Game;
+public:
+	Scanner() : _dictData(nullptr), _aCodePtr(nullptr), _gameType(L9_V1), _l9V1Game(-1) {}
+
+	/**
+	 * Scan passed file for a valid game, and if found return it's offset
+	 */
+	long scanner(byte *StartFile, uint32 size, byte **dictData = nullptr,
+		byte **aCodePtr = nullptr);
+
+	/**
+	 * Returns the info for a V1 game
+	 */
+	const L9V1GameInfo &v1Game() const;
+};
+
 /**
  * Detection manager for specific games
  */
-class GameDetection {
+class GameDetection : public Scanner {
 private:
 	byte *&_startData;
 	size_t &_fileSize;
diff --git a/engines/glk/level9/detection_tables.h b/engines/glk/level9/detection_tables.h
index dda4ce4..eb8ae7d 100644
--- a/engines/glk/level9/detection_tables.h
+++ b/engines/glk/level9/detection_tables.h
@@ -795,6 +795,13 @@ static const gln_patch_table_t GLN_PATCH_TABLE[] = {
 	{0x0000, 0x00, 0x0000, 0x00, 0x0000},
 };
 
+const L9V1GameInfo L9_V1_GAMES[] = {
+	0x1a, 0x24, 301, 0x0000, -0x004b, 0x0080, -0x002b, 0x00d0, 0x03b0, 0x0f80, 0x4857, /* Colossal Adventure */
+	0x20, 0x3b, 283, -0x0583, 0x0000, -0x0508, -0x04e0, 0x0000, 0x0800, 0x1000, 0x39d1, /* Adventure Quest */
+	0x14, 0xff, 153, -0x00d6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0a20, 0x16bf, 0x420d, /* Dungeon Adventure */
+	0x15, 0x5d, 252, -0x3e70, 0x0000, -0x3d30, -0x3ca0, 0x0100, 0x4120, -0x3b9d, 0x3988, /* Lords of Time */
+	0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
+};
 
 // TODO: The list of Level 9 games and detection entries needs to be filled out
 const PlainGameDescriptor LEVEL9_GAME_LIST[] = {
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index b4ab1a8..bbaad57 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -63,8 +63,8 @@ struct SaveStruct {
 };
 
 /* Enumerations */
-enum L9GameTypes { L9_V1, L9_V2, L9_V3, L9_V4 };
 enum L9MsgTypes { MSGT_V1, MSGT_V2 };
+
 /*
     Graphics type    Resolution     Scale stack reset
     -------------------------------------------------
@@ -80,18 +80,17 @@ L9BYTE *startfile, *pictureaddress, *picturedata;
 byte *startdata;
 size_t FileSize, picturesize;
 
-L9BYTE *L9Pointers[12];
-L9BYTE *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
-L9BYTE *startmd, *endmd, *endwdp5, *wordtable, *dictdata, *defdict;
+byte *L9Pointers[12];
+byte *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
+byte *startmd, *endmd, *endwdp5, *wordtable, *dictdata, *defdict;
 L9UINT16 dictdatalen;
-L9BYTE *startmdV2;
+byte *startmdV2;
 
 int wordcase;
 int unpackcount;
 char unpackbuf[8];
 L9BYTE *dictptr;
 char threechars[34];
-int L9GameType;
 int L9MsgType;
 char LastGame[MAX_PATH];
 char FirstLine[FIRSTLINESIZE];
@@ -143,20 +142,6 @@ L9UINT16 gnostack[128];
 L9BYTE gnoscratch[32];
 int object, gnosp, numobjectfound, searchdepth, inithisearchpos;
 
-
-struct L9V1GameInfo {
-	L9BYTE dictVal1, dictVal2;
-	int dictStart, L9Ptrs[5], absData, msgStart, msgLen;
-};
-const L9V1GameInfo L9V1Games[] = {
-	0x1a, 0x24, 301, 0x0000, -0x004b, 0x0080, -0x002b, 0x00d0, 0x03b0, 0x0f80, 0x4857, /* Colossal Adventure */
-	0x20, 0x3b, 283, -0x0583, 0x0000, -0x0508, -0x04e0, 0x0000, 0x0800, 0x1000, 0x39d1, /* Adventure Quest */
-	0x14, 0xff, 153, -0x00d6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0a20, 0x16bf, 0x420d, /* Dungeon Adventure */
-	0x15, 0x5d, 252, -0x3e70, 0x0000, -0x3d30, -0x3ca0, 0x0100, 0x4120, -0x3b9d, 0x3988, /* Lords of Time */
-	0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
-};
-int L9V1Game;
-
 /* Prototypes */
 L9BOOL LoadGame2(const char *filename, char *picname);
 int getlongcode();
@@ -269,7 +254,7 @@ void level9_initialize() {
 
 	lastchar = '.';
 	lastactualchar = 0;
-	L9V1Game = -1;
+	g_vm->_detection._l9V1Game = -1;
 
 	startfile = pictureaddress = picturedata = nullptr;
 }
@@ -688,480 +673,6 @@ L9BOOL load(const char *filename) {
 	return TRUE;
 }
 
-L9UINT16 scanmovewa5d0(L9BYTE *Base, L9UINT32 *Pos) {
-	L9UINT16 ret = L9WORD(Base + *Pos);
-	(*Pos) += 2;
-	return ret;
-}
-
-L9UINT32 scangetaddr(int Code, L9BYTE *Base, L9UINT32 *Pos, L9UINT32 acode, int *Mask) {
-	(*Mask) |= 0x20;
-	if (Code & 0x20) {
-		/* getaddrshort */
-		signed char diff = Base[*Pos];
-		(*Pos)++;
-		return (*Pos) + diff - 1;
-	} else {
-		return acode + scanmovewa5d0(Base, Pos);
-	}
-}
-
-void scangetcon(int Code, L9UINT32 *Pos, int *Mask) {
-	(*Pos)++;
-	if (!(Code & 64))(*Pos)++;
-	(*Mask) |= 0x40;
-}
-
-L9BOOL CheckCallDriverV4(L9BYTE *Base, L9UINT32 Pos) {
-	int i, j;
-
-	/* Look back for an assignment from a variable
-	 * to list9[0], which is used to specify the
-	 * driver call.
-	 */
-	for (i = 0; i < 2; i++) {
-		int x = Pos - ((i + 1) * 3);
-		if ((Base[x] == 0x89) && (Base[x + 1] == 0x00)) {
-			/* Get the variable being copied to list9[0] */
-			int var = Base[x + 2];
-
-			/* Look back for an assignment to the variable. */
-			for (j = 0; j < 2; j++) {
-				int y = x - ((j + 1) * 3);
-				if ((Base[y] == 0x48) && (Base[y + 2] == var)) {
-					/* If this a V4 driver call? */
-					switch (Base[y + 1]) {
-					case 0x0E:
-					case 0x20:
-					case 0x22:
-						return TRUE;
-					}
-					return FALSE;
-				}
-			}
-		}
-	}
-	return FALSE;
-}
-
-L9BOOL ValidateSequence(L9BYTE *Base, L9BYTE *Image, L9UINT32 iPos, L9UINT32 acode, L9UINT32 *Size, L9UINT32 size, L9UINT32 *Min, L9UINT32 *Max, L9BOOL Rts, L9BOOL *JumpKill, L9BOOL *DriverV4) {
-	L9UINT32 Pos;
-	L9BOOL Finished = FALSE, Valid;
-	L9UINT32 Strange = 0;
-	int ScanCodeMask;
-	int Code;
-	*JumpKill = FALSE;
-
-	if (iPos >= size)
-		return FALSE;
-	Pos = iPos;
-	if (Pos < *Min) *Min = Pos;
-
-	if (Image[Pos]) return TRUE; /* hit valid code */
-
-	do {
-		Code = Base[Pos];
-		Valid = TRUE;
-		if (Image[Pos]) break; /* converged to found code */
-		Image[Pos++] = 2;
-		if (Pos > *Max) *Max = Pos;
-
-		ScanCodeMask = 0x9f;
-		if (Code & 0x80) {
-			ScanCodeMask = 0xff;
-			if ((Code & 0x1f) > 0xa)
-				Valid = FALSE;
-			Pos += 2;
-		} else switch (Code & 0x1f) {
-			case 0: { /* goto */
-				L9UINT32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
-				Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, TRUE/*Rts*/, JumpKill, DriverV4);
-				Finished = TRUE;
-				break;
-			}
-			case 1: { /* intgosub */
-				L9UINT32 Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
-				Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, TRUE, JumpKill, DriverV4);
-				break;
-			}
-			case 2: /* intreturn */
-				Valid = Rts;
-				Finished = TRUE;
-				break;
-			case 3: /* printnumber */
-				Pos++;
-				break;
-			case 4: /* messagev */
-				Pos++;
-				break;
-			case 5: /* messagec */
-				scangetcon(Code, &Pos, &ScanCodeMask);
-				break;
-			case 6: /* function */
-				switch (Base[Pos++]) {
-				case 2:/* random */
-					Pos++;
-					break;
-				case 1:/* calldriver */
-					if (DriverV4) {
-						if (CheckCallDriverV4(Base, Pos - 2))
-							*DriverV4 = TRUE;
-					}
-					break;
-				case 3:/* save */
-				case 4:/* restore */
-				case 5:/* clearworkspace */
-				case 6:/* clear stack */
-					break;
-				case 250: /* printstr */
-					while (Base[Pos++]);
-					break;
-
-				default:
-#ifdef L9DEBUG
-					/* printf("scan: illegal function call: %d",Base[Pos-1]); */
-#endif
-					Valid = FALSE;
-					break;
-				}
-				break;
-			case 7: /* input */
-				Pos += 4;
-				break;
-			case 8: /* varcon */
-				scangetcon(Code, &Pos, &ScanCodeMask);
-				Pos++;
-				break;
-			case 9: /* varvar */
-				Pos += 2;
-				break;
-			case 10: /* _add */
-				Pos += 2;
-				break;
-			case 11: /* _sub */
-				Pos += 2;
-				break;
-			case 14: /* jump */
-#ifdef L9DEBUG
-				/* printf("jmp at codestart: %ld",acode); */
-#endif
-				*JumpKill = TRUE;
-				Finished = TRUE;
-				break;
-			case 15: /* exit */
-				Pos += 4;
-				break;
-			case 16: /* ifeqvt */
-			case 17: /* ifnevt */
-			case 18: /* ifltvt */
-			case 19: { /* ifgtvt */
-				L9UINT32 Val;
-				Pos += 2;
-				Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
-				Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
-				break;
-			}
-			case 20: /* screen */
-				if (Base[Pos++]) Pos++;
-				break;
-			case 21: /* cleartg */
-				Pos++;
-				break;
-			case 22: /* picture */
-				Pos++;
-				break;
-			case 23: /* getnextobject */
-				Pos += 6;
-				break;
-			case 24: /* ifeqct */
-			case 25: /* ifnect */
-			case 26: /* ifltct */
-			case 27: { /* ifgtct */
-				L9UINT32 Val;
-				Pos++;
-				scangetcon(Code, &Pos, &ScanCodeMask);
-				Val = scangetaddr(Code, Base, &Pos, acode, &ScanCodeMask);
-				Valid = ValidateSequence(Base, Image, Val, acode, Size, size, Min, Max, Rts, JumpKill, DriverV4);
-				break;
-			}
-			case 28: /* printinput */
-				break;
-			case 12: /* ilins */
-			case 13: /* ilins */
-			case 29: /* ilins */
-			case 30: /* ilins */
-			case 31: /* ilins */
-#ifdef L9DEBUG
-				/* printf("scan: illegal instruction"); */
-#endif
-				Valid = FALSE;
-				break;
-			}
-		if (Valid && (Code & ~ScanCodeMask))
-			Strange++;
-	} while (Valid && !Finished && Pos < size); /* && Strange==0); */
-	(*Size) += Pos - iPos;
-	return Valid; /* && Strange==0; */
-}
-
-L9BYTE calcchecksum(L9BYTE *ptr, L9UINT32 num) {
-	L9BYTE d1 = 0;
-	while (num-- != 0) d1 += *ptr++;
-	return d1;
-}
-
-long Scan(L9BYTE *StartFile, L9UINT32 size) {
-	L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
-	L9BYTE *Image = (L9BYTE *)calloc(size, 1);
-	L9UINT32 i, num, Size, MaxSize = 0;
-	int j;
-	L9UINT16 d0 = 0, l9, md, ml, dd, dl;
-	L9UINT32 Min, Max;
-	long Offset = -1;
-	L9BOOL JumpKill, DriverV4;
-
-	if ((Chk == nullptr) || (Image == nullptr)) {
-		error("Unable to allocate memory for game scan! Exiting...");
-	}
-
-	Chk[0] = 0;
-	for (i = 1; i <= size; i++)
-		Chk[i] = Chk[i - 1] + StartFile[i - 1];
-
-	for (i = 0; i < size - 33; i++) {
-		num = L9WORD(StartFile + i) + 1;
-		/*
-		        Chk[i] = 0 +...+ i-1
-		        Chk[i+n] = 0 +...+ i+n-1
-		        Chk[i+n] - Chk[i] = i + ... + i+n
-		*/
-		if (num > 0x2000 && i + num <= size && Chk[i + num] == Chk[i]) {
-			md = L9WORD(StartFile + i + 0x2);
-			ml = L9WORD(StartFile + i + 0x4);
-			dd = L9WORD(StartFile + i + 0xa);
-			dl = L9WORD(StartFile + i + 0xc);
-
-			if (ml > 0 && md > 0 && i + md + ml <= size && dd > 0 && dl > 0 && i + dd + dl * 4 <= size) {
-				/* v4 files may have acodeptr in 8000-9000, need to fix */
-				for (j = 0; j < 12; j++) {
-					d0 = L9WORD(StartFile + i + 0x12 + j * 2);
-					if (j != 11 && d0 >= 0x8000 && d0 < 0x9000) {
-						if (d0 >= 0x8000 + LISTAREASIZE) break;
-					} else if (i + d0 > size) break;
-				}
-				/* list9 ptr must be in listarea, acode ptr in data */
-				if (j < 12 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
-
-				l9 = L9WORD(StartFile + i + 0x12 + 10 * 2);
-				if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
-
-				Size = 0;
-				Min = Max = i + d0;
-				DriverV4 = 0;
-				if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, &DriverV4)) {
-#ifdef L9DEBUG
-					printf("Found valid header at %ld, code size %ld", i, Size);
-#endif
-					if (Size > MaxSize && Size > 100) {
-						Offset = i;
-						MaxSize = Size;
-						L9GameType = DriverV4 ? L9_V4 : L9_V3;
-					}
-				}
-			}
-		}
-	}
-	free(Chk);
-	free(Image);
-	return Offset;
-}
-
-long ScanV2(L9BYTE *StartFile, L9UINT32 size) {
-	L9BYTE *Chk = (L9BYTE *)malloc(size + 1);
-	L9BYTE *Image = (L9BYTE *)calloc(size, 1);
-	L9UINT32 i, Size, MaxSize = 0, num;
-	int j;
-	L9UINT16 d0 = 0, l9;
-	L9UINT32 Min, Max;
-	long Offset = -1;
-	L9BOOL JumpKill;
-
-	if ((Chk == nullptr) || (Image == nullptr)) {
-		error("Unable to allocate memory for game scan! Exiting...");
-	}
-
-	Chk[0] = 0;
-	for (i = 1; i <= size; i++)
-		Chk[i] = Chk[i - 1] + StartFile[i - 1];
-
-	for (i = 0; i < size - 28; i++) {
-		num = L9WORD(StartFile + i + 28) + 1;
-		if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == StartFile[i + 0x1e]) {
-			for (j = 0; j < 14; j++) {
-				d0 = L9WORD(StartFile + i + j * 2);
-				if (j != 13 && d0 >= 0x8000 && d0 < 0x9000) {
-					if (d0 >= 0x8000 + LISTAREASIZE) break;
-				} else if (i + d0 > size) break;
-			}
-			/* list9 ptr must be in listarea, acode ptr in data */
-			if (j < 14 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
-
-			l9 = L9WORD(StartFile + i + 6 + 9 * 2);
-			if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
-
-			Size = 0;
-			Min = Max = i + d0;
-			if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
-#ifdef L9DEBUG
-				printf("Found valid V2 header at %ld, code size %ld", i, Size);
-#endif
-				if (Size > MaxSize && Size > 100) {
-					Offset = i;
-					MaxSize = Size;
-				}
-			}
-		}
-	}
-	free(Chk);
-	free(Image);
-	return Offset;
-}
-
-long ScanV1(L9BYTE *StartFile, L9UINT32 size) {
-	L9BYTE *Image = (L9BYTE *)calloc(size, 1);
-	L9UINT32 i, Size;
-	int Replace;
-	L9BYTE *ImagePtr;
-	long MaxPos = -1;
-	L9UINT32 MaxCount = 0;
-	L9UINT32 Min, Max; //, MaxMax, MaxMin;
-	L9BOOL JumpKill; // , MaxJK;
-
-	int dictOff1 = 0, dictOff2 = 0;
-	L9BYTE dictVal1 = 0xff, dictVal2 = 0xff;
-
-	if (Image == nullptr) {
-		error("Unable to allocate memory for game scan! Exiting...");
-	}
-
-	for (i = 0; i < size; i++) {
-		if ((StartFile[i] == 0 && StartFile[i + 1] == 6) || (StartFile[i] == 32 && StartFile[i + 1] == 4)) {
-			Size = 0;
-			Min = Max = i;
-			Replace = 0;
-			if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
-				if (Size > MaxCount && Size > 100 && Size < 10000) {
-					MaxCount = Size;
-					//MaxMin = Min;
-					//MaxMax = Max;
-
-					MaxPos = i;
-					//MaxJK = JumpKill;
-				}
-				Replace = 0;
-			}
-			for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
-				if (*ImagePtr == 2)
-					*ImagePtr = Replace;
-			}
-		}
-	}
-#ifdef L9DEBUG
-	printf("V1scan found code at %ld size %ld", MaxPos, MaxCount);
-#endif
-
-	/* V1 dictionary detection from L9Cut by Paul David Doherty */
-	for (i = 0; i < size - 20; i++) {
-		if (StartFile[i] == 'A') {
-			if (StartFile[i + 1] == 'T' && StartFile[i + 2] == 'T' && StartFile[i + 3] == 'A' && StartFile[i + 4] == 'C' && StartFile[i + 5] == 0xcb) {
-				dictOff1 = i;
-				dictVal1 = StartFile[dictOff1 + 6];
-				break;
-			}
-		}
-	}
-	for (i = dictOff1; i < size - 20; i++) {
-		if (StartFile[i] == 'B') {
-			if (StartFile[i + 1] == 'U' && StartFile[i + 2] == 'N' && StartFile[i + 3] == 'C' && StartFile[i + 4] == 0xc8) {
-				dictOff2 = i;
-				dictVal2 = StartFile[dictOff2 + 5];
-				break;
-			}
-		}
-	}
-	L9V1Game = -1;
-	if (dictVal1 != 0xff || dictVal2 != 0xff) {
-		for (i = 0; i < sizeof L9V1Games / sizeof L9V1Games[0]; i++) {
-			if ((L9V1Games[i].dictVal1 == dictVal1) && (L9V1Games[i].dictVal2 == dictVal2)) {
-				L9V1Game = i;
-				dictdata = StartFile + dictOff1 - L9V1Games[i].dictStart;
-			}
-		}
-	}
-
-#ifdef L9DEBUG
-	if (L9V1Game >= 0)
-		printf("V1scan found known dictionary: %d", L9V1Game);
-#endif
-
-	free(Image);
-
-	if (MaxPos > 0) {
-		acodeptr = StartFile + MaxPos;
-		return 0;
-	}
-	return -1;
-}
-
-#ifdef FULLSCAN
-void FullScan(L9BYTE *StartFile, L9UINT32 size) {
-	L9BYTE *Image = (L9BYTE *)calloc(size, 1);
-	L9UINT32 i, Size;
-	int Replace;
-	L9BYTE *ImagePtr;
-	L9UINT32 MaxPos = 0;
-	L9UINT32 MaxCount = 0;
-	L9UINT32 Min, Max, MaxMin, MaxMax;
-	int Offset;
-	L9BOOL JumpKill, MaxJK;
-	for (i = 0; i < size; i++) {
-		Size = 0;
-		Min = Max = i;
-		Replace = 0;
-		if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
-			if (Size > MaxCount) {
-				MaxCount = Size;
-				MaxMin = Min;
-				MaxMax = Max;
-
-				MaxPos = i;
-				MaxJK = JumpKill;
-			}
-			Replace = 0;
-		}
-		for (ImagePtr = Image + Min; ImagePtr <= Image + Max; ImagePtr++) {
-			if (*ImagePtr == 2)
-				*ImagePtr = Replace;
-		}
-	}
-	printf("%ld %ld %ld %ld %s", MaxPos, MaxCount, MaxMin, MaxMax, MaxJK ? "jmp killed" : "");
-	/* search for reference to MaxPos */
-	Offset = 0x12 + 11 * 2;
-	for (i = 0; i < size - Offset - 1; i++) {
-		if ((L9WORD(StartFile + i + Offset)) + i == MaxPos) {
-			printf("possible v3,4 Code reference at : %ld", i);
-			/* startdata=StartFile+i; */
-		}
-	}
-	Offset = 13 * 2;
-	for (i = 0; i < size - Offset - 1; i++) {
-		if ((L9WORD(StartFile + i + Offset)) + i == MaxPos)
-			printf("possible v2 Code reference at : %ld", i);
-	}
-	free(Image);
-}
-#endif
-
 L9BOOL findsubs(L9BYTE *testptr, L9UINT32 testsize, L9BYTE **picdata, L9UINT32 *picsize) {
 	int i, j, length, count;
 	L9BYTE *picptr, *startptr, *tmpptr;
@@ -1269,44 +780,32 @@ L9BOOL intinitialise(const char *filename, char *picname) {
 	screencalled = 0;
 	l9textmode = 0;
 
-#ifdef FULLSCAN
-	FullScan(startfile, FileSize);
-#endif
-
-	Offset = Scan(startfile, FileSize);
+	Offset = g_vm->_detection.scanner(startfile, FileSize, &dictdata, &acodeptr);
 	if (Offset < 0) {
-		Offset = ScanV2(startfile, FileSize);
-		L9GameType = L9_V2;
-		if (Offset < 0) {
-			Offset = ScanV1(startfile, FileSize);
-			L9GameType = L9_V1;
-			if (Offset < 0) {
-				error("\rUnable to locate valid Level 9 game in file: %s\r", filename);
-				return FALSE;
-			}
-		}
+		error("Unable to locate valid Level 9 game in file: %s", filename);
+		return FALSE;
 	}
 
 	startdata = startfile + Offset;
 	FileSize -= Offset;
 
 	/* setup pointers */
-	if (L9GameType == L9_V1) {
-		if (L9V1Game < 0) {
+	if (g_vm->_detection._gameType == L9_V1) {
+		if (g_vm->_detection._l9V1Game < 0) {
 			error("\rWhat appears to be V1 game data was found, but the game was not recognised.\rEither this is an unknown V1 game file or, more likely, it is corrupted.\r");
 			return FALSE;
 		}
 		for (i = 0; i < 6; i++) {
-			int off = L9V1Games[L9V1Game].L9Ptrs[i];
+			int off = g_vm->_detection.v1Game().L9Ptrs[i];
 			if (off < 0)
 				L9Pointers[i + 2] = acodeptr + off;
 			else
 				L9Pointers[i + 2] = workspace.listarea + off;
 		}
-		absdatablock = acodeptr - L9V1Games[L9V1Game].absData;
+		absdatablock = acodeptr - g_vm->_detection.v1Game().absData;
 	} else {
 		/* V2,V3,V4 */
-		hdoffset = L9GameType == L9_V2 ? 4 : 0x12;
+		hdoffset = g_vm->_detection._gameType == L9_V2 ? 4 : 0x12;
 		for (i = 0; i < 12; i++) {
 			L9UINT16 d0 = L9WORD(startdata + hdoffset + i * 2);
 			L9Pointers[i] = (i != 11 && d0 >= 0x8000 && d0 <= 0x9000) ? workspace.listarea + d0 - 0x8000 : startdata + d0;
@@ -1320,11 +819,11 @@ L9BOOL intinitialise(const char *filename, char *picname) {
 		acodeptr = L9Pointers[11];
 	}
 
-	switch (L9GameType) {
+	switch (g_vm->_detection._gameType) {
 	case L9_V1: {
 		double a1;
-		startmd = acodeptr + L9V1Games[L9V1Game].msgStart;
-		startmdV2 = startmd + L9V1Games[L9V1Game].msgLen;
+		startmd = acodeptr + g_vm->_detection.v1Game().msgStart;
+		startmdV2 = startmd + g_vm->_detection.v1Game().msgLen;
 
 		if (analyseV1(&a1) && a1 > 2 && a1 < 10) {
 			L9MsgType = MSGT_V1;
@@ -1393,8 +892,14 @@ L9BOOL intinitialise(const char *filename, char *picname) {
 	return TRUE;
 }
 
+static byte calcChecksum(byte *ptr, uint32 num) {
+	byte d1 = 0;
+	while (num-- != 0) d1 += *ptr++;
+	return d1;
+}
+
 L9BOOL checksumgamedata() {
-	return calcchecksum(startdata, L9WORD(startdata) + 1) == 0;
+	return calcChecksum(startdata, L9WORD(startdata) + 1) == 0;
 }
 
 L9UINT16 movewa5d0() {
@@ -1462,14 +967,14 @@ void printnumber() {
 }
 
 void messagec() {
-	if (L9GameType <= L9_V2)
+	if (g_vm->_detection._gameType <= L9_V2)
 		printmessageV2(getcon());
 	else
 		printmessage(getcon());
 }
 
 void messagev() {
-	if (L9GameType <= L9_V2)
+	if (g_vm->_detection._gameType <= L9_V2)
 		printmessageV2(*getvar());
 	else
 		printmessage(*getvar());
@@ -1932,7 +1437,7 @@ void function() {
 
 	switch (d0) {
 	case 1:
-		if (L9GameType == L9_V1)
+		if (g_vm->_detection._gameType == L9_V1)
 			StopGame();
 		else
 			calldriver();
@@ -2048,7 +1553,7 @@ L9UINT32 readdecimal(char *buff) {
 
 void checknumber() {
 	if (*obuff >= 0x30 && *obuff < 0x3a) {
-		if (L9GameType == L9_V4) {
+		if (g_vm->_detection._gameType == L9_V4) {
 			*list9ptr = 1;
 			L9SETWORD(list9ptr + 1, readdecimal(obuff));
 			L9SETWORD(list9ptr + 3, 0);
@@ -2067,7 +1572,7 @@ void NextCheat() {
 	memmove(&workspace, &CheatWorkspace, sizeof(GameState));
 	codeptr = acodeptr + workspace.codeptr;
 
-	if (!((L9GameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++))) {
+	if (!((g_vm->_detection._gameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++))) {
 		Cheating = FALSE;
 		printstring("\rCheat failed.\r");
 		*ibuff = 0;
@@ -2122,7 +1627,7 @@ L9BOOL CheckHash() {
 	} else if (scumm_stricmp(ibuff, "#dictionary") == 0) {
 		CheatWord = 0;
 		printstring("\r");
-		while ((L9GameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++)) {
+		while ((g_vm->_detection._gameType <= L9_V2) ? GetWordV2(ibuff, CheatWord++) : GetWordV3(ibuff, CheatWord++)) {
 			error("%s ", ibuff);
 			if (os_stoplist() || !Running) break;
 		}
@@ -2131,7 +1636,7 @@ L9BOOL CheckHash() {
 	} else if (scumm_strnicmp(ibuff, "#picture ", 9) == 0) {
 		int pic = 0;
 		if (sscanf(ibuff + 9, "%d", &pic) == 1) {
-			if (L9GameType == L9_V4)
+			if (g_vm->_detection._gameType == L9_V4)
 				os_show_bitmap(pic, 0, 0);
 			else
 				show_picture(pic);
@@ -2157,7 +1662,7 @@ L9BOOL CheckHash() {
 L9BOOL IsInputChar(char c) {
 	if (c == '-' || c == '\'')
 		return TRUE;
-	if ((L9GameType >= L9_V3) && (c == '.' || c == ','))
+	if ((g_vm->_detection._gameType >= L9_V3) && (c == '.' || c == ','))
 		return TRUE;
 	return Common::isAlnum(c);
 }
@@ -2444,7 +1949,7 @@ L9BOOL inputV2(int *wordcount) {
 }
 
 void input() {
-	if (L9GameType == L9_V3 && FirstPicture >= 0) {
+	if (g_vm->_detection._gameType == L9_V3 && FirstPicture >= 0) {
 		show_picture(FirstPicture);
 		FirstPicture = -1;
 	}
@@ -2454,7 +1959,7 @@ void input() {
 	   are called out of line */
 
 	codeptr--;
-	if (L9GameType <= L9_V2) {
+	if (g_vm->_detection._gameType <= L9_V2) {
 		int wordcount;
 		if (inputV2(&wordcount)) {
 			L9BYTE *obuffptr = (L9BYTE *) obuff;
@@ -2519,7 +2024,7 @@ void exit1(L9BYTE *d4, L9BYTE *d5p, L9BYTE d6, L9BYTE d7) {
 	if (--d1) {
 		do {
 			d0 = *a0;
-			if (L9GameType == L9_V4) {
+			if (g_vm->_detection._gameType == L9_V4) {
 				if ((d0 == 0) && (*(a0 + 1) == 0))
 					goto notfn4;
 			}
@@ -2622,7 +2127,7 @@ int scaley(int y) {
 }
 
 void detect_gfx_mode() {
-	if (L9GameType == L9_V3) {
+	if (g_vm->_detection._gameType == L9_V3) {
 		/* These V3 games use graphics logic similar to the V2 games */
 		if (strstr(FirstLine, "price of magik") != 0)
 			gfx_mode = GFX_V3A;
@@ -2653,7 +2158,7 @@ void detect_gfx_mode() {
 void _screen() {
 	int mode = 0;
 
-	if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
+	if (g_vm->_detection._gameType == L9_V3 && strlen(FirstLine) == 0) {
 		if (*codeptr++)
 			codeptr++;
 		return;
@@ -2662,7 +2167,7 @@ void _screen() {
 	detect_gfx_mode();
 	l9textmode = *codeptr++;
 	if (l9textmode) {
-		if (L9GameType == L9_V4)
+		if (g_vm->_detection._gameType == L9_V4)
 			mode = 2;
 		else if (picturedata)
 			mode = 1;
@@ -3094,7 +2599,7 @@ void absrunsub(int d0) {
 }
 
 void show_picture(int pic) {
-	if (L9GameType == L9_V3 && strlen(FirstLine) == 0) {
+	if (g_vm->_detection._gameType == L9_V3 && strlen(FirstLine) == 0) {
 		FirstPicture = pic;
 		return;
 	}
@@ -3135,7 +2640,7 @@ void picture() {
 }
 
 void GetPictureSize(int *width, int *height) {
-	if (L9GameType == L9_V4) {
+	if (g_vm->_detection._gameType == L9_V4) {
 		if (width != nullptr)
 			*width = 0;
 		if (height != nullptr)


Commit: d9c8237042c626a205ff5f59bcdbfe9ea31599d6
    https://github.com/scummvm/scummvm/commit/d9c8237042c626a205ff5f59bcdbfe9ea31599d6
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:30-07:00

Commit Message:
GLK: LEVEL9: Hook up new detection

Changed paths:
    engines/glk/level9/detection.cpp
    engines/glk/level9/detection.h
    engines/glk/level9/detection_tables.h


diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
index 3b3228e..93e8143 100644
--- a/engines/glk/level9/detection.cpp
+++ b/engines/glk/level9/detection.cpp
@@ -25,6 +25,7 @@
 #include "glk/level9/level9_main.h"
 #include "glk/level9/os_glk.h"
 #include "glk/blorb.h"
+#include "glk/detection.h"
 #include "common/debug.h"
 #include "common/file.h"
 #include "common/md5.h"
@@ -62,14 +63,14 @@ const L9V1GameInfo &Scanner::v1Game() const {
 	return L9_V1_GAMES[_l9V1Game];
 }
 
-long Scanner::scan(byte *StartFile, uint32 size) {
+long Scanner::scan(byte *startFile, uint32 size) {
 	byte *Chk = (byte *)malloc(size + 1);
 	byte *Image = (byte *)calloc(size, 1);
 	uint32 i, num, Size, MaxSize = 0;
 	int j;
 	uint16 d0 = 0, l9, md, ml, dd, dl;
 	uint32 Min, Max;
-	long Offset = -1;
+	long offset = -1;
 	bool JumpKill, DriverV4;
 
 	if ((Chk == nullptr) || (Image == nullptr)) {
@@ -78,25 +79,25 @@ long Scanner::scan(byte *StartFile, uint32 size) {
 
 	Chk[0] = 0;
 	for (i = 1; i <= size; i++)
-		Chk[i] = Chk[i - 1] + StartFile[i - 1];
+		Chk[i] = Chk[i - 1] + startFile[i - 1];
 
 	for (i = 0; i < size - 33; i++) {
-		num = L9WORD(StartFile + i) + 1;
+		num = L9WORD(startFile + i) + 1;
 		/*
 		        Chk[i] = 0 +...+ i-1
 		        Chk[i+n] = 0 +...+ i+n-1
 		        Chk[i+n] - Chk[i] = i + ... + i+n
 		*/
 		if (num > 0x2000 && i + num <= size && Chk[i + num] == Chk[i]) {
-			md = L9WORD(StartFile + i + 0x2);
-			ml = L9WORD(StartFile + i + 0x4);
-			dd = L9WORD(StartFile + i + 0xa);
-			dl = L9WORD(StartFile + i + 0xc);
+			md = L9WORD(startFile + i + 0x2);
+			ml = L9WORD(startFile + i + 0x4);
+			dd = L9WORD(startFile + i + 0xa);
+			dl = L9WORD(startFile + i + 0xc);
 
 			if (ml > 0 && md > 0 && i + md + ml <= size && dd > 0 && dl > 0 && i + dd + dl * 4 <= size) {
 				/* v4 files may have acodeptr in 8000-9000, need to fix */
 				for (j = 0; j < 12; j++) {
-					d0 = L9WORD(StartFile + i + 0x12 + j * 2);
+					d0 = L9WORD(startFile + i + 0x12 + j * 2);
 					if (j != 11 && d0 >= 0x8000 && d0 < 0x9000) {
 						if (d0 >= 0x8000 + LISTAREASIZE) break;
 					} else if (i + d0 > size) break;
@@ -104,15 +105,15 @@ long Scanner::scan(byte *StartFile, uint32 size) {
 				/* list9 ptr must be in listarea, acode ptr in data */
 				if (j < 12 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
 
-				l9 = L9WORD(StartFile + i + 0x12 + 10 * 2);
+				l9 = L9WORD(startFile + i + 0x12 + 10 * 2);
 				if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
 
 				Size = 0;
 				Min = Max = i + d0;
 				DriverV4 = 0;
-				if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, &DriverV4)) {
+				if (ValidateSequence(startFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, &DriverV4)) {
 					if (Size > MaxSize && Size > 100) {
-						Offset = i;
+						offset = i;
 						MaxSize = Size;
 						_gameType = DriverV4 ? L9_V4 : L9_V3;
 					}
@@ -123,17 +124,17 @@ long Scanner::scan(byte *StartFile, uint32 size) {
 
 	free(Chk);
 	free(Image);
-	return Offset;
+	return offset;
 }
 
-long Scanner::ScanV2(byte *StartFile, uint32 size) {
+long Scanner::ScanV2(byte *startFile, uint32 size) {
 	byte *Chk = (byte *)malloc(size + 1);
 	byte *Image = (byte *)calloc(size, 1);
 	uint32 i, Size, MaxSize = 0, num;
 	int j;
 	uint16 d0 = 0, l9;
 	uint32 Min, Max;
-	long Offset = -1;
+	long offset = -1;
 	bool JumpKill;
 
 	if ((Chk == nullptr) || (Image == nullptr)) {
@@ -142,13 +143,13 @@ long Scanner::ScanV2(byte *StartFile, uint32 size) {
 
 	Chk[0] = 0;
 	for (i = 1; i <= size; i++)
-		Chk[i] = Chk[i - 1] + StartFile[i - 1];
+		Chk[i] = Chk[i - 1] + startFile[i - 1];
 
 	for (i = 0; i < size - 28; i++) {
-		num = L9WORD(StartFile + i + 28) + 1;
-		if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == StartFile[i + 0x1e]) {
+		num = L9WORD(startFile + i + 28) + 1;
+		if (i + num <= size && ((Chk[i + num] - Chk[i + 32]) & 0xff) == startFile[i + 0x1e]) {
 			for (j = 0; j < 14; j++) {
-				d0 = L9WORD(StartFile + i + j * 2);
+				d0 = L9WORD(startFile + i + j * 2);
 				if (j != 13 && d0 >= 0x8000 && d0 < 0x9000) {
 					if (d0 >= 0x8000 + LISTAREASIZE) break;
 				} else if (i + d0 > size) break;
@@ -156,17 +157,17 @@ long Scanner::ScanV2(byte *StartFile, uint32 size) {
 			/* list9 ptr must be in listarea, acode ptr in data */
 			if (j < 14 /*|| (d0>=0x8000 && d0<0x9000)*/) continue;
 
-			l9 = L9WORD(StartFile + i + 6 + 9 * 2);
+			l9 = L9WORD(startFile + i + 6 + 9 * 2);
 			if (l9 < 0x8000 || l9 >= 0x8000 + LISTAREASIZE) continue;
 
 			Size = 0;
 			Min = Max = i + d0;
-			if (ValidateSequence(StartFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
+			if (ValidateSequence(startFile, Image, i + d0, i + d0, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
 #ifdef L9DEBUG
 				printf("Found valid V2 header at %ld, code size %ld", i, Size);
 #endif
 				if (Size > MaxSize && Size > 100) {
-					Offset = i;
+					offset = i;
 					MaxSize = Size;
 				}
 			}
@@ -174,10 +175,10 @@ long Scanner::ScanV2(byte *StartFile, uint32 size) {
 	}
 	free(Chk);
 	free(Image);
-	return Offset;
+	return offset;
 }
 
-long Scanner::ScanV1(byte *StartFile, uint32 size) {
+long Scanner::ScanV1(byte *startFile, uint32 size) {
 	byte *Image = (byte *)calloc(size, 1);
 	uint32 i, Size;
 	int Replace;
@@ -195,11 +196,11 @@ long Scanner::ScanV1(byte *StartFile, uint32 size) {
 	}
 
 	for (i = 0; i < size; i++) {
-		if ((StartFile[i] == 0 && StartFile[i + 1] == 6) || (StartFile[i] == 32 && StartFile[i + 1] == 4)) {
+		if ((startFile[i] == 0 && startFile[i + 1] == 6) || (startFile[i] == 32 && startFile[i + 1] == 4)) {
 			Size = 0;
 			Min = Max = i;
 			Replace = 0;
-			if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
+			if (ValidateSequence(startFile, Image, i, i, &Size, size, &Min, &Max, false, &JumpKill, nullptr)) {
 				if (Size > MaxCount && Size > 100 && Size < 10000) {
 					MaxCount = Size;
 					//MaxMin = Min;
@@ -219,19 +220,19 @@ long Scanner::ScanV1(byte *StartFile, uint32 size) {
 
 	/* V1 dictionary detection from L9Cut by Paul David Doherty */
 	for (i = 0; i < size - 20; i++) {
-		if (StartFile[i] == 'A') {
-			if (StartFile[i + 1] == 'T' && StartFile[i + 2] == 'T' && StartFile[i + 3] == 'A' && StartFile[i + 4] == 'C' && StartFile[i + 5] == 0xcb) {
+		if (startFile[i] == 'A') {
+			if (startFile[i + 1] == 'T' && startFile[i + 2] == 'T' && startFile[i + 3] == 'A' && startFile[i + 4] == 'C' && startFile[i + 5] == 0xcb) {
 				dictOff1 = i;
-				dictVal1 = StartFile[dictOff1 + 6];
+				dictVal1 = startFile[dictOff1 + 6];
 				break;
 			}
 		}
 	}
 	for (i = dictOff1; i < size - 20; i++) {
-		if (StartFile[i] == 'B') {
-			if (StartFile[i + 1] == 'U' && StartFile[i + 2] == 'N' && StartFile[i + 3] == 'C' && StartFile[i + 4] == 0xc8) {
+		if (startFile[i] == 'B') {
+			if (startFile[i + 1] == 'U' && startFile[i + 2] == 'N' && startFile[i + 3] == 'C' && startFile[i + 4] == 0xc8) {
 				dictOff2 = i;
-				dictVal2 = StartFile[dictOff2 + 5];
+				dictVal2 = startFile[dictOff2 + 5];
 				break;
 			}
 		}
@@ -241,7 +242,7 @@ long Scanner::ScanV1(byte *StartFile, uint32 size) {
 		for (i = 0; i < sizeof L9_V1_GAMES / sizeof L9_V1_GAMES[0]; i++) {
 			if ((L9_V1_GAMES[i].dictVal1 == dictVal1) && (L9_V1_GAMES[i].dictVal2 == dictVal2)) {
 				_l9V1Game = i;
-				(*_dictData) = StartFile + dictOff1 - L9_V1_GAMES[i].dictStart;
+				(*_dictData) = startFile + dictOff1 - L9_V1_GAMES[i].dictStart;
 			}
 		}
 	}
@@ -249,7 +250,7 @@ long Scanner::ScanV1(byte *StartFile, uint32 size) {
 	free(Image);
 
 	if (MaxPos > 0 && _aCodePtr) {
-		(*_aCodePtr) = StartFile + MaxPos;
+		(*_aCodePtr) = startFile + MaxPos;
 		return 0;
 	}
 
@@ -463,7 +464,7 @@ bool Scanner::CheckCallDriverV4(byte *Base, uint32 Pos) {
 }
 
 #ifdef FULLSCAN
-void Scanner::fullScan(byte *StartFile, uint32 size) {
+void Scanner::fullScan(byte *startFile, uint32 size) {
 	byte *Image = (byte *)calloc(size, 1);
 	uint32 i, Size;
 	int Replace;
@@ -471,13 +472,13 @@ void Scanner::fullScan(byte *StartFile, uint32 size) {
 	uint32 MaxPos = 0;
 	uint32 MaxCount = 0;
 	uint32 Min, Max, MaxMin, MaxMax;
-	int Offset;
+	int offset;
 	bool JumpKill, MaxJK;
 	for (i = 0; i < size; i++) {
 		Size = 0;
 		Min = Max = i;
 		Replace = 0;
-		if (ValidateSequence(StartFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
+		if (ValidateSequence(startFile, Image, i, i, &Size, size, &Min, &Max, FALSE, &JumpKill, nullptr)) {
 			if (Size > MaxCount) {
 				MaxCount = Size;
 				MaxMin = Min;
@@ -495,16 +496,16 @@ void Scanner::fullScan(byte *StartFile, uint32 size) {
 	}
 	printf("%ld %ld %ld %ld %s", MaxPos, MaxCount, MaxMin, MaxMax, MaxJK ? "jmp killed" : "");
 	/* search for reference to MaxPos */
-	Offset = 0x12 + 11 * 2;
-	for (i = 0; i < size - Offset - 1; i++) {
-		if ((L9WORD(StartFile + i + Offset)) + i == MaxPos) {
+	offset = 0x12 + 11 * 2;
+	for (i = 0; i < size - offset - 1; i++) {
+		if ((L9WORD(startFile + i + offset)) + i == MaxPos) {
 			printf("possible v3,4 Code reference at : %ld", i);
-			/* startdata=StartFile+i; */
+			/* startdata=startFile+i; */
 		}
 	}
-	Offset = 13 * 2;
-	for (i = 0; i < size - Offset - 1; i++) {
-		if ((L9WORD(StartFile + i + Offset)) + i == MaxPos)
+	offset = 13 * 2;
+	for (i = 0; i < size - offset - 1; i++) {
+		if ((L9WORD(startFile + i + offset)) + i == MaxPos)
 			printf("possible v2 Code reference at : %ld", i);
 	}
 	free(Image);
@@ -585,7 +586,6 @@ gln_game_tableref_t GameDetection::gln_gameid_identify_game() {
 static const uint16 GLN_CRC_POLYNOMIAL = 0xa001;
 
 uint16 GameDetection::gln_get_buffer_crc(const void *void_buffer, size_t length, size_t padding) {
-
 	const char *buffer = (const char *)void_buffer;
 	uint16 crc;
 	size_t index;
@@ -683,15 +683,26 @@ void GameDetection::gln_gameid_game_name_reset() {
 /*----------------------------------------------------------------------*/
 
 void Level9MetaEngine::getSupportedGames(PlainGameList &games) {
-	for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
-		games.push_back(*pd);
+	const char *prior_id = nullptr;
+
+	for (const gln_game_table_t *pd = GLN_GAME_TABLE; pd->name; ++pd) {
+		if (prior_id == nullptr || strcmp(pd->gameId, prior_id)) {
+			PlainGameDescriptor gd;
+			gd.gameId = pd->gameId;
+			gd.description = pd->name;
+			games.push_back(gd);
+
+			prior_id = pd->gameId;
+		}
 	}
 }
 
 GameDescriptor Level9MetaEngine::findGame(const char *gameId) {
-	for (const PlainGameDescriptor *pd = LEVEL9_GAME_LIST; pd->gameId; ++pd) {
-		if (!strcmp(gameId, pd->gameId))
-			return *pd;
+	for (const gln_game_table_t *pd = GLN_GAME_TABLE; pd->gameId; ++pd) {
+		if (!strcmp(gameId, pd->gameId)) {
+			GameDescriptor gd(pd->gameId, pd->name, 0);
+			return gd;
+		}
 	}
 
 	return PlainGameDescriptor();
@@ -704,42 +715,64 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
 		if (file->isDirectory())
 			continue;
 		Common::String filename = file->getName();
-		if (!filename.hasSuffixIgnoreCase(".l9"))
+		if (!filename.hasSuffixIgnoreCase(".l9") && !filename.hasSuffixIgnoreCase(".dat"))
 			continue;
 
-		// Open up the file and calculate the md5
+		// Open up the file so we can get it's size
 		Common::File gameFile;
 		if (!gameFile.open(*file))
 			continue;
 
-		Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
-		size_t _fileSize = gameFile.size();
-		gameFile.seek(0);
-		bool isBlorb = Blorb::isBlorb(gameFile, ID_ADRI);
+		size_t fileSize = gameFile.size();
+		if (fileSize > 0xffff) {
+			// Too big to possibly be a Level 9 game
+			gameFile.close();
+			continue;
+		}
+
+		// Read in the game data
+		Common::Array<byte> data;
+		data.resize(fileSize);
+		gameFile.read(&data[0], fileSize);
 		gameFile.close();
 
-		if (!isBlorb && Blorb::hasBlorbExt(filename))
+		// Check if it's a valid Level 9 game
+		byte *startFile = &data[0];
+		Scanner scanner;
+		int offset = scanner.scanner(&data[0], fileSize) < 0;
+		if (offset < 0)
 			continue;
 
-		// Check for known games
-		const GlkDetectionEntry *p = LEVEL9_GAMES;
-		while (p->_gameId && (md5 != p->_md5 || _fileSize != p->_filesize))
-			++p;
-
-		if (!p->_gameId) {
-			const PlainGameDescriptor &desc = LEVEL9_GAME_LIST[0];
-			gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, _fileSize));
-		} else {
-			PlainGameDescriptor gameDesc = findGame(p->_gameId);
-			gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, p->_extra, filename, p->_language));
-		}
+		// Check for the specific game
+		byte *startData = startFile + offset;
+		GameDetection detection(startData, fileSize);
+
+		const gln_game_tableref_t game = detection.gln_gameid_identify_game();
+		if (!game)
+			continue;
+
+		// Found the game, add a detection entry
+		DetectedGame gd = DetectedGame(game->gameId, game->name, Common::UNK_LANG,
+			Common::kPlatformUnknown, game->extra);
+		gd.addExtraEntry("filename", filename);
+		gameList.push_back(gd);
 	}
 
 	return !gameList.empty();
 }
 
 void Level9MetaEngine::detectClashes(Common::StringMap &map) {
-	// No implementation
+	const char *prior_id = nullptr;
+
+	for (const gln_game_table_t *pd = GLN_GAME_TABLE; pd->name; ++pd) {
+		if (prior_id == nullptr || strcmp(pd->gameId, prior_id)) {
+			prior_id = pd->gameId;
+
+			if (map.contains(pd->gameId))
+				error("Duplicate game Id found - %s", pd->gameId);
+			map[pd->gameId] = "";
+		}
+	}
 }
 
 } // End of namespace Level9
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
index 89bd854..ae876b9 100644
--- a/engines/glk/level9/detection.h
+++ b/engines/glk/level9/detection.h
@@ -37,7 +37,9 @@ struct gln_game_table_t {
 	const size_t length;        ///< Datafile length in bytes
 	const byte checksum;        ///< 8-bit checksum, last datafile byte
 	const uint16 crc;           ///< 16-bit CRC, L9cut-internal
-	const char *const name;     ///< Game title and platform
+	const char *const gameId;   ///< Game Id
+	const char *const name;     ///< Game title
+	const char *const extra;    ///< Disk number, platform, etc.
 };
 typedef const gln_game_table_t *gln_game_tableref_t;
 
diff --git a/engines/glk/level9/detection_tables.h b/engines/glk/level9/detection_tables.h
index eb8ae7d..b43deff 100644
--- a/engines/glk/level9/detection_tables.h
+++ b/engines/glk/level9/detection_tables.h
@@ -31,408 +31,382 @@ namespace Glk {
 namespace Level9 {
 
 /**
- * The following game database is obtained from L9cut's l9data_d.h, and
- * lets us find a game's name from its data CRC.  Entries marked "WANTED" in
- * l9data_d.h, and file commentary, have been removed for brevity, and the
- * file has been reformatted (patchlevel data removed).
+ * The following game database is modified for ScummVM based on the data obtained
+ * from L9cut's l9data_d.h, and lets us find a game's name from its data CRC.
+ * Entries marked "WANTED" in l9data_d.h, and file commentary, have been removed for brevity,
+ * and the file has been reformatted (patchlevel data removed).
  *
  * The version of l9data_d.h used is 050 (22 Oct 2002).
  */
 static const gln_game_table_t GLN_GAME_TABLE[] = {
-	{0x5323, 0xb7, 0x8af7, "Adventure Quest (Amstrad CPC/Spectrum)"},
-
-	{0x630e, 0x8d, 0x7d7d, "Dungeon Adventure (Amstrad CPC)"},
-	{0x630e, 0xbe, 0x3374, "Dungeon Adventure (MSX)"},
-
-	{0x5eb9, 0x30, 0xe99a, "Lords of Time (Amstrad CPC)"},
-	{0x5eb9, 0x5d, 0xc098, "Lords of Time (MSX)"},
-	{0x5eb9, 0x6e, 0xc689, "Lords of Time (Spectrum)"},
-
-	{0x5fab, 0x5c, 0xa309, "Snowball (Amstrad CPC)"},
-	{0x5fab, 0x2f, 0x8aa2, "Snowball (MSX)"},
-
-	{0x60c4, 0x28, 0x0154, "Return to Eden (Amstrad CPC/Commodore 64[v1])"},
-	{0x6064, 0x01, 0x5b3c, "Return to Eden (BBC[v1])"},
-	{0x6064, 0x95, 0x510c, "Return to Eden (Commodore 64[v2])"},
-	{0x6064, 0xda, 0xe610, "Return to Eden (Commodore 64[v2] *corrupt*)"},
-	{0x6064, 0xbd, 0x73ec, "Return to Eden (Atari *corrupt*)"},
-	{0x6047, 0x6c, 0x17ab, "Return to Eden (BBC[v2])"},
-	{0x5ca1, 0x33, 0x1c43, "Return to Eden (Spectrum[v1])"},
-	{0x5cb7, 0x64, 0x0790, "Return to Eden (Spectrum[v2])"},
-	{0x5cb7, 0xfe, 0x3533, "Return to Eden (MSX)"},
-
-	{0x34b3, 0x20, 0xccda, "Erik the Viking (BBC/Commodore 64)"},
-	{0x34b3, 0x53, 0x8f00, "Erik the Viking (Spectrum)"},
-	{0x34b3, 0xc7, 0x9058, "Erik the Viking (Amstrad CPC)"},
-
-	{0x63be, 0xd6, 0xcf5d, "Emerald Isle (Atari/Commodore 64/Amstrad CPC/Spectrum)"},
-	{0x63be, 0x0a, 0x21ed, "Emerald Isle (MSX *corrupt*)"},
-	{0x378c, 0x8d, 0x3a21, "Emerald Isle (BBC)"},
-
-	{0x506c, 0xf0, 0xba72, "Red Moon (BBC/Commodore 64/Amstrad CPC/MSX)"},
-	{0x505d, 0x32, 0x2dcf, "Red Moon (Spectrum)"},
-
-	{0x772b, 0xcd, 0xa503, "Worm in Paradise (Spectrum 128)"},
-	{0x546c, 0xb7, 0x9420, "Worm in Paradise (Spectrum 48)"},
-	{0x6d84, 0xf9, 0x49ae, "Worm in Paradise (Commodore 64 *corrupt*)"},
-	{0x6d84, 0xc8, 0x943f, "Worm in Paradise (Commodore 64 *fixed*)"},
-	{0x6030, 0x47, 0x46ad, "Worm in Paradise (Amstrad CPC)"},
-	{0x5828, 0xbd, 0xe7cb, "Worm in Paradise (BBC)"},
-
-	{0x7410, 0x5e, 0x60be, "Price of Magik (Spectrum 128)"},
-	{0x5aa4, 0xc1, 0x10a0, "Price of Magik (Spectrum 48[v1])"},
-	{0x5aa4, 0xc1, 0xeda4, "Price of Magik (Spectrum 48[v2])"},
-	{0x6fc6, 0x14, 0xf9b6, "Price of Magik (Commodore 64)"},
-	{0x5aa4, 0xc1, 0xbbf4, "Price of Magik (Amstrad CPC)"},
-	{0x5671, 0xbc, 0xff35, "Price of Magik (BBC)"},
-
-	{0x76f4, 0x5e, 0x1fe5, "Colossal Adventure /JoD (Amiga/PC)"},
-	{0x76f4, 0x5a, 0xcf4b, "Colossal Adventure /JoD (ST)"},
-	{0x6e60, 0x83, 0x18e0, "Adventure Quest /JoD (Amiga/PC)"},
-	{0x6e5c, 0xf6, 0xd356, "Adventure Quest /JoD (ST)"},
-	{0x6f0c, 0x95, 0x1f64, "Dungeon Adventure /JoD (Amiga/PC/ST)"},
-
-	{0x6f70, 0x40, 0xbd91, "Colossal Adventure /JoD (MSX)"},
-
-	{0x6f6e, 0x78, 0x28cd, "Colossal Adventure /JoD (Spectrum 128)"},
-	{0x6970, 0xd6, 0xa820, "Adventure Quest /JoD (Spectrum 128)"},
-	{0x6de8, 0x4c, 0xd795, "Dungeon Adventure /JoD (Spectrum 128)"},
-
-	{0x6f4d, 0xcb, 0xe8f2, "Colossal Adventure /JoD (Amstrad CPC128[v1]/Spectrum +3)"},
-	{0x6f6a, 0xa5, 0x8dd2, "Colossal Adventure /JoD (Amstrad CPC128[v2])"},
-	{0x6968, 0x32, 0x0c01, "Adventure Quest /JoD (Amstrad CPC128/Spectrum +3)"},
-	{0x6dc0, 0x63, 0x5d95, "Dungeon Adventure /JoD (Amstrad CPC128/Spectrum +3)"},
-
-	{0x5e31, 0x7c, 0xaa54, "Colossal Adventure /JoD (Amstrad CPC64)"},
-	{0x5b50, 0x66, 0x1800, "Adventure Quest /JoD (Amstrad CPC64)"},
-	{0x58a6, 0x24, 0xb50f, "Dungeon Adventure /JoD (Amstrad CPC64)"},
-
-	{0x6c8e, 0xb6, 0x9be3, "Colossal Adventure /JoD (Commodore 64)"},
-	{0x63b6, 0x2e, 0xef38, "Adventure Quest /JoD (Commodore 64)"},
-	{0x6bd2, 0x65, 0xa41f, "Dungeon Adventure /JoD (Commodore 64)"},
-
-	{0x5b16, 0x3b, 0xe2aa, "Colossal Adventure /JoD (Atari)"},
-	{0x5b58, 0x50, 0x332e, "Adventure Quest /JoD (Atari)"},
-	{0x593a, 0x80, 0x7a34, "Dungeon Adventure /JoD (Atari)"},
-
-	{0x5a8e, 0xf2, 0x7cca, "Colossal Adventure /JoD (Spectrum 48)"},
-	{0x5ace, 0x11, 0xdc12, "Adventure Quest /JoD (Spectrum 48)"},
-	{0x58a3, 0x38, 0x8ce4, "Dungeon Adventure /JoD (Spectrum 48)"},
-
-	{0x7b31, 0x6e, 0x2e2b, "Snowball /SD (Amiga/ST)"},
-	{0x7d16, 0xe6, 0x5438, "Return to Eden /SD (Amiga/ST)"},
-	{0x7cd9, 0x0c, 0x4df1, "Worm in Paradise /SD (Amiga/ST)"},
-
-	{0x7b2f, 0x70, 0x6955, "Snowball /SD (Mac/PC/Spectrum 128)"},
-	{0x7b2f, 0x70, 0x6f6c, "Snowball /SD (Amstrad CPC/Spectrum +3)"},
-	{0x7d14, 0xe8, 0xfbab, "Return to Eden /SD (PC)"},
-	{0x7cff, 0xf8, 0x6044, "Return to Eden /SD (Amstrad CPC/Spectrum +3)"},
-	{0x7cf8, 0x24, 0x9c1c, "Return to Eden /SD (Mac)"},
-	{0x7c55, 0x18, 0xdaee, "Return to Eden /SD (Spectrum 128)"},
-	{0x7cd7, 0x0e, 0x4feb, "Worm in Paradise /SD (Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3)"},
-
-	{0x7363, 0x65, 0xa0ab, "Snowball /SD (Commodore 64)"},
-	{0x772f, 0xca, 0x8602, "Return to Eden /SD (Commodore 64)"},
-	{0x788d, 0x72, 0x888a, "Worm in Paradise /SD (Commodore 64)"},
-
-	{0x6bf8, 0x3f, 0xc9f7, "Snowball /SD (Atari)"},
-	{0x60f7, 0x68, 0xc2bc, "Return to Eden /SD (Atari)"},
-	{0x6161, 0xf3, 0xe6d7, "Worm in Paradise /SD (Atari)"},
-
-	{0x67a3, 0x9d, 0x1d05, "Snowball /SD (Apple ][)"},
-	{0x639c, 0x8b, 0x06e2, "Return to Eden /SD (Apple ][)"},
-	{0x60dd, 0xf2, 0x5bb8, "Worm in Paradise /SD (Apple ][)"},
-
-	{0x6541, 0x02, 0x2e6c, "Snowball /SD (Spectrum 48)"},
-	{0x5f43, 0xca, 0x828c, "Return to Eden /SD (Spectrum 48)"},
-	{0x5ebb, 0xf1, 0x4dec, "Worm in Paradise /SD (Spectrum 48)"},
-
-	{0x8333, 0xb7, 0xe2ac, "Adrian Mole I, pt. 1 (Commodore 64)"},
-	{0x844d, 0x50, 0x5353, "Adrian Mole I, pt. 2 (Commodore 64)"},
-	{0x8251, 0x5f, 0x862a, "Adrian Mole I, pt. 3 (Commodore 64)"},
-	{0x7a78, 0x5e, 0x6ea3, "Adrian Mole I, pt. 4 (Commodore 64)"},
-
-	{0x7c6f, 0x0f, 0xba24, "Adrian Mole I, pt. 1 (Amstrad CPC)"},
-
-	{0x72fa, 0x8b, 0x6f12, "Adrian Mole I, pt. 1 (Spectrum)"},
-	{0x738e, 0x5b, 0x7e3d, "Adrian Mole I, pt. 2 (Spectrum)"},
-	{0x7375, 0xe5, 0x3f3e, "Adrian Mole I, pt. 3 (Spectrum)"},
-	{0x78d5, 0xe3, 0xcd7d, "Adrian Mole I, pt. 4 (Spectrum)"},
-
-	{0x3a31, 0xe5, 0x0bdb, "Adrian Mole I, pt. 1 (BBC)"},
-	{0x37f1, 0x77, 0xd231, "Adrian Mole I, pt. 2 (BBC)"},
-	{0x3900, 0x1c, 0x5d9a, "Adrian Mole I, pt. 3 (BBC)"},
-	{0x3910, 0xac, 0x07f9, "Adrian Mole I, pt. 4 (BBC)"},
-	{0x3ad6, 0xa7, 0x95d2, "Adrian Mole I, pt. 5 (BBC)"},
-	{0x38a5, 0x0f, 0xdefc, "Adrian Mole I, pt. 6 (BBC)"},
-	{0x361e, 0x7e, 0xfd9f, "Adrian Mole I, pt. 7 (BBC)"},
-	{0x3934, 0x75, 0xe141, "Adrian Mole I, pt. 8 (BBC)"},
-	{0x3511, 0xcc, 0xd829, "Adrian Mole I, pt. 9 (BBC)"},
-	{0x38dd, 0x31, 0x2534, "Adrian Mole I, pt. 10 (BBC)"},
-	{0x39c0, 0x44, 0x89df, "Adrian Mole I, pt. 11 (BBC)"},
-	{0x3a12, 0x8f, 0xc2bd, "Adrian Mole I, pt. 12 (BBC)"},
-
-	{0x7931, 0xb9, 0xc51b, "Adrian Mole II, pt. 1 (Commodore 64/Amstrad CPC)"},
-	{0x7cdf, 0xa5, 0x43e3, "Adrian Mole II, pt. 2 (Commodore 64/Amstrad CPC)"},
-	{0x7a0c, 0x97, 0x4bea, "Adrian Mole II, pt. 3 (Commodore 64/Amstrad CPC)"},
-	{0x7883, 0xe2, 0xee0e, "Adrian Mole II, pt. 4 (Commodore 64/Amstrad CPC)"},
-
-	{0x6841, 0x4a, 0x94e7, "Adrian Mole II, pt. 1 (Spectrum)"},
-	{0x6bc0, 0x62, 0xab3d, "Adrian Mole II, pt. 2 (Spectrum)"},
-	{0x692c, 0x21, 0x2015, "Adrian Mole II, pt. 3 (Spectrum)"},
-	{0x670a, 0x94, 0xa2a6, "Adrian Mole II, pt. 4 (Spectrum)"},
-
-	{0x593a, 0xaf, 0x30e9, "Adrian Mole II, pt. 1 (BBC)"},
-	{0x57e6, 0x8a, 0xc41a, "Adrian Mole II, pt. 2 (BBC)"},
-	{0x5819, 0xcd, 0x1ba0, "Adrian Mole II, pt. 3 (BBC)"},
-	{0x579b, 0xad, 0xa723, "Adrian Mole II, pt. 4 (BBC)"},
-
-	{0x765d, 0xcd, 0xfc02, "The Archers, pt. 1 (Commodore 64)"},
-	{0x6e58, 0x07, 0xbffc, "The Archers, pt. 2 (Commodore 64)"},
-	{0x7e98, 0x6a, 0x95e5, "The Archers, pt. 3 (Commodore 64)"},
-	{0x81e2, 0xd5, 0xb278, "The Archers, pt. 4 (Commodore 64)"},
-
-	{0x6ce5, 0x58, 0x46de, "The Archers, pt. 1 (Spectrum)"},
-	{0x68da, 0xc1, 0x3b8e, "The Archers, pt. 2 (Spectrum)"},
-	{0x6c67, 0x9a, 0x9a6a, "The Archers, pt. 3 (Spectrum)"},
-	{0x6d91, 0xb9, 0x12a7, "The Archers, pt. 4 (Spectrum)"},
-
-	{0x5834, 0x42, 0xcc9d, "The Archers, pt. 1 (BBC)"},
-	{0x56dd, 0x51, 0xe582, "The Archers, pt. 2 (BBC)"},
-	{0x5801, 0x53, 0xf2ef, "The Archers, pt. 3 (BBC)"},
-	{0x54a4, 0x01, 0xc0ab, "The Archers, pt. 4 (BBC)"},
-
-	{0x579e, 0x97, 0x9faa, "Lords of Time /T&M GD (BBC)"},
-	{0x5500, 0x50, 0xca75, "Red Moon /T&M GD (BBC)"},
-	{0x579a, 0x2a, 0x9373, "Price of Magik /T&M GD (BBC)"},
-
-	{0x4fd2, 0x9d, 0x799a, "Lancelot, pt. 1 GD (BBC)"},
-	{0x4dac, 0xa8, 0x86ed, "Lancelot, pt. 2 GD (BBC)"},
-	{0x4f96, 0x22, 0x30f8, "Lancelot, pt. 3 GD (BBC)"},
-
-	{0x55ce, 0xa1, 0xba12, "Scapeghost, pt. 1 GD (BBC)"},
-	{0x54a6, 0xa9, 0xc9f3, "Scapeghost, pt. 2 GD (BBC)"},
-	{0x51bc, 0xe3, 0x89c3, "Scapeghost, pt. 3 GD (BBC)"},
-
-	{0x46ec, 0x64, 0x2300, "Knight Orc, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x6140, 0x18, 0x4f66, "Knight Orc, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x640e, 0xc1, 0xfc69, "Knight Orc, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x5ff0, 0xf8, 0x3a13, "Gnome Ranger, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x6024, 0x01, 0xaaa9, "Gnome Ranger, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x6036, 0x3d, 0x6c6c, "Gnome Ranger, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x69fe, 0x56, 0xecfb, "Lords of Time /T&M GD (Amstrad CPC/Spectrum +3)"},
-	{0x6888, 0x8d, 0x7f6a, "Red Moon /T&M GD (Amstrad CPC/Spectrum +3)"},
-	{0x5a50, 0xa9, 0xa5fa, "Price of Magik /T&M GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x5c7a, 0x44, 0x460e, "Lancelot, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x53a2, 0x1e, 0x2fae, "Lancelot, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x5914, 0x22, 0x4a31, "Lancelot, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x5a38, 0xf7, 0x876e, "Ingrid's Back, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x531a, 0xed, 0xcf3f, "Ingrid's Back, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x57e4, 0x19, 0xb354, "Ingrid's Back, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x5cbc, 0xa5, 0x0dbe, "Scapeghost, pt. 1 GD (Amstrad CPC/Spectrum +3)"},
-	{0x5932, 0x4e, 0xb2f5, "Scapeghost, pt. 2 GD (Amstrad CPC/Spectrum +3)"},
-	{0x5860, 0x95, 0x3227, "Scapeghost, pt. 3 GD (Amstrad CPC/Spectrum +3)"},
-
-	{0x74e0, 0x92, 0x885e, "Knight Orc, pt. 1 GD (Spectrum 128)"},
-	{0x6dbc, 0x97, 0x6f55, "Knight Orc, pt. 2 GD (Spectrum 128)"},
-	{0x7402, 0x07, 0x385f, "Knight Orc, pt. 3 GD (Spectrum 128)"},
-
-	{0x52aa, 0xdf, 0x7b5b, "Gnome Ranger, pt. 1 GD (Spectrum 128)"},
-	{0x6ffa, 0xdb, 0xdde2, "Gnome Ranger, pt. 2 GD (Spectrum 128)"},
-	{0x723a, 0x69, 0x039b, "Gnome Ranger, pt. 3 GD (Spectrum 128)"},
-
-	{0x6f1e, 0xda, 0x2ce0, "Lords of Time /T&M GD (Spectrum 128)"},
-	{0x6da0, 0xb8, 0x3802, "Red Moon /T&M GD (Spectrum 128)"},
-	{0x6108, 0xdd, 0xefe7, "Price of Magik /T&M GD (Spectrum 128)"},
-
-	{0x768c, 0xe8, 0x8fc6, "Lancelot, pt. 1 GD (Spectrum 128)"},
-	{0x76b0, 0x1d, 0x0fcd, "Lancelot, pt. 2 GD (Spectrum 128)"},
-	{0x765e, 0x4f, 0x3b73, "Lancelot, pt. 3 GD (Spectrum 128)"},
-
-	{0x76a0, 0x3a, 0xb803, "Ingrid's Back, pt. 1 GD (Spectrum 128)"},
-	{0x7674, 0x0b, 0xe92f, "Ingrid's Back, pt. 2 GD (Spectrum 128)"},
-	{0x765e, 0xba, 0x086d, "Ingrid's Back, pt. 3 GD (Spectrum 128)"},
-
-	{0x762e, 0x82, 0x8848, "Scapeghost, pt. 1 GD (Spectrum 128)"},
-	{0x5bd6, 0x35, 0x79ef, "Scapeghost, pt. 2 GD (Spectrum 128)"},
-	{0x6fa8, 0xa4, 0x62c2, "Scapeghost, pt. 3 GD (Spectrum 128)"},
-
-	{0xbb93, 0x36, 0x6a05, "Knight Orc, pt. 1 (Amiga)"},
-	{0xbb6e, 0xad, 0x4d40, "Knight Orc, pt. 1 (ST)"},
-	{0xc58e, 0x4a, 0x4e9d, "Knight Orc, pt. 2 (Amiga/ST)"},
-	{0xcb9a, 0x0f, 0x0804, "Knight Orc, pt. 3 (Amiga/ST)"},
-
-	{0xbb6e, 0xa6, 0x9753, "Knight Orc, pt. 1 (PC)"},
-	{0xc58e, 0x43, 0xe9ce, "Knight Orc, pt. 2 (PC)"},
-	{0xcb9a, 0x08, 0x6c36, "Knight Orc, pt. 3 (PC)"},
-
-	{0x898a, 0x43, 0xfc8b, "Knight Orc, pt. 1 (Apple ][)"},
-	{0x8b9f, 0x61, 0x7288, "Knight Orc, pt. 2 (Apple ][)"},
-	{0x8af9, 0x61, 0x7542, "Knight Orc, pt. 3 (Apple ][)"},
-
-	{0x8970, 0x6b, 0x3c7b, "Knight Orc, pt. 1 (Commodore 64 Gfx)"},
-	{0x8b90, 0x4e, 0x098c, "Knight Orc, pt. 2 (Commodore 64 Gfx)"},
-	{0x8aea, 0x4e, 0xca54, "Knight Orc, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x86d0, 0xb7, 0xadbd, "Knight Orc, pt. 1 (Spectrum 48)"},
-	{0x8885, 0x22, 0xe293, "Knight Orc, pt. 2 (Spectrum 48)"},
-	{0x87e5, 0x0e, 0xdc33, "Knight Orc, pt. 3 (Spectrum 48)"},
-
-	{0xb1a9, 0x80, 0x5fb7, "Gnome Ranger, pt. 1 (Amiga/ST)"},
-	{0xab9d, 0x31, 0xbe6d, "Gnome Ranger, pt. 2 (Amiga/ST)"},
-	{0xae28, 0x87, 0xb6b6, "Gnome Ranger, pt. 3 (Amiga/ST)"},
-
-	{0xb0ec, 0xc2, 0x0053, "Gnome Ranger, pt. 1 (ST[v1])"},
-	{0xaf82, 0x83, 0x19f7, "Gnome Ranger, pt. 2 (ST[v1])"},
-
-	{0xb1aa, 0xad, 0xaf47, "Gnome Ranger, pt. 1 (PC)"},
-	{0xb19e, 0x92, 0x8f96, "Gnome Ranger, pt. 1 (ST[v2])"},
-	{0xab8b, 0xbf, 0x31e6, "Gnome Ranger, pt. 2 (PC/ST[v2])"},
-	{0xae16, 0x81, 0x8741, "Gnome Ranger, pt. 3 (PC/ST[v2])"},
-
-	{0xad41, 0xa8, 0x42c5, "Gnome Ranger, pt. 1 (Commodore 64 TO)"},
-	{0xa735, 0xf7, 0x2e08, "Gnome Ranger, pt. 2 (Commodore 64 TO)"},
-	{0xa9c0, 0x9e, 0x0d70, "Gnome Ranger, pt. 3 (Commodore 64 TO)"},
-
-	{0x908e, 0x0d, 0x58a7, "Gnome Ranger, pt. 1 (Commodore 64 Gfx)"},
-	{0x8f6f, 0x0a, 0x411a, "Gnome Ranger, pt. 2 (Commodore 64 Gfx)"},
-	{0x9060, 0xbb, 0xe75d, "Gnome Ranger, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x8aab, 0xc0, 0xde5f, "Gnome Ranger, pt. 1 (Spectrum 48)"},
-	{0x8ac8, 0x9a, 0xc89b, "Gnome Ranger, pt. 2 (Spectrum 48)"},
-	{0x8a93, 0x4f, 0x10cc, "Gnome Ranger, pt. 3 (Spectrum 48)"},
-
-	{0xb57c, 0x44, 0x7779, "Lords of Time /T&M (PC)"},
-	{0xa69e, 0x6c, 0xb268, "Red Moon /T&M (PC)"},
-	{0xbac7, 0x7f, 0xddb2, "Price of Magik /T&M (PC)"},
-
-	{0xb579, 0x89, 0x3e89, "Lords of Time /T&M (ST)"},
-	{0xa698, 0x41, 0xcaca, "Red Moon /T&M (ST)"},
-	{0xbac4, 0x80, 0xa750, "Price of Magik /T&M (ST)"},
-
-	{0xb576, 0x2a, 0x7239, "Lords of Time /T&M (Amiga)"},
-	{0xa692, 0xd1, 0x6a99, "Red Moon /T&M (Amiga)"},
-	{0xbaca, 0x3a, 0x221b, "Price of Magik /T&M (Amiga)"},
-
-	{0xb563, 0x6a, 0x0c5c, "Lords of Time /T&M (Mac)"},
-	{0xa67c, 0xb8, 0xff41, "Red Moon /T&M (Mac)"},
-	{0xbab2, 0x87, 0x09f5, "Price of Magik /T&M (Mac)"},
-
-	{0xb38c, 0x37, 0x9f8e, "Lords of Time /T&M (Commodore 64 TO)"},
-	{0xa4e2, 0xa6, 0x016d, "Red Moon /T&M (Commodore 64 TO)"},
-	{0xb451, 0xa8, 0x2682, "Price of Magik /T&M (Commodore 64 TO)"},
-
-	{0x9070, 0x43, 0x45d4, "Lords of Time /T&M (Commodore 64 Gfx)"},
-	{0x903f, 0x6b, 0x603e, "Red Moon /T&M (Commodore 64 Gfx)"},
-	{0x8f51, 0xb2, 0x6c9a, "Price of Magik /T&M (Commodore 64 Gfx)"},
-
-	{0x8950, 0xa1, 0xbb16, "Lords of Time /T&M (Spectrum 48)"},
-	{0x8813, 0x11, 0x22de, "Red Moon /T&M (Spectrum 48)"},
-	{0x8a60, 0x2a, 0x29ed, "Price of Magik /T&M (Spectrum 48)"},
-
-	{0xb260, 0xe5, 0xc5b2, "Lords of Time /T&M (PC/ST *USA*)"},
-	{0xa3a4, 0xdf, 0x6732, "Red Moon /T&M (PC/ST *USA*)"},
-	{0xb7a0, 0x7e, 0x2226, "Price of Magik /T&M (PC/ST *USA*)"},
-
-	{0xb257, 0xf8, 0xfbd5, "Lords of Time /T&M (Amiga *USA*)"},
-	{0xa398, 0x82, 0xd031, "Red Moon /T&M (Amiga *USA*)"},
-	{0xb797, 0x1f, 0x84a9, "Price of Magik /T&M (Amiga *USA*)"},
-
-	{0x8d78, 0x3a, 0xba6e, "Lords of Time /T&M (Commodore 64 Gfx *USA*)"},
-	{0x8d56, 0xd3, 0x146a, "Red Moon /T&M (Commodore 64 Gfx *USA*)"},
-	{0x8c46, 0xf0, 0xcaf6, "Price of Magik /T&M (Commodore 64 Gfx *USA*)"},
-
-	{0xc0cf, 0x4e, 0xb7fa, "Lancelot, pt. 1 (Amiga/PC/ST)"},
-	{0xd5e9, 0x6a, 0x4192, "Lancelot, pt. 2 (Amiga/PC/ST)"},
-	{0xbb8f, 0x1a, 0x7487, "Lancelot, pt. 3 (Amiga/PC/ST)"},
-
-	{0xc0bd, 0x57, 0x6ef1, "Lancelot, pt. 1 (Mac)"},
-	{0xd5d7, 0x99, 0x770b, "Lancelot, pt. 2 (Mac)"},
-	{0xbb7d, 0x17, 0xbc42, "Lancelot, pt. 3 (Mac)"},
-
-	{0xb4c9, 0x94, 0xd784, "Lancelot, pt. 1 (Commodore 64 TO)"},
-	{0xb729, 0x51, 0x8ee5, "Lancelot, pt. 2 (Commodore 64 TO)"},
-	{0xb702, 0xe4, 0x1809, "Lancelot, pt. 3 (Commodore 64 TO)"},
-
-	{0x8feb, 0xba, 0xa800, "Lancelot, pt. 1 (Commodore 64 Gfx)"},
-	{0x8f6b, 0xfa, 0x0f7e, "Lancelot, pt. 2 (Commodore 64 Gfx)"},
-	{0x8f71, 0x2f, 0x0ddc, "Lancelot, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x8ade, 0xf2, 0xfffb, "Lancelot, pt. 1 (Spectrum 48)"},
-	{0x8b0e, 0xfb, 0x0bab, "Lancelot, pt. 2 (Spectrum 48)"},
-	{0x8ab3, 0xc1, 0xcb62, "Lancelot, pt. 3 (Spectrum 48)"},
-
-	{0xbba4, 0x94, 0x0871, "Lancelot, pt. 1 (Amiga/PC *USA*)"},
-	{0xd0c0, 0x56, 0x8c48, "Lancelot, pt. 2 (Amiga/PC *USA*)"},
-	{0xb6ac, 0xc6, 0xaea0, "Lancelot, pt. 3 (Amiga/PC *USA*)"},
-
-	{0x8afc, 0x07, 0x8321, "Lancelot, pt. 1 (Commodore 64 Gfx *USA*)"},
-	{0x8aec, 0x13, 0x6791, "Lancelot, pt. 2 (Commodore 64 Gfx *USA*)"},
-	{0x8aba, 0x0d, 0x5602, "Lancelot, pt. 3 (Commodore 64 Gfx *USA*)"},
-
-	{0xd19b, 0xad, 0x306d, "Ingrid's Back, pt. 1 (PC)"},
-	{0xc5a5, 0xfe, 0x3c98, "Ingrid's Back, pt. 2 (PC)"},
-	{0xd7ae, 0x9e, 0x1878, "Ingrid's Back, pt. 3 (PC)"},
-
-	{0xd188, 0x13, 0xdc60, "Ingrid's Back, pt. 1 (Amiga)"},
-	{0xc594, 0x03, 0xea95, "Ingrid's Back, pt. 2 (Amiga)"},
-	{0xd79f, 0xb5, 0x1661, "Ingrid's Back, pt. 3 (Amiga)"},
-
-	{0xd183, 0x83, 0xef72, "Ingrid's Back, pt. 1 (ST)"},
-	{0xc58f, 0x65, 0xf337, "Ingrid's Back, pt. 2 (ST)"},
-	{0xd79a, 0x57, 0x49c5, "Ingrid's Back, pt. 3 (ST)"},
-
-	{0xb770, 0x03, 0x9a03, "Ingrid's Back, pt. 1 (Commodore 64 TO)"},
-	{0xb741, 0xb6, 0x2aa5, "Ingrid's Back, pt. 2 (Commodore 64 TO)"},
-	{0xb791, 0xa1, 0xd065, "Ingrid's Back, pt. 3 (Commodore 64 TO)"},
-
-	{0x9089, 0xce, 0xc5e2, "Ingrid's Back, pt. 1 (Commodore 64 Gfx)"},
-	{0x908d, 0x80, 0x30c7, "Ingrid's Back, pt. 2 (Commodore 64 Gfx)"},
-	{0x909e, 0x9f, 0xdecc, "Ingrid's Back, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x8ab7, 0x68, 0xee57, "Ingrid's Back, pt. 1 (Spectrum 48)"},
-	{0x8b1e, 0x84, 0x2538, "Ingrid's Back, pt. 2 (Spectrum 48)"},
-	{0x8b1c, 0xa8, 0x9262, "Ingrid's Back, pt. 3 (Spectrum 48)"},
-
-	{0xbeab, 0x2d, 0x94d9, "Scapeghost, pt. 1 (Amiga)"},
-	{0xc132, 0x14, 0x7adc, "Scapeghost, pt. 1 (Amiga *bak*)"},
-	{0xbe94, 0xcc, 0x04b8, "Scapeghost, pt. 1 (PC/ST)"},
-	{0x99bd, 0x65, 0x032e, "Scapeghost, pt. 2 (Amiga/PC/ST)"},
-	{0xbcb6, 0x7a, 0x7d4f, "Scapeghost, pt. 3 (Amiga/PC/ST)"},
-
-	{0x9058, 0xcf, 0x9748, "Scapeghost, pt. 1 (Commodore 64 Gfx)"},
-	{0x8f43, 0xc9, 0xeefd, "Scapeghost, pt. 2 (Commodore 64 Gfx)"},
-	{0x90ac, 0x68, 0xb4a8, "Scapeghost, pt. 3 (Commodore 64 Gfx)"},
-
-	{0x8a21, 0xf4, 0xd9e4, "Scapeghost, pt. 1 (Spectrum 48)"},
-	{0x8a12, 0xe3, 0xc2ff, "Scapeghost, pt. 2 (Spectrum 48)"},
-	{0x8a16, 0xcc, 0x4f3b, "Scapeghost, pt. 3 (Spectrum 48)"},
-
-	{0x3ebb, 0x00, 0xf6dc, "Champion of the Raj (English) 1/2 GD (Amiga)"},
-	{0x0fd8, 0x00, 0xf250, "Champion of the Raj (English) 2/2 GD (Amiga)"},
-
-	{0x3e8f, 0x00, 0x5599, "Champion of the Raj (English) 1/2 GD (ST)"},
-
-	{0x3e4f, 0x00, 0xb202, "Champion of the Raj (English) 1/2 GD (PC)"},
-	{0x14a3, 0x00, 0xa288, "Champion of the Raj (English) 2/2 GD (PC)"},
-
-	{0x1929, 0x00, 0xd4b2, "Champion of the Raj (demo), 1/2 GD (ST)"},
-	{0x40e0, 0x02, 0x080d, "Champion of the Raj (demo), 2/2 GD (ST)"},
-
-	{0x4872, 0x00, 0x9515, "Champion of the Raj (German) 1/2 GD (Amiga)"},
-	{0x11f5, 0x00, 0xbf39, "Champion of the Raj (German) 2/2 GD (Amiga)"},
-
-	{0x4846, 0x00, 0xd9c1, "Champion of the Raj (German) 1/2 GD (ST)"},
-	{0x11f5, 0x00, 0x7aa4, "Champion of the Raj (German) 2/2 GD (ST)"},
-
-	{0x110f, 0x00, 0x4b57, "Champion of the Raj (French) 2/2 GD (ST)"},
-
-	{0x0000, 0x00, 0x0000, nullptr}
+	{0x8333, 0xb7, 0xe2ac, "adrianmole1", "Adrian Mole I", "pt. 1/Commodore 64"},
+	{0x844d, 0x50, 0x5353, "adrianmole1", "Adrian Mole I", "pt. 2/Commodore 64"},
+	{0x8251, 0x5f, 0x862a, "adrianmole1", "Adrian Mole I", "pt. 3/Commodore 64"},
+	{0x7a78, 0x5e, 0x6ea3, "adrianmole1", "Adrian Mole I", "pt. 4/Commodore 64"},
+
+	{0x7c6f, 0x0f, 0xba24, "adrianmole1", "Adrian Mole I", "pt. 1/Amstrad CPC"},
+
+	{0x72fa, 0x8b, 0x6f12, "adrianmole1", "Adrian Mole I", "pt. 1/Spectrum"},
+	{0x738e, 0x5b, 0x7e3d, "adrianmole1", "Adrian Mole I", "pt. 2/Spectrum"},
+	{0x7375, 0xe5, 0x3f3e, "adrianmole1", "Adrian Mole I", "pt. 3/Spectrum"},
+	{0x78d5, 0xe3, 0xcd7d, "adrianmole1", "Adrian Mole I", "pt. 4/Spectrum"},
+
+	{0x3a31, 0xe5, 0x0bdb, "adrianmole1", "Adrian Mole I", "pt. 1/BBC"},
+	{0x37f1, 0x77, 0xd231, "adrianmole1", "Adrian Mole I", "pt. 2/BBC"},
+	{0x3900, 0x1c, 0x5d9a, "adrianmole1", "Adrian Mole I", "pt. 3/BBC"},
+	{0x3910, 0xac, 0x07f9, "adrianmole1", "Adrian Mole I", "pt. 4/BBC"},
+	{0x3ad6, 0xa7, 0x95d2, "adrianmole1", "Adrian Mole I", "pt. 5/BBC"},
+	{0x38a5, 0x0f, 0xdefc, "adrianmole1", "Adrian Mole I", "pt. 6/BBC"},
+	{0x361e, 0x7e, 0xfd9f, "adrianmole1", "Adrian Mole I", "pt. 7/BBC"},
+	{0x3934, 0x75, 0xe141, "adrianmole1", "Adrian Mole I", "pt. 8/BBC"},
+	{0x3511, 0xcc, 0xd829, "adrianmole1", "Adrian Mole I", "pt. 9/BBC"},
+	{0x38dd, 0x31, 0x2534, "adrianmole1", "Adrian Mole I", "pt. 10/BBC"},
+	{0x39c0, 0x44, 0x89df, "adrianmole1", "Adrian Mole I", "pt. 11/BBC"},
+	{0x3a12, 0x8f, 0xc2bd, "adrianmole1", "Adrian Mole I", "pt. 12/BBC"},
+
+	{0x7931, 0xb9, 0xc51b, "adrianmole2", "Adrian Mole II", "pt. 1/Commodore 64/Amstrad CPC"},
+	{0x7cdf, 0xa5, 0x43e3, "adrianmole2", "Adrian Mole II", "pt. 2/Commodore 64/Amstrad CPC"},
+	{0x7a0c, 0x97, 0x4bea, "adrianmole2", "Adrian Mole II", "pt. 3/Commodore 64/Amstrad CPC"},
+	{0x7883, 0xe2, 0xee0e, "adrianmole2", "Adrian Mole II", "pt. 4/Commodore 64/Amstrad CPC"},
+
+	{0x6841, 0x4a, 0x94e7, "adrianmole2", "Adrian Mole II", "pt. 1/Spectrum"},
+	{0x6bc0, 0x62, 0xab3d, "adrianmole2", "Adrian Mole II", "pt. 2/Spectrum"},
+	{0x692c, 0x21, 0x2015, "adrianmole2", "Adrian Mole II", "pt. 3/Spectrum"},
+	{0x670a, 0x94, 0xa2a6, "adrianmole2", "Adrian Mole II", "pt. 4/Spectrum"},
+
+	{0x593a, 0xaf, 0x30e9, "adrianmole2", "Adrian Mole II", "pt. 1/BBC"},
+	{0x57e6, 0x8a, 0xc41a, "adrianmole2", "Adrian Mole II", "pt. 2/BBC"},
+	{0x5819, 0xcd, 0x1ba0, "adrianmole2", "Adrian Mole II", "pt. 3/BBC"},
+	{0x579b, 0xad, 0xa723, "adrianmole2", "Adrian Mole II", "pt. 4/BBC"},
+
+	{0x765d, 0xcd, 0xfc02, "thearchers", "The Archers", "pt. 1/Commodore 64"},
+	{0x6e58, 0x07, 0xbffc, "thearchers", "The Archers", "pt. 2/Commodore 64"},
+	{0x7e98, 0x6a, 0x95e5, "thearchers", "The Archers", "pt. 3/Commodore 64"},
+	{0x81e2, 0xd5, 0xb278, "thearchers", "The Archers", "pt. 4/Commodore 64"},
+
+	{0x6ce5, 0x58, 0x46de, "thearchers", "The Archers", "pt. 1/Spectrum"},
+	{0x68da, 0xc1, 0x3b8e, "thearchers", "The Archers", "pt. 2/Spectrum"},
+	{0x6c67, 0x9a, 0x9a6a, "thearchers", "The Archers", "pt. 3/Spectrum"},
+	{0x6d91, 0xb9, 0x12a7, "thearchers", "The Archers", "pt. 4/Spectrum"},
+
+	{0x5834, 0x42, 0xcc9d, "thearchers", "The Archers", "pt. 1/BBC"},
+	{0x56dd, 0x51, 0xe582, "thearchers", "The Archers", "pt. 2/BBC"},
+	{0x5801, 0x53, 0xf2ef, "thearchers", "The Archers", "pt. 3/BBC"},
+	{0x54a4, 0x01, 0xc0ab, "thearchers", "The Archers", "pt. 4/BBC"},
+
+	{0x5323, 0xb7, 0x8af7, "adventurequest", "Adventure Quest", "Amstrad CPC/Spectrum"},
+	{0x6e60, 0x83, 0x18e0, "adventurequest", "Adventure Quest /JoD", "Amiga/PC"},
+	{0x6e5c, 0xf6, 0xd356, "adventurequest", "Adventure Quest /JoD", "ST"},
+	{0x6970, 0xd6, 0xa820, "adventurequest", "Adventure Quest /JoD", "Spectrum 128"},
+	{0x6968, 0x32, 0x0c01, "adventurequest", "Adventure Quest /JoD", "Amstrad CPC128/Spectrum +3"},
+	{0x5b50, 0x66, 0x1800, "adventurequest", "Adventure Quest /JoD", "Amstrad CPC64"},
+	{0x63b6, 0x2e, 0xef38, "adventurequest", "Adventure Quest /JoD", "Commodore 64"},
+	{0x5b58, 0x50, 0x332e, "adventurequest", "Adventure Quest /JoD", "Atari"},
+	{0x5ace, 0x11, 0xdc12, "adventurequest", "Adventure Quest /JoD", "Spectrum 48"},
+
+	{0x3ebb, 0x00, 0xf6dc, "championraj", "Champion of the Raj", "English 1/2 GD/Amiga"},
+	{0x0fd8, 0x00, 0xf250, "championraj", "Champion of the Raj", "English 2/2 GD/Amiga"},
+
+	{0x3e8f, 0x00, 0x5599, "championraj", "Champion of the Raj", "English 1/2 GD/ST"},
+
+	{0x3e4f, 0x00, 0xb202, "championraj", "Champion of the Raj", "English 1/2 GD/PC"},
+	{0x14a3, 0x00, 0xa288, "championraj", "Champion of the Raj", "English 2/2 GD/PC"},
+
+	{0x1929, 0x00, 0xd4b2, "championraj", "Champion of the Raj", "demo, 1/2 GD/ST"},
+	{0x40e0, 0x02, 0x080d, "championraj", "Champion of the Raj", "demo, 2/2 GD/ST"},
+
+	{0x4872, 0x00, 0x9515, "championraj", "Champion of the Raj", "German 1/2 GD/Amiga"},
+	{0x11f5, 0x00, 0xbf39, "championraj", "Champion of the Raj", "German 2/2 GD/Amiga"},
+
+	{0x4846, 0x00, 0xd9c1, "championraj", "Champion of the Raj", "German 1/2 GD/ST"},
+	{0x11f5, 0x00, 0x7aa4, "championraj", "Champion of the Raj", "German 2/2 GD/ST"},
+
+	{0x110f, 0x00, 0x4b57, "championraj", "Champion of the Raj", "French 2/2 GD/ST"},
+
+	{0x76f4, 0x5e, 0x1fe5, "colossaladvjod", "Colossal Adventure /JoD", "Amiga/PC"},
+	{0x76f4, 0x5a, 0xcf4b, "colossaladvjod", "Colossal Adventure /JoD", "ST"},
+	{0x6f70, 0x40, 0xbd91, "colossaladvjod", "Colossal Adventure /JoD", "MSX"},
+	{0x6f6e, 0x78, 0x28cd, "colossaladvjod", "Colossal Adventure /JoD", "Spectrum 128"},
+	{0x6f4d, 0xcb, 0xe8f2, "colossaladvjod", "Colossal Adventure /JoD", "Amstrad CPC128[v1]/Spectrum +3"},
+	{0x6f6a, 0xa5, 0x8dd2, "colossaladvjod", "Colossal Adventure /JoD", "Amstrad CPC128[v2]"},
+	{0x5e31, 0x7c, 0xaa54, "colossaladvjod", "Colossal Adventure /JoD", "Amstrad CPC64"},
+	{0x6c8e, 0xb6, 0x9be3, "colossaladvjod", "Colossal Adventure /JoD", "Commodore 64"},
+	{0x5b16, 0x3b, 0xe2aa, "colossaladvjod", "Colossal Adventure /JoD", "Atari"},
+	{0x5a8e, 0xf2, 0x7cca, "colossaladvjod", "Colossal Adventure /JoD", "Spectrum 48"},
+
+	{0x630e, 0x8d, 0x7d7d, "dungeonadv", "Dungeon Adventure", "Amstrad CPC"},
+	{0x630e, 0xbe, 0x3374, "dungeonadv", "Dungeon Adventure", "MSX"},
+	{0x6de8, 0x4c, 0xd795, "dungeonadvjod", "Dungeon Adventure /JoD", "Spectrum 128"},
+	{0x6dc0, 0x63, 0x5d95, "dungeonadvjod", "Dungeon Adventure /JoD", "Amstrad CPC128/Spectrum +3"},
+	{0x58a6, 0x24, 0xb50f, "dungeonadvjod", "Dungeon Adventure /JoD", "Amstrad CPC64"},
+	{0x6bd2, 0x65, 0xa41f, "dungeonadvjod", "Dungeon Adventure /JoD", "Commodore 64"},
+	{0x593a, 0x80, 0x7a34, "dungeonadvjod", "Dungeon Adventure /JoD", "Atari"},
+	{0x58a3, 0x38, 0x8ce4, "dungeonadvjod", "Dungeon Adventure /JoD", "Spectrum 48"},
+	{0x6f0c, 0x95, 0x1f64, "dungeonadvjod", "Dungeon Adventure /JoD", "Amiga/PC/ST"},
+
+	{0x63be, 0xd6, 0xcf5d, "emeraldisle", "Emerald Isle", "Atari/Commodore 64/Amstrad CPC/Spectrum"},
+	{0x63be, 0x0a, 0x21ed, "emeraldisle", "Emerald Isle", "MSX *corrupt*"},
+	{0x378c, 0x8d, 0x3a21, "emeraldisle", "Emerald Isle", "BBC"},
+
+	{0x34b3, 0x20, 0xccda, "eriktheviking", "Erik the Viking", "BBC/Commodore 64"},
+	{0x34b3, 0x53, 0x8f00, "eriktheviking", "Erik the Viking", "Spectrum"},
+	{0x34b3, 0xc7, 0x9058, "eriktheviking", "Erik the Viking", "Amstrad CPC"},
+
+	{0x5ff0, 0xf8, 0x3a13, "gnomeranger", "Gnome Ranger", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+	{0x6024, 0x01, 0xaaa9, "gnomeranger", "Gnome Ranger", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+	{0x6036, 0x3d, 0x6c6c, "gnomeranger", "Gnome Ranger", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+	{0x52aa, 0xdf, 0x7b5b, "gnomeranger", "Gnome Ranger", "pt. 1 GD/Spectrum 128"},
+	{0x6ffa, 0xdb, 0xdde2, "gnomeranger", "Gnome Ranger", "pt. 2 GD/Spectrum 128"},
+	{0x723a, 0x69, 0x039b, "gnomeranger", "Gnome Ranger", "pt. 3 GD/Spectrum 128"},
+
+	{0xb1a9, 0x80, 0x5fb7, "gnomeranger", "Gnome Ranger", "pt. 1/Amiga/ST"},
+	{0xab9d, 0x31, 0xbe6d, "gnomeranger", "Gnome Ranger", "pt. 2/Amiga/ST"},
+	{0xae28, 0x87, 0xb6b6, "gnomeranger", "Gnome Ranger", "pt. 3/Amiga/ST"},
+
+	{0xb0ec, 0xc2, 0x0053, "gnomeranger", "Gnome Ranger", "pt. 1/ST[v1]"},
+	{0xaf82, 0x83, 0x19f7, "gnomeranger", "Gnome Ranger", "pt. 2/ST[v1]"},
+
+	{0xb1aa, 0xad, 0xaf47, "gnomeranger", "Gnome Ranger", "pt. 1/PC"},
+	{0xb19e, 0x92, 0x8f96, "gnomeranger", "Gnome Ranger", "pt. 1/ST[v2]"},
+	{0xab8b, 0xbf, 0x31e6, "gnomeranger", "Gnome Ranger", "pt. 2/PC/ST[v2]"},
+	{0xae16, 0x81, 0x8741, "gnomeranger", "Gnome Ranger", "pt. 3/PC/ST[v2]"},
+
+	{0xad41, 0xa8, 0x42c5, "gnomeranger", "Gnome Ranger", "pt. 1/Commodore 64 TO"},
+	{0xa735, 0xf7, 0x2e08, "gnomeranger", "Gnome Ranger", "pt. 2/Commodore 64 TO"},
+	{0xa9c0, 0x9e, 0x0d70, "gnomeranger", "Gnome Ranger", "pt. 3/Commodore 64 TO"},
+
+	{0x908e, 0x0d, 0x58a7, "gnomeranger", "Gnome Ranger", "pt. 1/Commodore 64 Gfx"},
+	{0x8f6f, 0x0a, 0x411a, "gnomeranger", "Gnome Ranger", "pt. 2/Commodore 64 Gfx"},
+	{0x9060, 0xbb, 0xe75d, "gnomeranger", "Gnome Ranger", "pt. 3/Commodore 64 Gfx"},
+
+	{0x8aab, 0xc0, 0xde5f, "gnomeranger", "Gnome Ranger", "pt. 1/Spectrum 48"},
+	{0x8ac8, 0x9a, 0xc89b, "gnomeranger", "Gnome Ranger", "pt. 2/Spectrum 48"},
+	{0x8a93, 0x4f, 0x10cc, "gnomeranger", "Gnome Ranger", "pt. 3/Spectrum 48"},
+
+	{0x5a38, 0xf7, 0x876e, "ingridsback", "Ingrid's Back", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+	{0x531a, 0xed, 0xcf3f, "ingridsback", "Ingrid's Back", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+	{0x57e4, 0x19, 0xb354, "ingridsback", "Ingrid's Back", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+	{0x76a0, 0x3a, 0xb803, "ingridsback", "Ingrid's Back", "pt. 1 GD/Spectrum 128"},
+	{0x7674, 0x0b, 0xe92f, "ingridsback", "Ingrid's Back", "pt. 2 GD/Spectrum 128"},
+	{0x765e, 0xba, 0x086d, "ingridsback", "Ingrid's Back", "pt. 3 GD/Spectrum 128"},
+
+	{0xd19b, 0xad, 0x306d, "ingridsback", "Ingrid's Back", "pt. 1/PC"},
+	{0xc5a5, 0xfe, 0x3c98, "ingridsback", "Ingrid's Back", "pt. 2/PC"},
+	{0xd7ae, 0x9e, 0x1878, "ingridsback", "Ingrid's Back", "pt. 3/PC"},
+
+	{0xd188, 0x13, 0xdc60, "ingridsback", "Ingrid's Back", "pt. 1/Amiga"},
+	{0xc594, 0x03, 0xea95, "ingridsback", "Ingrid's Back", "pt. 2/Amiga"},
+	{0xd79f, 0xb5, 0x1661, "ingridsback", "Ingrid's Back", "pt. 3/Amiga"},
+
+	{0xd183, 0x83, 0xef72, "ingridsback", "Ingrid's Back", "pt. 1/ST"},
+	{0xc58f, 0x65, 0xf337, "ingridsback", "Ingrid's Back", "pt. 2/ST"},
+	{0xd79a, 0x57, 0x49c5, "ingridsback", "Ingrid's Back", "pt. 3/ST"},
+
+	{0xb770, 0x03, 0x9a03, "ingridsback", "Ingrid's Back", "pt. 1/Commodore 64 TO"},
+	{0xb741, 0xb6, 0x2aa5, "ingridsback", "Ingrid's Back", "pt. 2/Commodore 64 TO"},
+	{0xb791, 0xa1, 0xd065, "ingridsback", "Ingrid's Back", "pt. 3/Commodore 64 TO"},
+
+	{0x9089, 0xce, 0xc5e2, "ingridsback", "Ingrid's Back", "pt. 1/Commodore 64 Gfx"},
+	{0x908d, 0x80, 0x30c7, "ingridsback", "Ingrid's Back", "pt. 2/Commodore 64 Gfx"},
+	{0x909e, 0x9f, 0xdecc, "ingridsback", "Ingrid's Back", "pt. 3/Commodore 64 Gfx"},
+
+	{0x8ab7, 0x68, 0xee57, "ingridsback", "Ingrid's Back", "pt. 1/Spectrum 48"},
+	{0x8b1e, 0x84, 0x2538, "ingridsback", "Ingrid's Back", "pt. 2/Spectrum 48"},
+	{0x8b1c, 0xa8, 0x9262, "ingridsback", "Ingrid's Back", "pt. 3/Spectrum 48"},
+
+	{0x46ec, 0x64, 0x2300, "knightorc", "Knight Orc", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+	{0x6140, 0x18, 0x4f66, "knightorc", "Knight Orc", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+	{0x640e, 0xc1, 0xfc69, "knightorc", "Knight Orc", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+	{0x74e0, 0x92, 0x885e, "knightorc", "Knight Orc", "pt. 1 GD/Spectrum 128"},
+	{0x6dbc, 0x97, 0x6f55, "knightorc", "Knight Orc", "pt. 2 GD/Spectrum 128"},
+	{0x7402, 0x07, 0x385f, "knightorc", "Knight Orc", "pt. 3 GD/Spectrum 128"},
+
+	{0xbb93, 0x36, 0x6a05, "knightorc", "Knight Orc", "pt. 1/Amiga"},
+	{0xbb6e, 0xad, 0x4d40, "knightorc", "Knight Orc", "pt. 1/ST"},
+	{0xc58e, 0x4a, 0x4e9d, "knightorc", "Knight Orc", "pt. 2/Amiga/ST"},
+	{0xcb9a, 0x0f, 0x0804, "knightorc", "Knight Orc", "pt. 3/Amiga/ST"},
+
+	{0xbb6e, 0xa6, 0x9753, "knightorc", "Knight Orc", "pt. 1/PC"},
+	{0xc58e, 0x43, 0xe9ce, "knightorc", "Knight Orc", "pt. 2/PC"},
+	{0xcb9a, 0x08, 0x6c36, "knightorc", "Knight Orc", "pt. 3/PC"},
+
+	{0x898a, 0x43, 0xfc8b, "knightorc", "Knight Orc", "pt. 1/Apple ]["},
+	{0x8b9f, 0x61, 0x7288, "knightorc", "Knight Orc", "pt. 2/Apple ]["},
+	{0x8af9, 0x61, 0x7542, "knightorc", "Knight Orc", "pt. 3/Apple ]["},
+
+	{0x8970, 0x6b, 0x3c7b, "knightorc", "Knight Orc", "pt. 1/Commodore 64 Gfx"},
+	{0x8b90, 0x4e, 0x098c, "knightorc", "Knight Orc", "pt. 2/Commodore 64 Gfx"},
+	{0x8aea, 0x4e, 0xca54, "knightorc", "Knight Orc", "pt. 3/Commodore 64 Gfx"},
+
+	{0x86d0, 0xb7, 0xadbd, "knightorc", "Knight Orc", "pt. 1/Spectrum 48"},
+	{0x8885, 0x22, 0xe293, "knightorc", "Knight Orc", "pt. 2/Spectrum 48"},
+	{0x87e5, 0x0e, 0xdc33, "knightorc", "Knight Orc", "pt. 3/Spectrum 48"},
+
+	{0x4fd2, 0x9d, 0x799a, "lancelot", "Lancelot", "pt. 1 GD/BBC"},
+	{0x4dac, 0xa8, 0x86ed, "lancelot", "Lancelot", "pt. 2 GD/BBC"},
+	{0x4f96, 0x22, 0x30f8, "lancelot", "Lancelot", "pt. 3 GD/BBC"},
+
+	{0x5c7a, 0x44, 0x460e, "lancelot", "Lancelot", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+	{0x53a2, 0x1e, 0x2fae, "lancelot", "Lancelot", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+	{0x5914, 0x22, 0x4a31, "lancelot", "Lancelot", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+	{0x768c, 0xe8, 0x8fc6, "lancelot", "Lancelot", "pt. 1 GD/Spectrum 128"},
+	{0x76b0, 0x1d, 0x0fcd, "lancelot", "Lancelot", "pt. 2 GD/Spectrum 128"},
+	{0x765e, 0x4f, 0x3b73, "lancelot", "Lancelot", "pt. 3 GD/Spectrum 128"},
+
+	{0xc0cf, 0x4e, 0xb7fa, "lancelot", "Lancelot", "pt. 1/Amiga/PC/ST"},
+	{0xd5e9, 0x6a, 0x4192, "lancelot", "Lancelot", "pt. 2/Amiga/PC/ST"},
+	{0xbb8f, 0x1a, 0x7487, "lancelot", "Lancelot", "pt. 3/Amiga/PC/ST"},
+
+	{0xc0bd, 0x57, 0x6ef1, "lancelot", "Lancelot", "pt. 1/Mac"},
+	{0xd5d7, 0x99, 0x770b, "lancelot", "Lancelot", "pt. 2/Mac"},
+	{0xbb7d, 0x17, 0xbc42, "lancelot", "Lancelot", "pt. 3/Mac"},
+
+	{0xb4c9, 0x94, 0xd784, "lancelot", "Lancelot", "pt. 1/Commodore 64 TO"},
+	{0xb729, 0x51, 0x8ee5, "lancelot", "Lancelot", "pt. 2/Commodore 64 TO"},
+	{0xb702, 0xe4, 0x1809, "lancelot", "Lancelot", "pt. 3/Commodore 64 TO"},
+
+	{0x8feb, 0xba, 0xa800, "lancelot", "Lancelot", "pt. 1/Commodore 64 Gfx"},
+	{0x8f6b, 0xfa, 0x0f7e, "lancelot", "Lancelot", "pt. 2/Commodore 64 Gfx"},
+	{0x8f71, 0x2f, 0x0ddc, "lancelot", "Lancelot", "pt. 3/Commodore 64 Gfx"},
+
+	{0x8ade, 0xf2, 0xfffb, "lancelot", "Lancelot", "pt. 1/Spectrum 48"},
+	{0x8b0e, 0xfb, 0x0bab, "lancelot", "Lancelot", "pt. 2/Spectrum 48"},
+	{0x8ab3, 0xc1, 0xcb62, "lancelot", "Lancelot", "pt. 3/Spectrum 48"},
+
+	{0xbba4, 0x94, 0x0871, "lancelot", "Lancelot", "pt. 1/Amiga/PC *USA*"},
+	{0xd0c0, 0x56, 0x8c48, "lancelot", "Lancelot", "pt. 2/Amiga/PC *USA*"},
+	{0xb6ac, 0xc6, 0xaea0, "lancelot", "Lancelot", "pt. 3/Amiga/PC *USA*"},
+
+	{0x8afc, 0x07, 0x8321, "lancelot", "Lancelot", "pt. 1/Commodore 64 Gfx *USA*"},
+	{0x8aec, 0x13, 0x6791, "lancelot", "Lancelot", "pt. 2/Commodore 64 Gfx *USA*"},
+	{0x8aba, 0x0d, 0x5602, "lancelot", "Lancelot", "pt. 3/Commodore 64 Gfx *USA*"},
+
+	{0x5eb9, 0x30, 0xe99a, "lordsoftime", "Lords of Time", "Amstrad CPC"},
+	{0x5eb9, 0x5d, 0xc098, "lordsoftime", "Lords of Time", "MSX"},
+	{0x5eb9, 0x6e, 0xc689, "lordsoftime", "Lords of Time", "Spectrum"},
+	{0x579e, 0x97, 0x9faa, "lordsoftimetmgd", "Lords of Time /T&M GD", "BBC"},
+	{0x69fe, 0x56, 0xecfb, "lordsoftimetmgd", "Lords of Time /T&M GD", "Amstrad CPC/Spectrum +3"},
+	{0x6f1e, 0xda, 0x2ce0, "lordsoftimetmgd", "Lords of Time /T&M GD", "Spectrum 128"},
+	{0xb57c, 0x44, 0x7779, "lordsoftimetm", "Lords of Time /T&M", "PC"},
+	{0xb579, 0x89, 0x3e89, "lordsoftimetm", "Lords of Time /T&M", "ST"},
+	{0xb576, 0x2a, 0x7239, "lordsoftimetm", "Lords of Time /T&M", "Amiga"},
+	{0xb563, 0x6a, 0x0c5c, "lordsoftimetm", "Lords of Time /T&M", "Mac"},
+	{0xb38c, 0x37, 0x9f8e, "lordsoftimetm", "Lords of Time /T&M", "Commodore 64 TO"},
+	{0x9070, 0x43, 0x45d4, "lordsoftimetm", "Lords of Time /T&M", "Commodore 64 Gfx"},
+	{0x8950, 0xa1, 0xbb16, "lordsoftimetm", "Lords of Time /T&M", "Spectrum 48"},
+	{0xb260, 0xe5, 0xc5b2, "lordsoftimetm", "Lords of Time /T&M", "PC/ST *USA*"},
+	{0xb257, 0xf8, 0xfbd5, "lordsoftimetm", "Lords of Time /T&M", "Amiga *USA*"},
+	{0x8d78, 0x3a, 0xba6e, "lordsoftimetm", "Lords of Time /T&M", "Commodore 64 Gfx *USA*"},
+
+	{0x7410, 0x5e, 0x60be, "princeofmagik", "Price of Magik", "Spectrum 128"},
+	{0x5aa4, 0xc1, 0x10a0, "princeofmagik", "Price of Magik", "Spectrum 48[v1]"},
+	{0x5aa4, 0xc1, 0xeda4, "princeofmagik", "Price of Magik", "Spectrum 48[v2]"},
+	{0x6fc6, 0x14, 0xf9b6, "princeofmagik", "Price of Magik", "Commodore 64"},
+	{0x5aa4, 0xc1, 0xbbf4, "princeofmagik", "Price of Magik", "Amstrad CPC"},
+	{0x5671, 0xbc, 0xff35, "princeofmagik", "Price of Magik", "BBC"},
+	{0x6108, 0xdd, 0xefe7, "princeofmagiktmgd", "Price of Magik /T&M GD", "Spectrum 128"},
+	{0x579a, 0x2a, 0x9373, "princeofmagiktmgd", "Price of Magik /T&M GD", "BBC"},
+	{0x5a50, 0xa9, 0xa5fa, "princeofmagiktmgd", "Price of Magik /T&M GD", "Amstrad CPC/Spectrum +3"},
+	{0xbac7, 0x7f, 0xddb2, "princeofmagiktm", "Price of Magik /T&M", "PC"},
+	{0xbac4, 0x80, 0xa750, "princeofmagiktm", "Price of Magik /T&M", "ST"},
+	{0xbaca, 0x3a, 0x221b, "princeofmagiktm", "Price of Magik /T&M", "Amiga"},
+	{0xbab2, 0x87, 0x09f5, "princeofmagiktm", "Price of Magik /T&M", "Mac"},
+	{0xb451, 0xa8, 0x2682, "princeofmagiktm", "Price of Magik /T&M", "Commodore 64 TO"},
+	{0x8f51, 0xb2, 0x6c9a, "princeofmagiktm", "Price of Magik /T&M", "Commodore 64 Gfx"},
+	{0x8a60, 0x2a, 0x29ed, "princeofmagiktm", "Price of Magik /T&M", "Spectrum 48"},
+	{0xb7a0, 0x7e, 0x2226, "princeofmagiktm", "Price of Magik /T&M", "PC/ST *USA*"},
+	{0xb797, 0x1f, 0x84a9, "princeofmagiktm", "Price of Magik /T&M", "Amiga *USA*"},
+	{0x8c46, 0xf0, 0xcaf6, "princeofmagiktm", "Price of Magik /T&M", "Commodore 64 Gfx *USA*"},
+
+	{0x506c, 0xf0, 0xba72, "redmoon", "Red Moon", "BBC/Commodore 64/Amstrad CPC/MSX"},
+	{0x505d, 0x32, 0x2dcf, "redmoon", "Red Moon", "Spectrum"},
+	{0x6da0, 0xb8, 0x3802, "redmoontmgd", "Red Moon /T&M GD", "Spectrum 128"},
+	{0x6888, 0x8d, 0x7f6a, "redmoontmgd", "Red Moon /T&M GD", "Amstrad CPC/Spectrum +3"},
+	{0x5500, 0x50, 0xca75, "redmoontmgd", "Red Moon /T&M GD", "BBC"},
+	{0xa69e, 0x6c, 0xb268, "redmoontm", "Red Moon /T&M", "PC"},
+	{0xa698, 0x41, 0xcaca, "redmoontm", "Red Moon /T&M", "ST"},
+	{0xa692, 0xd1, 0x6a99, "redmoontm", "Red Moon /T&M", "Amiga"},
+	{0xa67c, 0xb8, 0xff41, "redmoontm", "Red Moon /T&M", "Mac"},
+	{0xa4e2, 0xa6, 0x016d, "redmoontm", "Red Moon /T&M", "Commodore 64 TO"},
+	{0x903f, 0x6b, 0x603e, "redmoontm", "Red Moon /T&M", "Commodore 64 Gfx"},
+	{0x8813, 0x11, 0x22de, "redmoontm", "Red Moon /T&M", "Spectrum 48"},
+	{0xa3a4, 0xdf, 0x6732, "redmoontm", "Red Moon /T&M", "PC/ST *USA*"},
+	{0xa398, 0x82, 0xd031, "redmoontm", "Red Moon /T&M", "Amiga *USA*"},
+	{0x8d56, 0xd3, 0x146a, "redmoontm", "Red Moon /T&M", "Commodore 64 Gfx *USA*"},
+
+	{0x60c4, 0x28, 0x0154, "returntoeden", "Return to Eden", "Amstrad CPC/Commodore 64[v1]"},
+	{0x6064, 0x01, 0x5b3c, "returntoeden", "Return to Eden", "BBC[v1]"},
+	{0x6064, 0x95, 0x510c, "returntoeden", "Return to Eden", "Commodore 64[v2]"},
+	{0x6064, 0xda, 0xe610, "returntoeden", "Return to Eden", "Commodore 64[v2] *corrupt*"},
+	{0x6064, 0xbd, 0x73ec, "returntoeden", "Return to Eden", "Atari *corrupt*"},
+	{0x6047, 0x6c, 0x17ab, "returntoeden", "Return to Eden", "BBC[v2]"},
+	{0x5ca1, 0x33, 0x1c43, "returntoeden", "Return to Eden", "Spectrum[v1]"},
+	{0x5cb7, 0x64, 0x0790, "returntoeden", "Return to Eden", "Spectrum[v2]"},
+	{0x5cb7, 0xfe, 0x3533, "returntoeden", "Return to Eden", "MSX"},
+	{0x7d16, 0xe6, 0x5438, "returntoedensd", "Return to Eden /SD", "Amiga/ST"},
+	{0x7d14, 0xe8, 0xfbab, "returntoedensd", "Return to Eden /SD", "PC"},
+	{0x7cff, 0xf8, 0x6044, "returntoedensd", "Return to Eden /SD", "Amstrad CPC/Spectrum +3"},
+	{0x7cf8, 0x24, 0x9c1c, "returntoedensd", "Return to Eden /SD", "Mac"},
+	{0x7c55, 0x18, 0xdaee, "returntoedensd", "Return to Eden /SD", "Spectrum 128"},
+	{0x772f, 0xca, 0x8602, "returntoedensd", "Return to Eden /SD", "Commodore 64"},
+	{0x60f7, 0x68, 0xc2bc, "returntoedensd", "Return to Eden /SD", "Atari"},
+	{0x639c, 0x8b, 0x06e2, "returntoedensd", "Return to Eden /SD", "Apple ]["},
+	{0x5f43, 0xca, 0x828c, "returntoedensd", "Return to Eden /SD", "Spectrum 48"},
+
+	{0x55ce, 0xa1, 0xba12, "scapeghost", "Scapeghost", "pt. 1 GD/BBC"},
+	{0x54a6, 0xa9, 0xc9f3, "scapeghost", "Scapeghost", "pt. 2 GD/BBC"},
+	{0x51bc, 0xe3, 0x89c3, "scapeghost", "Scapeghost", "pt. 3 GD/BBC"},
+
+	{0x5cbc, 0xa5, 0x0dbe, "scapeghost", "Scapeghost", "pt. 1 GD/Amstrad CPC/Spectrum +3"},
+	{0x5932, 0x4e, 0xb2f5, "scapeghost", "Scapeghost", "pt. 2 GD/Amstrad CPC/Spectrum +3"},
+	{0x5860, 0x95, 0x3227, "scapeghost", "Scapeghost", "pt. 3 GD/Amstrad CPC/Spectrum +3"},
+
+	{0x762e, 0x82, 0x8848, "scapeghost", "Scapeghost", "pt. 1 GD/Spectrum 128"},
+	{0x5bd6, 0x35, 0x79ef, "scapeghost", "Scapeghost", "pt. 2 GD/Spectrum 128"},
+	{0x6fa8, 0xa4, 0x62c2, "scapeghost", "Scapeghost", "pt. 3 GD/Spectrum 128"},
+
+	{0xbeab, 0x2d, 0x94d9, "scapeghost", "Scapeghost", "pt. 1/Amiga"},
+	{0xc132, 0x14, 0x7adc, "scapeghost", "Scapeghost", "pt. 1/Amiga *bak*"},
+	{0xbe94, 0xcc, 0x04b8, "scapeghost", "Scapeghost", "pt. 1/PC/ST"},
+	{0x99bd, 0x65, 0x032e, "scapeghost", "Scapeghost", "pt. 2/Amiga/PC/ST"},
+	{0xbcb6, 0x7a, 0x7d4f, "scapeghost", "Scapeghost", "pt. 3/Amiga/PC/ST"},
+
+	{0x9058, 0xcf, 0x9748, "scapeghost", "Scapeghost", "pt. 1/Commodore 64 Gfx"},
+	{0x8f43, 0xc9, 0xeefd, "scapeghost", "Scapeghost", "pt. 2/Commodore 64 Gfx"},
+	{0x90ac, 0x68, 0xb4a8, "scapeghost", "Scapeghost", "pt. 3/Commodore 64 Gfx"},
+
+	{0x8a21, 0xf4, 0xd9e4, "scapeghost", "Scapeghost", "pt. 1/Spectrum 48"},
+	{0x8a12, 0xe3, 0xc2ff, "scapeghost", "Scapeghost", "pt. 2/Spectrum 48"},
+	{0x8a16, 0xcc, 0x4f3b, "scapeghost", "Scapeghost", "pt. 3/Spectrum 48"},
+
+	{0x5fab, 0x5c, 0xa309, "snowball", "Snowball", "Amstrad CPC"},
+	{0x5fab, 0x2f, 0x8aa2, "snowball", "Snowball", "MSX"},
+	{0x7b31, 0x6e, 0x2e2b, "snowballsd", "Snowball /SD", "Amiga/ST"},
+	{0x7b2f, 0x70, 0x6955, "snowballsd", "Snowball /SD", "Mac/PC/Spectrum 128"},
+	{0x7b2f, 0x70, 0x6f6c, "snowballsd", "Snowball /SD", "Amstrad CPC/Spectrum +3"},
+	{0x7363, 0x65, 0xa0ab, "snowballsd", "Snowball /SD", "Commodore 64"},
+	{0x6bf8, 0x3f, 0xc9f7, "snowballsd", "Snowball /SD", "Atari"},
+	{0x67a3, 0x9d, 0x1d05, "snowballsd", "Snowball /SD", "Apple ]["},
+	{0x6541, 0x02, 0x2e6c, "snowballsd", "Snowball /SD", "Spectrum 48"},
+
+	{0x772b, 0xcd, 0xa503, "worminparadise", "Worm in Paradise", "Spectrum 128"},
+	{0x546c, 0xb7, 0x9420, "worminparadise", "Worm in Paradise", "Spectrum 48"},
+	{0x6d84, 0xf9, 0x49ae, "worminparadise", "Worm in Paradise", "Commodore 64 *corrupt*"},
+	{0x6d84, 0xc8, 0x943f, "worminparadise", "Worm in Paradise", "Commodore 64 *fixed*"},
+	{0x6030, 0x47, 0x46ad, "worminparadise", "Worm in Paradise", "Amstrad CPC"},
+	{0x5828, 0xbd, 0xe7cb, "worminparadise", "Worm in Paradise", "BBC"},
+	{0x7cd9, 0x0c, 0x4df1, "worminparadise", "Worm in Paradise /SD", "Amiga/ST"},
+	{0x7cd7, 0x0e, 0x4feb, "worminparadisesd", "Worm in Paradise /SD", "Amstrad CPC/Mac/PC/Spectrum 128/Spectrum +3"},
+	{0x788d, 0x72, 0x888a, "worminparadisesd", "Worm in Paradise /SD", "Commodore 64"},
+	{0x6161, 0xf3, 0xe6d7, "worminparadisesd", "Worm in Paradise /SD", "Atari"},
+	{0x60dd, 0xf2, 0x5bb8, "worminparadisesd", "Worm in Paradise /SD", "Apple ]["},
+	{0x5ebb, 0xf1, 0x4dec, "worminparadisesd", "Worm in Paradise /SD", "Spectrum 48"},
+
+	{0x0000, 0x00, 0x0000, nullptr, nullptr, nullptr}
 };
 
 /**
@@ -803,13 +777,6 @@ const L9V1GameInfo L9_V1_GAMES[] = {
 	0x15, 0x6c, 284, -0x00f0, 0x0000, -0x0050, -0x0050, -0x0050, 0x0300, 0x1930, 0x3c17, /* Snowball */
 };
 
-// TODO: The list of Level 9 games and detection entries needs to be filled out
-const PlainGameDescriptor LEVEL9_GAME_LIST[] = {
-	{ "level9", "Level 9 IF Game" },
-
-	{ nullptr, nullptr }
-};
-
 const GlkDetectionEntry LEVEL9_GAMES[] = {
 	DT_END_MARKER
 };


Commit: 64b4f13b36030963b507be6e8fc4c4eb7bf22206
    https://github.com/scummvm/scummvm/commit/64b4f13b36030963b507be6e8fc4c4eb7bf22206
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-10-26T11:13:30-07:00

Commit Message:
GLK: LEVEL9: Compilation fixes

Changed paths:
    engines/glk/level9/detection.cpp
    engines/glk/level9/detection.h
    engines/glk/level9/level9_main.cpp
    engines/glk/level9/level9_main.h
    engines/glk/level9/os_glk.cpp


diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
index 93e8143..5788c0f 100644
--- a/engines/glk/level9/detection.cpp
+++ b/engines/glk/level9/detection.cpp
@@ -514,7 +514,7 @@ void Scanner::fullScan(byte *startFile, uint32 size) {
 
 /*----------------------------------------------------------------------*/
 
-GameDetection::GameDetection(byte *&startData, size_t &fileSize) :
+GameDetection::GameDetection(byte *&startData, uint32 &fileSize) :
 		_startData(startData), _fileSize(fileSize), _crcInitialized(false), _gameName(nullptr) {
 	Common::fill(&_crcTable[0], &_crcTable[256], 0);
 }
@@ -723,7 +723,7 @@ bool Level9MetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &
 		if (!gameFile.open(*file))
 			continue;
 
-		size_t fileSize = gameFile.size();
+		uint32 fileSize = gameFile.size();
 		if (fileSize > 0xffff) {
 			// Too big to possibly be a Level 9 game
 			gameFile.close();
diff --git a/engines/glk/level9/detection.h b/engines/glk/level9/detection.h
index ae876b9..f6832d1 100644
--- a/engines/glk/level9/detection.h
+++ b/engines/glk/level9/detection.h
@@ -101,7 +101,7 @@ public:
 class GameDetection : public Scanner {
 private:
 	byte *&_startData;
-	size_t &_fileSize;
+	uint32 &_fileSize;
 	bool _crcInitialized;
 	uint16 _crcTable[256];
 public:
@@ -110,7 +110,7 @@ public:
 	/**
 	 * Constructor
 	 */
-	GameDetection(byte *&startData, size_t &fileSize);
+	GameDetection(byte *&startData, uint32 &fileSize);
 
 	/**
 	 * Identify a game from its data length, checksum, and CRC.  Returns the
diff --git a/engines/glk/level9/level9_main.cpp b/engines/glk/level9/level9_main.cpp
index bbaad57..3cc027e 100644
--- a/engines/glk/level9/level9_main.cpp
+++ b/engines/glk/level9/level9_main.cpp
@@ -78,7 +78,7 @@ enum L9GfxTypes { GFX_V2, GFX_V3A, GFX_V3B, GFX_V3C };
 /* Global Variables */
 L9BYTE *startfile, *pictureaddress, *picturedata;
 byte *startdata;
-size_t FileSize, picturesize;
+uint32 FileSize, picturesize;
 
 byte *L9Pointers[12];
 byte *absdatablock, *list2ptr, *list3ptr, *list9startptr, *acodeptr;
@@ -1808,6 +1808,10 @@ L9BOOL corruptinginput() {
 	} while (TRUE);
 	/* ip22 */
 	checknumber();
+
+	// Unused variables
+	(void)keywordnumber;
+
 	return TRUE;
 }
 
diff --git a/engines/glk/level9/level9_main.h b/engines/glk/level9/level9_main.h
index 95d9f18..5bd1dff 100644
--- a/engines/glk/level9/level9_main.h
+++ b/engines/glk/level9/level9_main.h
@@ -87,7 +87,7 @@ struct Bitmap {
 #define L9SETDWORD(x,val) WRITE_LE_UINT32(x, val)
 
 extern byte *startdata;
-extern size_t FileSize;
+extern uint32 FileSize;
 
 extern void level9_initialize();
 
diff --git a/engines/glk/level9/os_glk.cpp b/engines/glk/level9/os_glk.cpp
index ab171c3..ac9a4ee 100644
--- a/engines/glk/level9/os_glk.cpp
+++ b/engines/glk/level9/os_glk.cpp
@@ -1007,14 +1007,15 @@ static void gln_graphics_timeout() {
 
 	static int deferred_repaint = FALSE;       /* Local delayed repaint flag */
 	static int ignore_counter;                 /* Count of calls ignored */
-
 	static int x_offset, y_offset;             /* Point plot offsets */
+
+#ifndef GARGLK
 	static int yield_counter;                  /* Yields in rendering */
 	static int saved_layer;                    /* Saved current layer */
 	static int saved_x, saved_y;               /* Saved x,y coord */
 
 	static int total_regions;                  /* Debug statistic */
-
+#endif
 	gln_byte *on_screen;                       /* On-screen image buffer */
 	gln_byte *off_screen;                      /* Off-screen image buffer */
 	long picture_size;                         /* Picture size in pixels */
@@ -1133,13 +1134,14 @@ static void gln_graphics_timeout() {
 		                              x_offset, y_offset,
 		                              GLN_GRAPHICS_PIXEL,
 		                              gln_graphics_width, gln_graphics_height);
-
+#ifndef GARGLK
 		/* Start a fresh picture rendering pass. */
 		yield_counter = 0;
 		saved_layer = 0;
 		saved_x = 0;
 		saved_y = 0;
 		total_regions = 0;
+#endif
 
 		/* Clear the new picture and deferred repaint flags. */
 		gln_graphics_new_picture = FALSE;
@@ -2333,7 +2335,7 @@ static void gln_status_print() {
 
 			/* Bracket, and output the extracted game name. */
 			g_vm->glk_put_string("[ ");
-			g_vm->glk_put_string((char *) game_name);
+			g_vm->glk_put_string(game_name);
 
 			for (index = strlen(game_name);
 			        index <= GLN_DEFAULT_STATUS_WIDTH; index++)
@@ -4545,12 +4547,6 @@ static const int GLN_WATCHDOG_TIMEOUT = 5,
                  GLN_WATCHDOG_PERIOD = 10240;
 
 /*
- * The following values need to be passed between the startup_code and main
- * functions.
- */
-static const char *gln_game_message = nullptr;  /* Error message. */
-
-/*
  * gln_establish_picture_filename()
  *
  * Given a game name, try to create an (optional) graphics data file. For





More information about the Scummvm-git-logs mailing list