[Scummvm-git-logs] scummvm master -> 852e3592771ebb440f505f130ef22bcb68c3720a

dreammaster paulfgilbert at gmail.com
Sat Nov 23 02:51:05 UTC 2019


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

Summary:
1c67621047 GLK: MAGNETIC: Added subengine files
9af0d15194 GLK: MAGNETIC: Startup fixes
9df3c85184 GLK: MAGNETIC: Making functions all class methods, statics into class fields
69f186c665 GLK: MAGNETIC: Moving local method static variables to class fields
53d9b6e1c6 GLK: Fixing defines clashes between sub-engines
852e359277 GLK: MAGNETIC: Hooking up loading/saving games


Commit: 1c6762104792fe84390c114b6eafe85dc7867d13
    https://github.com/scummvm/scummvm/commit/1c6762104792fe84390c114b6eafe85dc7867d13
Author: dreammaster (dreammaster at scummvm.org)
Date: 2019-11-22T18:49:07-08:00

Commit Message:
GLK: MAGNETIC: Added subengine files

Changed paths:
  A engines/glk/magnetic/defs.h
  A engines/glk/magnetic/glk.cpp
  A engines/glk/magnetic/main.cpp
  A engines/glk/magnetic/myth.cpp
  R engines/glk/magnetic/graphics.cpp
  R engines/glk/magnetic/sound.cpp
    engines/glk/magnetic/emu.cpp
    engines/glk/magnetic/magnetic.cpp
    engines/glk/magnetic/magnetic.h
    engines/glk/magnetic/magnetic_types.h
    engines/glk/module.mk


diff --git a/engines/glk/magnetic/defs.h b/engines/glk/magnetic/defs.h
new file mode 100644
index 0000000..b448200
--- /dev/null
+++ b/engines/glk/magnetic/defs.h
@@ -0,0 +1,471 @@
+/* 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 MAGNETIC_DEFS_H
+#define MAGNETIC_DEFS_H
+
+#include "common/scummsys.h"
+#include "glk/magnetic/magnetic_types.h"
+
+namespace Glk {
+namespace Magnetic {
+
+/*****************************************************************************\
+* Type definitions for Magnetic
+*
+* Note: When running into trouble please ensure that these types have the
+*       correct number of bits on your system !!!
+\*****************************************************************************/
+
+typedef byte type8;
+typedef int8 type8s;
+typedef uint16 type16;
+typedef int16 type16s;
+typedef uint32 type32;
+typedef int32 type32s;
+
+/****************************************************************************\
+* Compile time switches
+\****************************************************************************/
+
+/* Switch:  SAVEMEM
+   Purpose: Magnetic loads a complete graphics file into memory by default.
+            Setting this switch you tell Magnetic to load images on request
+            (saving memory, wasting load time)
+
+#define SAVEMEM
+*/
+
+/* Switch:  NO_ANIMATION
+   Purpose: By default Magnetic plays animated graphics.
+            Setting this switch to ignore animations, Magnetic shows the
+            static parts of the images anyway!
+
+#define NO_ANIMATION
+*/
+
+/****************************************************************************\
+* Abstract functions
+*
+* Note: These functions MUST be implemented by each port of Magnetic!
+\****************************************************************************/
+
+/****************************************************************************\
+* Function: ms_load_file
+*
+* Purpose: Load a save game file and restore game
+*
+* Parameters:   type8s* name            zero terminated string of filename
+*                                       typed by player
+*               type8*  ptr             pointer to data to save
+*               type16 size             number of bytes to save
+*
+* Result: 0 is successful
+*
+* Note: You probably want to put in a file requester!
+\****************************************************************************/
+
+extern type8 ms_load_file(const char *name, type8 *ptr, type16 size);
+
+/****************************************************************************\
+* Function: ms_save_file
+*
+* Purpose: Save the current game to file
+*
+* Parameters:   type8s* name            zero terminated string of filename
+*                                       typed by player
+*               type8*  ptr             pointer to data to save
+*               type16  size            number of bytes to save
+*
+* Result: 0 is successful
+*
+* Note: You probably want to put in a file requester!
+\****************************************************************************/
+
+extern type8 ms_save_file(const char *name, type8 *ptr, type16 size);
+
+/****************************************************************************\
+* Function: ms_statuschar
+*
+* Purpose: Output a single character to the status bar
+*
+* Parameters:   type8   c               character to be printed
+*
+* Note: All characters are printed as provided except for:
+*       0x0A resets the x position to zero
+*       0x09 moves the cursor to the right half of the bar, ie 'width-11'
+\****************************************************************************/
+
+extern void ms_statuschar(type8 c);
+
+/****************************************************************************\
+* Function: ms_putchar
+*
+* Purpose: Output a single character to the game/text windows
+*
+* Parameters:   type8   c               character to be printed
+*
+* Note: It is highly recommended to buffer the output, see also ms_flush()
+\****************************************************************************/
+
+extern void ms_putchar(type8 c);
+
+/****************************************************************************\
+* Function: ms_flush
+*
+* Purpose: Flush the output buffer (if applicable)
+*
+* Note: see also ms_putchar
+\****************************************************************************/
+
+extern void ms_flush(void);
+
+/****************************************************************************\
+* Function: ms_getchar
+*
+* Purpose: Read user input, buffered
+*
+* Parameters:   type8   trans           if not 0, translate any #undo
+*                                       input to a return code of 0
+*
+* Return: One character
+*
+* Note: The first time it is called a string should be read and then given
+*       back one byte at a time (ie. one for each call) until a '\n' is
+*       reached (which will be the last byte sent back before it all restarts)
+*       Returning a zero means 'undo' and the rest of the line must then be
+*       ignored.
+*       Returning 1 means that the opcode should return immediately. This is
+*       needed to prevent possible corruption of the game's memory in
+*       interpreters which allow a new game to be loaded without restarting.
+\****************************************************************************/
+
+extern type8 ms_getchar(type8 trans);
+
+/****************************************************************************\
+* Function: ms_showpic
+*
+* Purpose: Displays or hides a picture
+*
+* Parameter:    type32  c       number of image to be displayed
+*               type8   mode    mode == 0 means gfx off,
+*                               mode == 1 gfx on thumbnails,
+*                               mode == 2 gfx on normal.
+*
+* Note: For retrieving the raw data of a picture call ms_extract (see below)
+\****************************************************************************/
+
+extern void ms_showpic(type32 c, type8 mode);
+
+/****************************************************************************\
+* Function: ms_fatal
+*
+* Purpose: Handle fatal interpreter error
+*
+* Parameter:    type8s* txt     message
+\****************************************************************************/
+
+extern void ms_fatal(const char *txt);
+
+/****************************************************************************\
+* Magnetic core functions
+*
+* Note: These functions SHOULD be used somewhere in your port!
+\****************************************************************************/
+
+/****************************************************************************\
+* Function: ms_extract
+*
+* Purpose: Extract a picture and return a pointer to a raw bitmap
+*
+* Parameters:   type32  c               number of the picture
+*               type16* w               width of picture
+*               type16* h               height pf picture
+*               type16* pal             array for the palette (16 colours)
+*               type8*  is_anim         1 if animated picture, otherwise 0
+*                                       OR null (!)
+*
+* Return: Pointer to bitmap data if successful, otherwise null (also if gfx
+*         are disabled!)
+*
+* Note: All pictures have 16 colours and the palette entries use 3-bit RGB
+*       encoded as 00000RRR0GGG0BBB, that is, bits 0-2 give the blue
+*       component, bits 4-6 the green and bits 8-10 the red. The bitmap is
+*       one byte per pixel, with each byte giving the pixel's index into the
+*       palette. The data is given starting from the upper left corner. The
+*       image buffer is reused when the next picture is requested, so take
+*       care! More information on animated pictures are below!
+\****************************************************************************/
+
+extern type8 *ms_extract(type32 c, type16 *w, type16 *h, type16 *pal, type8 *is_anim);
+
+/****************************************************************************\
+* Magnetic animated pictures support
+*
+* Note: Some of the pictures for Wonderland and the Collection Volume 1 games
+* are animations. To detect these, pass a pointer to a type8 as the is_anim
+* argument to ms_extract().
+*
+* There are two types of animated images, however almost all images are type1.
+* A type1 image consists of four main elements:
+* 1) A static picture which is loaded straight at the beginning
+* 2) A set of frames with a mask. These frames are just "small pictures", which
+*    are coded like the normal static pictures. The image mask determines
+*    how the frame is removed after it has been displayed. A mask is exactly
+*    1/8 the size of the image and holds 1 bit per pixel, saying "remove pixel"
+*    or leave pixel set when frame gets removed. It might be a good idea to check
+*    your system documentation for masking operations as your system might be
+*    able to use this mask data directly.
+* 3) Positioning tables. These hold animation sequences consisting of commands
+*    like "Draw frame 12 at (123,456)"
+* 4) A playback script, which determines how to use the positioning tables.
+*    These scripts are handled inside Magnetic, so no need to worry about.
+*    However, details can be found in the ms_animate() function.
+*
+* A type2 image is like a type1 image, but it does not have a static
+* picture, nor does it have frame masking. It just consists of frames.
+*
+* How to support animations?
+* After getting is_anim == 1 you should call ms_animate() immediately, and at
+* regular intervals until ms_animate() returns 0. An appropriate interval
+* between calls is about 100 milliseconds.
+* Each call to ms_animate() will fill in the arguments with the address
+* and size of an array of ms_position structures (see below), each of
+* which holds an an animation frame number and x and y co-ordinates. To
+* display the animation, decode all the animation frames (discussed below)
+* from a single call to ms_animate() and display each one over the main picture.
+* If your port does not support animations, define NO_ANIMATION.
+\****************************************************************************/
+
+/****************************************************************************\
+* Function: ms_animate
+*
+* Purpose: Generate the next frame of an animation
+*
+* Parameters:   ms_position**   positions  array of ms_position structs
+*               type16*         count      size of array
+*
+* Return: 1 if animation continues, 0 if animation is finfished
+*
+* Note: The positions array holds size ms_positions structures. BEFORE calling
+*       ms_animate again, retrieve the frames for all the ms_positions
+*       structures with ms_get_anim_frame and display each one on the static
+*       main picture.
+\****************************************************************************/
+
+extern type8 ms_animate(struct ms_position **positions, type16 *count);
+
+/****************************************************************************\
+* Function: ms_get_anim_frame
+*
+* Purpose: Extracts the bitmap data of a single animation frame
+*
+* Parameters:   type16s   number        number of frame (see ms_position struct)
+*               type16*   width         width of frame
+*               type16*   height        height of frame
+*               type8**   mask          pointer to masking data, might be NULL
+*
+* Return: 1 if animation continues, 0 if animation is finfished
+*
+* Note: The format of the frame is identical to the main pictures' returned by
+*       ms_extract. The mask has one-bit-per-pixel, determing how to handle the
+*       removal of the frame.
+\****************************************************************************/
+
+extern type8 *ms_get_anim_frame(type16s number, type16 *width, type16 *height, type8 **mask);
+
+/****************************************************************************\
+* Function: ms_anim_is_repeating
+*
+* Purpose: Detects whether an animation is repeating
+*
+* Return: True if repeating
+\****************************************************************************/
+
+extern type8 ms_anim_is_repeating(void);
+
+/****************************************************************************\
+* Magnetic Windows hint support
+*
+* The windowed Magnetic Scolls games included online hints. To add support
+* for the hints to your magnetic port, you should implement the ms_showhints
+* function. It retrieves a pointer to an array of ms_hint structs
+* The root element is always hints[0]. The elcount determines the number
+* of items in this topic. You probably want to display those in some kind
+* of list interface. The content pointer points to the actual description of
+* the items, separated by '\0' terminators. The nodetype is 1 if the items are
+* "folders" and 2 if the items are hints. Hints should be displayed one after
+* another. For "folder" items, the links array holds the index of the hint in
+* the array which is to be displayed on selection. One hint block has exactly
+* one type. The parent element determines the "back" target.
+\****************************************************************************/
+
+/****************************************************************************\
+* Function: ms_showhints
+* Purpose: Show the player a hint
+*
+* Parameters:   ms_hint* hints          pointer to array of ms_hint structs
+*
+* Return: 0 on error, 1 on success
+\****************************************************************************/
+
+extern type8 ms_showhints(struct ms_hint *hints);
+
+/****************************************************************************\
+* Magnetic Windows sound support
+*
+* Wonderland contains music scores that are played when entering specific
+* locations in the game. The music data are actually MIDI events and can be
+* played through normal MIDI devices. The original game plays the MIDI score
+* until the end, even if the location is changed while playing. The playback
+* tempo is not included with the MIDI data. The ms_sndextract function
+* returns a recommended tempo, however depending on the MIDI implementation
+* and operating system, you might need to adapt it.
+\****************************************************************************/
+
+/****************************************************************************\
+* Function: ms_playmusic
+*
+* Purpose: Plays (or stops playing) a MIDI music score.
+*
+* Parameter:    type8 * midi_data       the MIDI data to play
+*               type32  length          the length of the MIDI data
+*               type16  tempo           the suggested tempo for playing
+*
+* Note: If midi_data is NULL, all that should happen is that any currently
+* playing music is stopped.
+* Note: The data returned contain a complete MIDI file header, so if pure
+*       memory processing is not applicable you can write the data to a
+*       temporary file and use external players or libraries.
+\****************************************************************************/
+
+extern void ms_playmusic(type8 *midi_data, type32 length, type16 tempo);
+
+/****************************************************************************\
+* Function: ms_init
+*
+* Purpose: Loads the interpreter with a game
+*
+* Parameters:   type8s* name            filename of story file
+*               type8s* gfxname         filename of graphics file (optional)
+*               type8s* hntname         filename of hints file (optional)
+*               type8s* sndname         filename of music file (optional)
+*
+* Return:       0 = failure
+*               1 = success (without graphics or graphics failed)
+*               2 = success (with graphics)
+*
+* Note: You must call this function before starting the ms_rungame loop
+\****************************************************************************/
+
+extern type8 ms_init(const char *name, const char *gfxname, const char *hntname, const char *sndname);
+
+/****************************************************************************\
+* Function: ms_rungame
+*
+* Purpose: Executes an interpreter instruction
+*
+* Return: True if successful
+*
+* Note: You must call this function in a loop like this:
+*       while (running) {running=ms_rungame();}
+\****************************************************************************/
+
+extern type8 ms_rungame(void);
+
+/****************************************************************************\
+* Function: ms_freemen
+*
+* Purpose: Frees all allocated ressources
+\****************************************************************************/
+
+extern void ms_freemem(void);
+
+/****************************************************************************\
+* Function: ms_seed
+*
+* Purpose: Initializes the interpreter's random number generator with
+*          the given seed
+*
+* Parameter:    type32  seed    seed
+\****************************************************************************/
+
+extern void ms_seed(type32 seed);
+
+/****************************************************************************\
+* Function: ms_is_running
+*
+* Purpose: Detects if game is running
+*
+* Return: True, if game is currently running
+\****************************************************************************/
+
+extern type8 ms_is_running(void);
+
+/****************************************************************************\
+* Function: ms_is_magwin
+*
+* Purpose: Detects Magnetic Windows games (Wonderland, Collection)
+*
+* Return: True, if Magnetic Windows game
+\****************************************************************************/
+
+extern type8 ms_is_magwin(void);
+
+/****************************************************************************\
+* Function: ms_stop
+*
+* Purpose: Stops further processing of opcodes
+\****************************************************************************/
+
+extern void ms_stop(void);
+
+/****************************************************************************\
+* Function: ms_status
+*
+* Purpose: Dumps interperetr state to stderr, ie. registers
+\****************************************************************************/
+
+extern void ms_status(void);
+
+/****************************************************************************\
+* Function: ms_count
+*
+* Purpose: Returns the number of executed intructions
+*
+* Return:  Instruction count
+\****************************************************************************/
+
+extern type32 ms_count(void);
+
+extern void write(const char *fmt, ...);
+
+extern void writeChar(char c);
+
+extern int gms_startup_code(int argc, char *argv[]);
+
+extern void gms_main();
+
+} // End of namespace Magnetic
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/magnetic/emu.cpp b/engines/glk/magnetic/emu.cpp
index 1d63f59..fe6c66c 100644
--- a/engines/glk/magnetic/emu.cpp
+++ b/engines/glk/magnetic/emu.cpp
@@ -20,21 +20,336 @@
  *
  */
 
-#include "glk/magnetic/magnetic.h"
+#include "glk/magnetic/defs.h"
+#include "common/file.h"
 
 namespace Glk {
 namespace Magnetic {
 
-//static const char *no_hints = "[Hints are not available.]\n";
-//static const char *not_supported = "[This function is not supported.]\n";
+type32 dreg[8], areg[8], i_count, string_size, rseed = 0, pc, arg1i, mem_size;
+type16 properties, fl_sub, fl_tab, fl_size, fp_tab, fp_size;
+type8 zflag, nflag, cflag, vflag, byte1, byte2, regnr, admode, opsize;
+type8 *arg1, *arg2, is_reversible, running = 0, tmparg[4] = {0, 0, 0, 0};
+type8 lastchar = 0, version = 0, sd = 0;
+type8 *decode_table, *restart = 0, *code = 0, *string = 0, *string2 = 0;
+type8 *string3 = 0, *dict = 0;
+type8 quick_flag = 0, gfx_ver = 0, *gfx_buf = nullptr, *gfx_data = 0;
+type8 *gfx2_hdr = 0, *gfx2_buf = nullptr;
+const char *gfx2_name = nullptr;
+type16 gfx2_hsize = 0;
+Common::File *gfx_fp = nullptr;
+type8 *snd_buf = nullptr, *snd_hdr = nullptr;
+type16 snd_hsize = 0;
+Common::File *snd_fp = nullptr;
 
-int Magnetic::ms_init(bool restarting) {
-	byte header[42];
-	uint32 i, j, dict_size, string2_size, code_size, dec;
+const char *const undo_ok = "\n[Previous turn undone.]";
+const char *const undo_fail = "\n[You can't \"undo\" what hasn't been done!]";
+type32 undo_regs[2][18], undo_pc, undo_size;
+type8 *undo[2] = {0, 0}, undo_stat[2] = {0, 0};
+type16 gfxtable = 0, table_dist = 0;
+type16 v4_id = 0, next_table = 1;
 
-	ms_stop();
+#ifndef NO_ANIMATION
+
+struct picture anim_frame_table[MAX_ANIMS];
+type16 pos_table_size = 0;
+type16 pos_table_count[MAX_POSITIONS];
+struct ms_position pos_table[MAX_POSITIONS][MAX_ANIMS];
+type8 *command_table = 0;
+type16s command_index = -1;
+struct lookup anim_table[MAX_POSITIONS];
+type16s pos_table_index = -1;
+type16s pos_table_max = -1;
+struct ms_position pos_array[MAX_FRAMES];
+type8 anim_repeat = 0;
+
+#endif
+
+/* Hint support */
+struct ms_hint *hints = 0;
+type8 *hint_contents = 0;
+const char *const no_hints = "[Hints are not available.]\n";
+const char *const not_supported = "[This function is not supported.]\n";
+
+#if defined(LOGEMU) || defined(LOGGFX) || defined(LOGHNT)
+FILE *dbg_log;
+#ifndef LOG_FILE
+#error LOG_FILE must be defined to be the name of the log file.
+#endif
+#endif
+
+/* prototypes */
+type32 read_reg(int, int);
+void write_reg(int, int, type32);
+
+#define MAX_STRING_SIZE  0xFF00
+#define MAX_PICTURE_SIZE 0xC800
+#define MAX_MUSIC_SIZE   0x4E20
+
+#ifdef LOGEMU
+void out(char *format, ...) {
+	va_list a;
+
+	va_start(a, format);
+	vfprintf(dbg_log, format, a);
+	va_end(a);
+}
+#endif
+#if defined(LOGGFX) || defined(LOGHNT)
+void out2(char *format, ...) {
+	va_list a;
+
+	va_start(a, format);
+	vfprintf(dbg_log, format, a);
+	va_end(a);
+	fflush(dbg_log);
+}
+#endif
+
+/* Convert virtual pointer to effective pointer */
+type8 *effective(type32 ptr) {
+	if ((version < 4) && (mem_size == 0x10000))
+		return &(code[ptr & 0xffff]);
+	if (ptr >= mem_size) {
+		ms_fatal("Outside memory experience");
+		return code;
+	}
+
+	return &(code[ptr]);
+}
+
+type32 read_l(type8 *ptr) {
+
+	return (type32)((type32) ptr[0] << 24 | (type32) ptr[1] << 16 | (type32) ptr[2] << 8 | (type32) ptr[3]);
+}
+
+type16 read_w(type8 *ptr) {
+	return (type16)(ptr[0] << 8 | ptr[1]);
+}
+
+void write_l(type8 *ptr, type32 val) {
+	ptr[3] = (type8) val;
+	val >>= 8;
+	ptr[2] = (type8) val;
+	val >>= 8;
+	ptr[1] = (type8) val;
+	val >>= 8;
+	ptr[0] = (type8) val;
+}
+
+void write_w(type8 *ptr, type16 val) {
+	ptr[1] = (type8) val;
+	val >>= 8;
+	ptr[0] = (type8) val;
+}
+
+type32 read_l2(type8 *ptr) {
+	return ((type32) ptr[1] << 24 | (type32) ptr[0] << 16 | (type32) ptr[3] << 8 | (type32) ptr[2]);
+}
+
+type16 read_w2(type8 *ptr) {
+	return (type16)(ptr[1] << 8 | ptr[0]);
+}
+
+/* Standard rand - for equal cross-platform behaviour */
+
+void ms_seed(type32 seed) {
+	rseed = seed;
+}
+
+type32 rand_emu(void) {
+	rseed = 1103515245L * rseed + 12345L;
+	return rseed & 0x7fffffffL;
+}
+
+void ms_freemem(void) {
+	if (code)
+		free(code);
+	if (string)
+		free(string);
+	if (string2)
+		free(string2);
+	if (string3)
+		free(string3);
+	if (dict)
+		free(dict);
+	if (undo[0])
+		free(undo[0]);
+	if (undo[1])
+		free(undo[1]);
+	if (restart)
+		free(restart);
+	code = string = string2 = string3 = dict = undo[0] = undo[1] = restart = 0;
+	if (gfx_data)
+		free(gfx_data);
+	if (gfx_buf)
+		free(gfx_buf);
+	if (gfx2_hdr)
+		free(gfx2_hdr);
+	if (gfx2_buf)
+		free(gfx2_buf);
+	delete gfx_fp;
+
+	gfx_data = gfx_buf = gfx2_hdr = gfx2_buf = nullptr;
+	gfx2_name = 0;
+	gfx_fp = nullptr;
+	gfx_ver = 0;
+	gfxtable = table_dist = 0;
+#ifndef NO_ANIMATION
+	pos_table_size = 0;
+	command_index = 0;
+	anim_repeat = 0;
+	pos_table_index = -1;
+	pos_table_max = -1;
+#endif
+	lastchar = 0;
+	if (hints)
+		free(hints);
+	if (hint_contents)
+		free(hint_contents);
+	hints = 0;
+	hint_contents = 0;
+	if (snd_hdr)
+		free(snd_hdr);
+	if (snd_buf)
+		free(snd_buf);
+	snd_hdr = nullptr;
+	snd_hsize = 0;
+	snd_buf = nullptr;
+}
+
+type8 ms_is_running(void) {
+	return running;
+}
+
+type8 ms_is_magwin(void) {
+	return (version == 4) ? 1 : 0;
+}
+
+void ms_stop(void) {
+	running = 0;
+}
+
+type8 init_gfx1(type8 *header) {
+#ifdef SAVEMEM
+	type32 i;
+#endif
+
+	if (!(gfx_buf = (type8 *)malloc(MAX_PICTURE_SIZE))) {
+		delete gfx_fp;
+		gfx_fp = nullptr;
+		return 1;
+	}
+#ifdef SAVEMEM
+	if (!(gfx_data = (type8 *)malloc(128))) {
+#else
+	if (!(gfx_data = (type8 *)malloc(read_l(header + 4) - 8))) {
+#endif
+		free(gfx_buf);
+		delete gfx_fp;
+		gfx_buf = nullptr;
+		gfx_fp = nullptr;
+		return 1;
+	}
+#ifdef SAVEMEM
+	if (!fp.read(gfx_data, 128, 1, gfx_fp)) {
+#else
+	if (gfx_fp->read(gfx_data, read_l(header + 4)) != read_l(header + 4)) {
+#endif
+		free(gfx_data);
+		free(gfx_buf);
+		delete gfx_fp;
+		gfx_data = gfx_buf = nullptr;
+		gfx_fp = nullptr;
+		return 1;
+	}
+
+#ifdef SAVEMEM
+	for (i = 0; i < 128; i += 4)
+		if (!read_l(gfx_data + i))
+			write_l(gfx_data + i, read_l(header + 4));
+#else
+	delete gfx_fp;
+	gfx_fp = nullptr;
+#endif
+
+	gfx_ver = 1;
+	return 2;
+}
+
+type8 init_gfx2(type8 *header) {
+	if (!(gfx_buf = (type8 *)malloc(MAX_PICTURE_SIZE))) {
+		delete gfx_fp;
+		gfx_fp = nullptr;
+		return 1;
+	}
+
+	gfx2_hsize = read_w(header + 4);
+	if (!(gfx2_hdr = (type8 *)malloc(gfx2_hsize))) {
+		free(gfx_buf);
+		delete gfx_fp;
+		gfx_buf = nullptr;
+		gfx_fp = nullptr;
+		return 1;
+	}
+
+	gfx_fp->seek(6);
+	if (gfx_fp->read(gfx2_hdr, gfx2_hsize) != gfx2_hsize) {
+		free(gfx_buf);
+		free(gfx2_hdr);
+		delete gfx_fp;
+		gfx_buf = nullptr;
+		gfx2_hdr = 0;
+		gfx_fp = nullptr;
+		return 1;
+	}
+
+	gfx_ver = 2;
+	return 2;
+}
+
+type8 init_snd(type8 *header) {
+	if (!(snd_buf = (type8 *)malloc(MAX_MUSIC_SIZE))) {
+		delete snd_fp;
+		snd_fp = nullptr;
+		return 1;
+	}
+
+	snd_hsize = read_w(header + 4);
+	if (!(snd_hdr = (type8 *)malloc(snd_hsize))) {
+		free(snd_buf);
+		delete snd_fp;
+		snd_buf = nullptr;
+		snd_fp = nullptr;
+		return 1;
+	}
+
+	snd_fp->seek(6);
+	if (snd_fp->read(snd_hdr, snd_hsize) != snd_hsize) {
+		free(snd_buf);
+		free(snd_hdr);
+		delete snd_fp;
+		snd_buf = nullptr;
+		snd_hdr = nullptr;
+		snd_fp = nullptr;
+		return 1;
+	}
+
+	return 2;
+}
+
+/* zero all registers and flags and load the game */
 
-	if (restarting) {
+type8 ms_init(const char *name, const char *gfxname, const char *hntname, const char *sndname) {
+	Common::File fp;
+	type8 header[42], header2[8], header3[4];
+	type32 i, dict_size, string2_size, code_size, dec;
+
+#if defined(LOGEMU) || defined(LOGGFX) || defined(LOGHNT)
+	dbg_log = fopen(LOG_FILE, "wt");
+#endif
+	ms_stop();
+	if (!name) {
 		if (!restart)
 			return 0;
 		else {
@@ -45,221 +360,3281 @@ int Magnetic::ms_init(bool restarting) {
 	} else {
 		undo_stat[0] = undo_stat[1] = 0;
 
-		if (_gameFile.read(header, 42) != 42 || READ_BE_UINT32(header) != MKTAG('M', 'a', 'S', 'c')
-			|| READ_LE_UINT32(header + 8) != 42)
+		if (!fp.open(name))
+			return 0;
+		if ((fp.read(header, 42) != 42) || (read_l(header) != 0x4d615363))
+			return 0;
+		if (read_l(header + 8) != 42)
 			return 0;
 
 		ms_freemem();
 		version = header[13];
-		code_size = READ_LE_UINT32(header + 14);
-		string_size = READ_LE_UINT32(header + 18);
-		string2_size = READ_LE_UINT32(header + 22);
-		dict_size = READ_LE_UINT32(header + 26);
-		undo_size = READ_LE_UINT32(header + 34);
-		undo_pc = READ_LE_UINT32(header + 38);
+		code_size = read_l(header + 14);
+		string_size = read_l(header + 18);
+		string2_size = read_l(header + 22);
+		dict_size = read_l(header + 26);
+		undo_size = read_l(header + 34);
+		undo_pc = read_l(header + 38);
 
 		if ((version < 4) && (code_size < 65536))
 			mem_size = 65536;
 		else
 			mem_size = code_size;
 
-		sd = (byte)((dict_size != 0L) ? 1 : 0);			// if (sd) => separate dict
+		/* Some C libraries don't like malloc(0), so make
+		   sure that undo_size is always positive. */
+		if (undo_size == 0)
+			undo_size = 8;
 
-		if (!(code = new byte[mem_size]) || !(string2 = new byte[string2_size]) ||
-			!(restart = new byte[undo_size]) || (sd &&
-				!(dict = new byte[dict_size]))) {
+		sd = (type8)((dict_size != 0L) ? 1 : 0); /* if (sd) => separate dict */
+
+		if (!(code = (type8 *)malloc(mem_size)) || !(string2 = (type8 *)malloc(string2_size)) ||
+		        !(restart = (type8 *)malloc(undo_size)) || (sd &&
+		                !(dict = (type8 *)malloc(dict_size)))) {
 			ms_freemem();
+			fp.close();
 			return 0;
 		}
 		if (string_size > MAX_STRING_SIZE) {
-			if (!(string = new byte[MAX_STRING_SIZE]) ||
-				!(string3 = new byte[string_size - MAX_STRING_SIZE])) {
+			if (!(string = (type8 *)malloc(MAX_STRING_SIZE)) ||
+			        !(string3 = (type8 *)malloc(string_size - MAX_STRING_SIZE))) {
 				ms_freemem();
+				fp.close();
 				return 0;
 			}
 		} else {
-			if (!(string = new byte[string_size])) {
+			if (!(string = (type8 *)malloc(string_size))) {
 				ms_freemem();
+				fp.close();
 				return 0;
 			}
 		}
-		if (!(undo[0] = new byte[undo_size]) || !(undo[1] = new byte[undo_size])) {
+		if (!(undo[0] = (type8 *)malloc(undo_size)) || !(undo[1] = (type8 *)malloc(undo_size))) {
 			ms_freemem();
+			fp.close();
 			return 0;
 		}
-		if (_gameFile.read(code, code_size) != code_size) {
+		if (fp.read(code, code_size) != code_size) {
 			ms_freemem();
+			fp.close();
 			return 0;
 		}
-
-		memcpy(restart, code, undo_size);	// fast restarts
+		memcpy(restart, code, undo_size);   /* fast restarts */
 		if (string_size > MAX_STRING_SIZE) {
-			if (_gameFile.read(string, MAX_STRING_SIZE) != MAX_STRING_SIZE) {
+			if (fp.read(string, MAX_STRING_SIZE) != MAX_STRING_SIZE) {
 				ms_freemem();
+				fp.close();
 				return 0;
 			}
-			if (_gameFile.read(string3, string_size - MAX_STRING_SIZE) != (string_size - MAX_STRING_SIZE)) {
+			if (fp.read(string3, string_size - MAX_STRING_SIZE) != string_size - MAX_STRING_SIZE) {
 				ms_freemem();
+				fp.close();
 				return 0;
 			}
 		} else {
-			if (_gameFile.read(string, string_size) != string_size) {
+			if (fp.read(string, string_size) != string_size) {
 				ms_freemem();
+				fp.close();
 				return 0;
 			}
 		}
-		if (_gameFile.read(string2, string2_size) != string2_size) {
+		if (fp.read(string2, string2_size) != string2_size) {
 			ms_freemem();
+			fp.close();
 			return 0;
 		}
-		if (sd && _gameFile.read(dict, dict_size) != dict_size) {
+		if (sd && fp.read(dict, dict_size) != dict_size)  {
 			ms_freemem();
+			fp.close();
 			return 0;
 		}
-
-		dec = READ_LE_UINT32(header + 30);
-		if (dec >= string_size) {
+		dec = read_l(header + 30);
+		if (dec >= string_size)
 			decode_table = string2 + dec - string_size;
-		} else {
+		else {
 			if (dec >= MAX_STRING_SIZE)
 				decode_table = string3 + dec - MAX_STRING_SIZE;
 			else
 				decode_table = string + dec;
 		}
+		fp.close();
 	}
 
 	for (i = 0; i < 8; i++)
 		dreg[i] = areg[i] = 0;
-	write_reg(8 + 7, 2, 0xfffe);	// Stack-pointer, -2 due to MS-DOS segments
+	write_reg(8 + 7, 2, 0xfffe);    /* Stack-pointer, -2 due to MS-DOS segments */
 	pc = 0;
 	zflag = nflag = cflag = vflag = 0;
 	i_count = 0;
 	running = 1;
 
-	if (restarting)
-		return (byte)(gfx_buf ? 2 : 1);		// Restarted
+	if (!name)
+		return (type8)(gfx_buf ? 2 : 1);    /* Restarted */
 
 	if (version == 4) {
-		// Try loading a hint file
-		if (_hintFile.isOpen()) {
-			_hintFile.seek(0);
-
-			if (_hintFile.readUint32BE() == MKTAG('M', 'a', 'H', 't')) {
-				uint16 blkcnt, elcnt, ntype, elsize, conidx;
-
-				// Allocate memory for hints
-				hints = new ms_hint[MAX_HINTS];
-				hint_contents = new byte[MAX_HCONTENTS];
+		/* Try loading a hint file */
+		Common::File hnt_fp;
+		if (hntname && hnt_fp.open(hntname)) {
+			if ((hnt_fp.read(&header3, 4) == 4) && (read_l(header3) == 0x4D614874)) {
+				type8 buf[8];
+				type16 j, blkcnt, elcnt, ntype, elsize, conidx;
 
+				/* Allocate memory for hints */
+				hints = (ms_hint *)malloc(MAX_HINTS * sizeof(struct ms_hint));
+				hint_contents = (type8 *)malloc(MAX_HCONTENTS);
 				if ((hints != 0) && (hint_contents != 0)) {
-					// Read number of blocks
-					blkcnt = _hintFile.readUint16LE();
-
+					/* Read number of blocks */
+					if (hnt_fp.read(&buf, 2) != 2 && !hnt_fp.eos())
+						return 0;
+					blkcnt = read_w2(buf);
+#ifdef LOGHNT
+					out2("Blocks: %d\n", blkcnt);
+#endif
 					conidx = 0;
 					for (i = 0; i < blkcnt; i++) {
-						// Read number of elements
-						elcnt = _hintFile.readUint16LE();
+#ifdef LOGHNT
+						out2("\nBlock No. %d\n", i);
+#endif
+						/* Read number of elements */
+						if (hnt_fp.read(&buf, 2) != 2 && !hnt_fp.eos()) return 0;
+						elcnt = read_w2(buf);
+#ifdef LOGHNT
+						out2("Elements: %d\n", elcnt);
+#endif
 						hints[i].elcount = elcnt;
 
-						// Read node type
-						ntype = _hintFile.readUint16LE();
+						/* Read node type */
+						if (hnt_fp.read(&buf, 2) != 2 && !hnt_fp.eos()) return 0;
+						ntype = read_w2(buf);
+#ifdef LOGHNT
+						if (ntype == 1)
+							out2("Type: Node\n");
+						else
+							out2("Type: Leaf\n");
+#endif
 						hints[i].nodetype = ntype;
-						hints[i].content = hint_contents + conidx;
-
+						hints[i].content = (const char *)hint_contents + conidx;
+#ifdef LOGHNT
+						out2("Elements:\n");
+#endif
 						for (j = 0; j < elcnt; j++) {
-							elsize = _hintFile.readUint16LE();
-							if (_hintFile.read(hint_contents + conidx, elsize) != elsize || _hintFile.eos())
-								return 0;
+							if (hnt_fp.read(&buf, 2) != 2 && !hnt_fp.eos()) return 0;
+							elsize = read_w2(buf);
+							if (hnt_fp.read(hint_contents + conidx, elsize) != elsize && !hnt_fp.eos()) return 0;
 							hint_contents[conidx + elsize - 1] = '\0';
-
+#ifdef LOGHNT
+							out2("%s\n", hint_contents + conidx);
+#endif
 							conidx += elsize;
 						}
 
-						// Do we need a jump table?
+						/* Do we need a jump table? */
 						if (ntype == 1) {
+#ifdef LOGHNT
+							out2("Jump to block:\n");
+#endif
 							for (j = 0; j < elcnt; j++) {
-								hints[i].links[j] = _hintFile.readUint16LE();
+								if (hnt_fp.read(&buf, 2) != 2 && !hnt_fp.eos()) return 0;
+								hints[i].links[j] = read_w2(buf);
+#ifdef LOGHNT
+								out2("%d\n", hints[i].links[j]);
+#endif
 							}
 						}
 
-						// Read the parent block
-						hints[i].parent = _hintFile.readUint16LE();
-
+						/* Read the parent block */
+						if (hnt_fp.read(&buf, 2) != 2 && !hnt_fp.eos()) return 0;
+						hints[i].parent = read_w2(buf);
+#ifdef LOGHNT
+						out2("Parent: %d\n", hints[i].parent);
+#endif
 					}
 				} else {
-					delete[] hints;
-					delete[] hint_contents;
-					hints = nullptr;
-					hint_contents = nullptr;
+					if (hints)
+						free(hints);
+					if (hint_contents)
+						free(hint_contents);
+					hints = 0;
+					hint_contents = 0;
 				}
 			}
+			hnt_fp.close();
 		}
 
-		// Try loading a music file
-		if (_sndFile.isOpen() && _sndFile.size() >= 8) {
-			_sndFile.seek(0);
-
-			if (_sndFile.readUint32BE() != MKTAG('M', 'a', 'S', 'd'))
-				return 0;
-
-			init_snd(_sndFile.readUint32LE());
+		/* Try loading a music file */
+		snd_fp = new Common::File();
+		if (sndname && snd_fp->open(sndname)) {
+			if (snd_fp->read(&header2, 8) != 8) {
+				delete snd_fp;
+				snd_fp = nullptr;
+			} else {
+				if (read_l(header2) == 0x4D615364) { /* MaSd */
+					init_snd(header2);
+#ifdef LOGSND
+					out2("Sound file loaded.\n");
+#endif
+				}
+			}
+		} else {
+			delete snd_fp;
+			snd_fp = nullptr;
 		}
 	}
 
-	if (!_gfxFile.isOpen() || _gfxFile.size() < 8)
+	if (!gfxname)
+		return 1;
+
+	gfx_fp = new Common::File();
+	if (!gfx_fp->open(gfxname) || gfx_fp->read(&header2, 8) != 8) {
+		delete gfx_fp;
+		gfx_fp = nullptr;
 		return 1;
-	_gfxFile.seek(0);
-	uint tag = _gfxFile.readUint32BE();
+	}
 
-	if (version < 4 && tag == MKTAG('M', 'a', 'P', 'i'))
-		return init_gfx1(_gfxFile.readUint32LE() - 8);
-	else if (version == 4 && tag == MKTAG('M', 'a', 'P', '2'))
-		return init_gfx2(_gfxFile.readUint16LE());
+	if (version < 4 && read_l(header2) == 0x4D615069) /* MaPi */
+		return init_gfx1(header2);
+	else if (version == 4 && read_l(header2) == 0x4D615032) /* MaP2 */
+		return init_gfx2(header2);
+	delete gfx_fp;
+	gfx_fp = nullptr;
+	return 1;
+}
 
+type8 is_blank(type16 line, type16 width) {
+	type32s i;
+
+	for (i = line * width; i < (line + 1) * width; i++)
+		if (gfx_buf[i])
+			return 0;
 	return 1;
 }
 
-void Magnetic::ms_freemem() {
-	delete[] code;
-	delete[] string;
-	delete[] string2;
-	delete[] string3;
-	delete[] dict;
-	delete[] undo[0];
-	delete[] undo[1];
-	delete[] restart;
-	code = string = string2 = string3 = dict = nullptr;
-	undo[0] = undo[1] = restart = nullptr;
+type8 *ms_extract1(type8 pic, type16 *w, type16 *h, type16 *pal) {
+	type8 *decodeTable, *data, bit, val, *buffer;
+	type16 tablesize, count;
+	type32 i, j, upsize, offset;
 
-	delete[] gfx_data;
-	delete[] gfx_buf;
-	delete[] gfx2_hdr;
-	delete[] gfx2_buf;
-	gfx_data = gfx_buf = gfx2_hdr = gfx2_buf = 0;
+	offset = read_l(gfx_data + 4 * pic);
+#ifdef SAVEMEM
+	type32 datasize;
 
-	gfx_fp.close();
+	if (fseek(gfx_fp, offset, SEEK_SET) < 0)
+		return 0;
+	datasize = read_l(gfx_data + 4 * (pic + 1)) - offset;
+	if (!(buffer = (type8 *)malloc(datasize)))
+		return 0;
+	if (fp.read(buffer, 1, datasize, gfx_fp) != datasize)
+		return 0;
+#else
+	buffer = gfx_data + offset - 8;
+#endif
 
-	gfx2_name.clear();
-	gfx_ver = 0;
-	gfxtable = table_dist = 0;
+	for (i = 0; i < 16; i++)
+		pal[i] = read_w(buffer + 0x1c + 2 * i);
+	w[0] = (type16)(read_w(buffer + 4) - read_w(buffer + 2));
+	h[0] = read_w(buffer + 6);
+
+	tablesize = read_w(buffer + 0x3c);
+	//datasize = read_l(buffer + 0x3e);
+	decodeTable = buffer + 0x42;
+	data = decodeTable + tablesize * 2 + 2;
+	upsize = h[0] * w[0];
+
+	for (i = 0, j = 0, count = 0, val = 0, bit = 7; i < upsize; i++, count--) {
+		if (!count) {
+			count = tablesize;
+			while (count < 0x80) {
+				if (data[j] & (1 << bit))
+					count = decodeTable[2 * count];
+				else
+					count = decodeTable[2 * count + 1];
+				if (!bit)
+					j++;
+				bit = (type8)(bit ? bit - 1 : 7);
+			}
+			count &= 0x7f;
+			if (count >= 0x10)
+				count -= 0x10;
+			else {
+				val = (type8)count;
+				count = 1;
+			}
+		}
+		gfx_buf[i] = val;
+	}
+	for (j = w[0]; j < upsize; j++)
+		gfx_buf[j] ^= gfx_buf[j - w[0]];
+
+#ifdef SAVEMEM
+	free(buffer);
+#endif
+	for (; h[0] > 0 && is_blank((type16)(h[0] - 1), w[0]); h[0]--);
+	for (i = 0; h[0] > 0 && is_blank((type16)i, w[0]); h[0]--, i++);
+	return gfx_buf + i * w[0];
+}
+
+type16s find_name_in_header(const char *name, type8 upper) {
+	type16s header_pos = 0;
+	char pic_name[8];
+	type8 i;
+
+	for (i = 0; i < 8; i++)
+		pic_name[i] = 0;
+	strncpy(pic_name, name, 6);
+	if (upper) {
+		for (i = 0; i < 8; i++)
+			pic_name[i] = (char)toupper(pic_name[i]);
+	}
+
+	while (header_pos < gfx2_hsize) {
+		const char *hname = (const char *)(gfx2_hdr + header_pos);
+		if (strncmp(hname, pic_name, 6) == 0)
+			return header_pos;
+		header_pos += 16;
+	}
+	return -1;
+}
+
+void extract_frame(struct picture *pic) {
+	type32 i, x, y, bit_x, mask, ywb, yw, value, values[4];
+
+	if (pic->width * pic->height > MAX_PICTURE_SIZE) {
+		ms_fatal("picture too large");
+		return;
+	}
+
+	for (y = 0; y < pic->height; y++) {
+		ywb = y * pic->wbytes;
+		yw = y * pic->width;
+
+		for (x = 0; x < pic->width; x++) {
+			if ((x % 8) == 0) {
+				for (i = 0; i < 4; i++)
+					values[i] = pic->data[ywb + (x / 8) + (pic->plane_step * i)];
+			}
+
+			bit_x = 7 - (x & 7);
+			mask = 1 << bit_x;
+			value = ((values[0] & mask) >> bit_x) << 0 |
+			        ((values[1] & mask) >> bit_x) << 1 |
+			        ((values[2] & mask) >> bit_x) << 2 |
+			        ((values[3] & mask) >> bit_x) << 3;
+			value &= 15;
+
+			gfx_buf[yw + x] = (type8)value;
+		}
+	}
+}
 
+type8 *ms_extract2(const char *name, type16 *w, type16 *h, type16 *pal, type8 *is_anim) {
+	struct picture main_pic;
+	type32 offset = 0, length = 0, i;
+	type16s header_pos = -1;
+#ifndef NO_ANIMATION
+	type8 *anim_data;
+	type32 j;
+#endif
+
+	if (is_anim != 0)
+		*is_anim = 0;
+	gfx2_name = name;
+
+#ifndef NO_ANIMATION
 	pos_table_size = 0;
-	command_index = 0;
-	anim_repeat = 0;
-	pos_table_index = -1;
-	pos_table_max = -1;
+#endif
 
-	lastchar = 0;
-	delete[] hints;
-	delete[] hint_contents;
-	hints = nullptr;
-	hint_contents = nullptr;
+#ifdef NO_ANIMATION
+	/* Find the uppercase (no animation) version of the picture first. */
+	header_pos = find_name_in_header(name, 1);
+#endif
+	if (header_pos < 0)
+		header_pos = find_name_in_header(name, 0);
+	if (header_pos < 0)
+		return 0;
 
-	delete[] snd_hdr;
-	delete[] snd_buf;
-	snd_hdr = nullptr;
-	snd_hsize = 0;
-	snd_buf = nullptr;
+	offset = read_l(gfx2_hdr + header_pos + 8);
+	length = read_l(gfx2_hdr + header_pos + 12);
+
+	if (offset != 0) {
+		if (gfx2_buf) {
+			free(gfx2_buf);
+			gfx2_buf = nullptr;
+		}
+
+		gfx2_buf = (type8 *)malloc(length);
+		if (!gfx2_buf)
+			return 0;
+
+		if (!gfx_fp->seek(offset)) {
+			free(gfx2_buf);
+			gfx2_buf = nullptr;
+			return 0;
+		}
+
+		if (gfx_fp->read(gfx2_buf, length) != length) {
+			free(gfx2_buf);
+			gfx2_buf = nullptr;
+			return 0;
+		}
+
+		for (i = 0; i < 16; i++)
+			pal[i] = read_w2(gfx2_buf + 4 + (2 * i));
+
+		main_pic.data = gfx2_buf + 48;
+		main_pic.data_size = read_l2(gfx2_buf + 38);
+		main_pic.width = read_w2(gfx2_buf + 42);
+		main_pic.height = read_w2(gfx2_buf + 44);
+		main_pic.wbytes = (type16)(main_pic.data_size / main_pic.height);
+		main_pic.plane_step = (type16)(main_pic.wbytes / 4);
+		main_pic.mask = (type8 *)0;
+		extract_frame(&main_pic);
+
+		*w = main_pic.width;
+		*h = main_pic.height;
+
+#ifndef NO_ANIMATION
+		/* Check for an animation */
+		anim_data = gfx2_buf + 48 + main_pic.data_size;
+		if ((anim_data[0] != 0xD0) || (anim_data[1] != 0x5E)) {
+			type8 *current;
+			type16 frame_count;
+			type16 value1, value2;
+
+			if (is_anim != 0)
+				*is_anim = 1;
+
+			current = anim_data + 6;
+			frame_count = read_w2(anim_data + 2);
+			if (frame_count > MAX_ANIMS) {
+				ms_fatal("animation frame array too short");
+				return 0;
+			}
+
+			/* Loop through each animation frame */
+			for (i = 0; i < frame_count; i++) {
+				anim_frame_table[i].data = current + 10;
+				anim_frame_table[i].data_size = read_l2(current);
+				anim_frame_table[i].width = read_w2(current + 4);
+				anim_frame_table[i].height = read_w2(current + 6);
+				anim_frame_table[i].wbytes = (type16)(anim_frame_table[i].data_size / anim_frame_table[i].height);
+				anim_frame_table[i].plane_step = (type16)(anim_frame_table[i].wbytes / 4);
+				anim_frame_table[i].mask = (type8 *)0;
+
+				current += anim_frame_table[i].data_size + 12;
+				value1 = read_w2(current - 2);
+				value2 = read_w2(current);
+
+				/* Get the mask */
+				if ((value1 == anim_frame_table[i].width) && (value2 == anim_frame_table[i].height)) {
+					type16 skip;
+
+					anim_frame_table[i].mask = (type8 *)(current + 4);
+					skip = read_w2(current + 2);
+					current += skip + 6;
+				}
+			}
+
+			/* Get the positioning tables */
+			pos_table_size = read_w2(current - 2);
+			if (pos_table_size > MAX_POSITIONS) {
+				ms_fatal("animation position array too short");
+				return 0;
+			}
+
+#ifdef LOGGFX_EXT
+			out2("POSITION TABLE DUMP\n");
+#endif
+			for (i = 0; i < pos_table_size; i++) {
+				pos_table_count[i] = read_w2(current + 2);
+				current += 4;
+
+				if (pos_table_count[i] > MAX_ANIMS) {
+					ms_fatal("animation position array too short");
+					return 0;
+				}
+
+				for (j = 0; j < pos_table_count[i]; j++) {
+					pos_table[i][j].x = read_w2(current);
+					pos_table[i][j].y = read_w2(current + 2);
+					pos_table[i][j].number = read_w2(current + 4) - 1;
+					current += 8;
+#ifdef LOGGFX_EXT
+					out2("Position entry: Table: %d  Entry: %d  X: %d Y: %d Frame: %d\n",
+					     i, j, pos_table[i][j].x, pos_table[i][j].y, pos_table[i][j].number);
+#endif
+				}
+			}
+
+			/* Get the command sequence table */
+			//command_count = read_w2(current);
+			command_table = current + 2;
+
+			for (i = 0; i < MAX_POSITIONS; i++) {
+				anim_table[i].flag = -1;
+				anim_table[i].count = -1;
+			}
+			command_index = 0;
+			anim_repeat = 0;
+			pos_table_index = -1;
+			pos_table_max = -1;
+		}
+#endif
+		return gfx_buf;
+	}
+	return 0;
+}
+
+type8 *ms_extract(type32 pic, type16 *w, type16 *h, type16 *pal, type8 *is_anim) {
+	if (is_anim)
+		*is_anim = 0;
+
+	if (gfx_buf) {
+		switch (gfx_ver) {
+		case 1:
+			return ms_extract1((type8)pic, w, h, pal);
+		case 2:
+			return ms_extract2((const char *)(code + pic), w, h, pal, is_anim);
+		default:
+			break;
+		}
+	}
+	return 0;
+}
+
+type8 ms_animate(struct ms_position **positions, type16 *count) {
+#ifndef NO_ANIMATION
+	type8 got_anim = 0;
+	type16 i, j, ttable;
+
+	if ((gfx_buf == 0) || (gfx2_buf == 0) || (gfx_ver != 2))
+		return 0;
+	if ((pos_table_size == 0) || (command_index < 0))
+		return 0;
+
+	*count = 0;
+	*positions = (struct ms_position *)0;
+
+	while (got_anim == 0) {
+		if (pos_table_max >= 0) {
+			if (pos_table_index < pos_table_max) {
+				for (i = 0; i < pos_table_size; i++) {
+					if (anim_table[i].flag > -1) {
+						if (*count >= MAX_FRAMES) {
+							ms_fatal("returned animation array too short");
+							return 0;
+						}
+
+						pos_array[*count] = pos_table[i][anim_table[i].flag];
+#ifdef LOGGFX_EXT
+						out2("Adding frame %d to buffer\n", pos_array[*count].number);
+#endif
+						(*count)++;
+
+						if (anim_table[i].flag < (pos_table_count[i] - 1))
+							anim_table[i].flag++;
+						if (anim_table[i].count > 0)
+							anim_table[i].count--;
+						else
+							anim_table[i].flag = -1;
+					}
+				}
+				if (*count > 0) {
+					*positions = pos_array;
+					got_anim = 1;
+				}
+				pos_table_index++;
+			}
+		}
+
+		if (got_anim == 0) {
+			type8 command = command_table[command_index];
+			command_index++;
+
+			pos_table_max = -1;
+			pos_table_index = -1;
+
+			switch (command) {
+			case 0x00:
+				command_index = -1;
+				return 0;
+			case 0x01:
+#ifdef LOGGFX_EXT
+				out2("Load Frame Table: %d  Start at: %d  Count: %d\n",
+				     command_table[command_index], command_table[command_index + 1],
+				     command_table[command_index + 2]);
+#endif
+				ttable = command_table[command_index];
+				command_index++;
+
+				if (ttable - 1 >= MAX_POSITIONS) {
+					ms_fatal("animation table too short");
+					return 0;
+				}
+
+				anim_table[ttable - 1].flag = (type16s)(command_table[command_index] - 1);
+				command_index++;
+				anim_table[ttable - 1].count = (type16s)(command_table[command_index] - 1);
+				command_index++;
+
+				/* Workaround for Wonderland "catter" animation */
+				if (v4_id == 0) {
+					if (strcmp(gfx2_name, "catter") == 0) {
+						if (command_index == 96)
+							anim_table[ttable - 1].count = 9;
+						if (command_index == 108)
+							anim_table[ttable - 1].flag = -1;
+						if (command_index == 156)
+							anim_table[ttable - 1].flag = -1;
+					}
+				}
+				break;
+			case 0x02:
+#ifdef LOGGFX_EXT
+				out2("Animate: %d\n", command_table[command_index]);
+#endif
+				pos_table_max = command_table[command_index];
+				pos_table_index = 0;
+				command_index++;
+				break;
+			case 0x03:
+#ifdef LOGGFX_EXT
+				out2("Stop/Repeat Param: %d\n", command_table[command_index]);
+				command_index = -1;
+				return 0;
+#else
+				if (v4_id == 0) {
+					command_index = -1;
+					return 0;
+				} else {
+					command_index = 0;
+					anim_repeat = 1;
+					pos_table_index = -1;
+					pos_table_max = -1;
+					for (j = 0; j < MAX_POSITIONS; j++) {
+						anim_table[j].flag = -1;
+						anim_table[j].count = -1;
+					}
+				}
+				break;
+#endif
+			case 0x04:
+#ifdef LOGGFX_EXT
+				out2("Unknown Command: %d Prop1: %d  Prop2: %d WARNING:not parsed\n", command,
+				     command_table[command_index], command_table[command_index + 1]);
+#endif
+				command_index += 3;
+				return 0;
+			case 0x05:
+				ttable = next_table;
+				command_index++;
+
+				anim_table[ttable - 1].flag = 0;
+				anim_table[ttable - 1].count = command_table[command_index];
+
+				pos_table_max = command_table[command_index];
+				pos_table_index = 0;
+				command_index++;
+				command_index++;
+				next_table++;
+				break;
+			default:
+				ms_fatal("unknown animation command");
+				command_index = -1;
+				return 0;
+			}
+		}
+	}
+#ifdef LOGGFX_EXT
+	out2("ms_animate() returning %d frames\n", *count);
+#endif
+	return got_anim;
+#else
+	return 0;
+#endif
+}
+
+type8 *ms_get_anim_frame(type16s number, type16 *width, type16 *height, type8 **mask) {
+#ifndef NO_ANIMATION
+	if (number >= 0) {
+		extract_frame(anim_frame_table + number);
+		*width = anim_frame_table[number].width;
+		*height = anim_frame_table[number].height;
+		*mask = anim_frame_table[number].mask;
+		return gfx_buf;
+	}
+#endif
+	return 0;
+}
+
+type8 ms_anim_is_repeating(void) {
+#ifndef NO_ANIMATION
+	return anim_repeat;
+#else
+	return 0;
+#endif
+}
+
+type16s find_name_in_sndheader(const char *name) {
+	type16s header_pos = 0;
+
+	while (header_pos < snd_hsize) {
+		const char *hname = (const char *)(snd_hdr + header_pos);
+		if (strcmp(hname, name) == 0)
+			return header_pos;
+		header_pos += 18;
+	}
+
+	return -1;
+}
+
+type8 *sound_extract(const char *name, type32 *length, type16 *tempo) {
+	type32 offset = 0;
+	type16s header_pos = -1;
+
+	if (header_pos < 0)
+		header_pos = find_name_in_sndheader(name);
+	if (header_pos < 0)
+		return 0;
+
+	*tempo = read_w(snd_hdr + header_pos + 8);
+	offset = read_l(snd_hdr + header_pos + 10);
+	*length = read_l(snd_hdr + header_pos + 14);
+
+	if (offset != 0) {
+		if (!snd_buf)
+			return 0;
+		if (!snd_fp->seek(offset) || snd_fp->read(snd_buf, *length) != *length)
+			return 0;
+
+		return snd_buf;
+	}
+
+	return nullptr;
+}
+
+void save_undo(void) {
+	type8 *tmp, i;
+	type32 tmp32;
+
+	tmp = undo[0];  /* swap buffers */
+	undo[0] = undo[1];
+	undo[1] = tmp;
+
+	for (i = 0; i < 18; i++) {
+		tmp32 = undo_regs[0][i];
+		undo_regs[0][i] = undo_regs[1][i];
+		undo_regs[1][i] = tmp32;
+	}
+
+	memcpy(undo[1], code, undo_size);
+	for (i = 0; i < 8; i++) {
+		undo_regs[1][i] = dreg[i];
+		undo_regs[1][8 + i] = areg[i];
+	}
+	undo_regs[1][16] = i_count;
+	undo_regs[1][17] = pc;  /* status flags intentionally omitted */
+
+	undo_stat[0] = undo_stat[1];
+	undo_stat[1] = 1;
+}
+
+type8 ms_undo(void) {
+	type8 i;
+
+	ms_flush();
+	if (!undo_stat[0])
+		return 0;
+
+	undo_stat[0] = undo_stat[1] = 0;
+	memcpy(code, undo[0], undo_size);
+	for (i = 0; i < 8; i++) {
+		dreg[i] = undo_regs[0][i];
+		areg[i] = undo_regs[0][8 + i];
+	}
+	i_count = undo_regs[0][16];
+	pc = undo_regs[0][17];  /* status flags intentionally omitted */
+	return 1;
+}
+
+#ifdef LOGEMU
+void log_status(void) {
+	int j;
+
+	fprintf(dbg_log, "\nD0:");
+	for (j = 0; j < 8; j++)
+		fprintf(dbg_log, " %8.8x", read_reg(j, 3));
+	fprintf(dbg_log, "\nA0:");
+	for (j = 0; j < 8; j++)
+		fprintf(dbg_log, " %8.8x", read_reg(8 + j, 3));
+	fprintf(dbg_log, "\nPC=%5.5x (%8.8x) ZCNV=%d%d%d%d - %d instructions\n\n",
+	        pc, code, zflag & 1, cflag & 1, nflag & 1, vflag & 1, i_count);
+}
+#endif
+
+void ms_status(void) {
+	int j;
+
+	Common::String s = "D0:";
+	for (j = 0; j < 8; j++)
+		s += Common::String::format(" %8.8lx", (long) read_reg(j, 3));
+	s += "\nA0:";
+
+	for (j = 0; j < 8; j++)
+		s += Common::String::format(" %8.8lx", (long) read_reg(8 + j, 3));
+	s += Common::String::format("\nPC=%5.5lx ZCNV=%d%d%d%d - %ld instructions\n",
+	                            (long) pc, zflag & 1, cflag & 1, nflag & 1, vflag & 1, (long) i_count);
+	warning("%s", s.c_str());
+}
+
+type32 ms_count(void) {
+	return i_count;
+}
+
+/* align register pointer for word/byte accesses */
+
+type8 *reg_align(type8 *ptr, type8 size) {
+	if (size == 1)
+		ptr += 2;
+	if (size == 0)
+		ptr += 3;
+	return ptr;
+}
+
+type32 read_reg(int i, int s) {
+	type8 *ptr;
+
+	if (i > 15) {
+		ms_fatal("invalid register in read_reg");
+		return 0;
+	}
+	if (i < 8)
+		ptr = (type8 *) & dreg[i];
+	else
+		ptr = (type8 *) & areg[i - 8];
+
+	switch (s) {
+	case 0:
+		return reg_align(ptr, 0)[0];
+	case 1:
+		return read_w(reg_align(ptr, 1));
+	default:
+		return read_l(ptr);
+	}
+}
+
+void write_reg(int i, int s, type32 val) {
+	type8 *ptr;
+
+	if (i > 15) {
+		ms_fatal("invalid register in write_reg");
+		return;
+	}
+	if (i < 8)
+		ptr = (type8 *) & dreg[i];
+	else
+		ptr = (type8 *) & areg[i - 8];
+
+	switch (s) {
+	case 0:
+		reg_align(ptr, 0)[0] = (type8)val;
+		break;
+	case 1:
+		write_w(reg_align(ptr, 1), (type16)val);
+		break;
+	default:
+		write_l(ptr, val);
+		break;
+	}
+}
+
+/* [35c4] */
+
+void char_out(type8 c) {
+	static type8 big = 0, period = 0, pipe = 0;
+
+	if (c == 0xff) {
+		big = 1;
+		return;
+	}
+	c &= 0x7f;
+	if (read_reg(3, 0)) {
+		if (c == 0x5f || c == 0x40)
+			c = 0x20;
+		if ((c >= 'a') && (c <= 'z'))
+			c &= 0xdf;
+		if (version < 4)
+			ms_statuschar(c);
+		return;
+	}
+	if (c == 0x5e)
+		c = 0x0a;
+	if (c == 0x40) {
+		if (read_reg(2, 0))
+			return;
+		else
+			c = 0x73;
+	}
+	if (version < 3 && c == 0x7e) {
+		lastchar = 0x7e;
+		c = 0x0a;
+	}
+	if (((c > 0x40) && (c < 0x5b)) || ((c > 0x60) && (c < 0x7b))) {
+		if (big) {
+			c &= 0xdf;
+			big = 0;
+		}
+		if (period)
+			char_out(0x20);
+	}
+	period = 0;
+	if (version < 4) {
+		if ((c == 0x2e) || (c == 0x3f) || (c == 0x21) || (c == 0x0a))
+			big = 1;
+		else if (c == 0x22)
+			big = 0;
+	} else {
+		if ((c == 0x20) && (lastchar == 0x0a))
+			return;
+		if ((c == 0x2e) || (c == 0x3f) || (c == 0x21) || (c == 0x0a))
+			big = 1;
+		else if (c == 0x22)
+			big = 0;
+	}
+	if (((c == 0x20) || (c == 0x0a)) && (c == lastchar))
+		return;
+	if (version < 3) {
+		if (pipe) {
+			pipe = 0;
+			return;
+		}
+		if (c == 0x7c) {
+			pipe = 1;
+			return;
+		}
+	} else {
+		if (c == 0x7e) {
+			c = 0x0a;
+			if (lastchar != 0x0a)
+				char_out(0x0a);
+		}
+	}
+	lastchar = c;
+	if (c == 0x5f)
+		c = 0x20;
+	if ((c == 0x2e) || (c == 0x2c) || (c == 0x3b) || (c == 0x3a) || (c == 0x21) || (c == 0x3f))
+		period = 1;
+	ms_putchar(c);
+}
+
+
+/* extract addressing mode information [1c6f] */
+
+void set_info(type8 b) {
+	regnr = (type8)(b & 0x07);
+	admode = (type8)((b >> 3) & 0x07);
+	opsize = (type8)(b >> 6);
+}
+
+/* read a word and increase pc */
+
+void read_word(void) {
+	type8 *epc;
+
+	epc = effective(pc);
+	byte1 = epc[0];
+	byte2 = epc[1];
+	pc += 2;
+}
+
+/* get addressing mode and set arg1 [1c84] */
+
+void set_arg1(void) {
+	type8 tmp[2], l1c;
+
+	is_reversible = 1;
+	switch (admode) {
+	case 0:
+		arg1 = reg_align((type8 *) & dreg[regnr], opsize);  /* Dx */
+		is_reversible = 0;
+#ifdef LOGEMU
+		out(" d%.1d", regnr);
+#endif
+		break;
+	case 1:
+		arg1 = reg_align((type8 *) & areg[regnr], opsize);  /* Ax */
+		is_reversible = 0;
+#ifdef LOGEMU
+		out(" a%.1d", regnr);
+#endif
+		break;
+	case 2:
+		arg1i = read_reg(8 + regnr, 2);     /* (Ax) */
+#ifdef LOGEMU
+		out(" (a%.1d)", regnr);
+#endif
+		break;
+	case 3:
+		arg1i = read_reg(8 + regnr, 2);     /* (Ax)+ */
+		write_reg(8 + regnr, 2, read_reg(8 + regnr, 2) + (1 << opsize));
+#ifdef LOGEMU
+		out(" (a%.1d)+", regnr);
+#endif
+		break;
+	case 4:
+		write_reg(8 + regnr, 2, read_reg(8 + regnr, 2) - (1 << opsize));
+		arg1i = read_reg(8 + regnr, 2);     /* -(Ax) */
+#ifdef LOGEMU
+		out(" -(a%.1d)", regnr);
+#endif
+		break;
+	case 5: {
+		type16s i = (type16s) read_w(effective(pc));
+		arg1i = read_reg(8 + regnr, 2) + i;
+		pc += 2;    /* offset.w(Ax) */
+#ifdef LOGEMU
+		out(" %X(a%.1d)", i, regnr);
+#endif
+	}
+	break;
+	case 6:
+		tmp[0] = byte1;
+		tmp[1] = byte2;
+		read_word();    /* offset.b(Ax, Dx/Ax) [1d1c] */
+#ifdef LOGEMU
+		out(" %.2X(a%.1d,", (int) byte2, regnr);
+#endif
+		arg1i = read_reg(regnr + 8, 2) + (type8s) byte2;
+#ifdef LOGEMU
+		if ((byte1 >> 4) > 8)
+			out("a%.1d", (byte1 >> 4) - 8);
+		else
+			out("d%.1d", byte1 >> 4);
+#endif
+		if (byte1 & 0x08) {
+#ifdef LOGEMU
+			out(".l)");
+#endif
+			arg1i += (type32s) read_reg((byte1 >> 4), 2);
+		} else {
+#ifdef LOGEMU
+			out(".w)");
+#endif
+			arg1i += (type16s) read_reg((byte1 >> 4), 1);
+		}
+		byte1 = tmp[0];
+		byte2 = tmp[1];
+		break;
+	case 7:     /* specials */
+		switch (regnr) {
+		case 0:
+			arg1i = read_w(effective(pc));  /* $xxxx.W */
+			pc += 2;
+#ifdef LOGEMU
+			out(" %.4X.w", arg1i);
+#endif
+			break;
+		case 1:
+			arg1i = read_l(effective(pc));  /* $xxxx */
+			pc += 4;
+#ifdef LOGEMU
+			out(" %.4X", arg1i);
+#endif
+			break;
+		case 2:
+			arg1i = (type16s) read_w(effective(pc)) + pc;   /* $xxxx(PC) */
+			pc += 2;
+#ifdef LOGEMU
+			out(" %.4X(pc)", arg1i);
+#endif
+			break;
+		case 3:
+			l1c = effective(pc)[0];     /* $xx(PC,A/Dx) */
+#ifdef LOGEMU
+			out(" ???2", arg1i);
+#endif
+			if (l1c & 0x08)
+				arg1i = pc + (type32s) read_reg((l1c >> 4), 2);
+			else
+				arg1i = pc + (type16s) read_reg((l1c >> 4), 1);
+			l1c = effective(pc)[1];
+			pc += 2;
+			arg1i += (type8s) l1c;
+			break;
+		case 4:
+			arg1i = pc; /* #$xxxx */
+			if (opsize == 0)
+				arg1i += 1;
+			pc += 2;
+			if (opsize == 2)
+				pc += 2;
+#ifdef LOGEMU
+			out(" #%.4X", arg1i);
+#endif
+			break;
+		}
+		break;
+	}
+	if (is_reversible)
+		arg1 = effective(arg1i);
+}
+
+/* get addressing mode and set arg2 [1bc5] */
+
+void set_arg2_nosize(int use_dx, type8 b) {
+	if (use_dx)
+		arg2 = (type8 *) dreg;
+	else
+		arg2 = (type8 *) areg;
+	arg2 += (b & 0x0e) << 1;
+}
+
+void set_arg2(int use_dx, type8 b) {
+	set_arg2_nosize(use_dx, b);
+	arg2 = reg_align(arg2, opsize);
+}
+
+/* [1b9e] */
+
+void swap_args(void) {
+	type8 *tmp;
+
+	tmp = arg1;
+	arg1 = arg2;
+	arg2 = tmp;
+}
+
+/* [1cdc] */
+
+void push(type32 c) {
+	write_reg(15, 2, read_reg(15, 2) - 4);
+	write_l(effective(read_reg(15, 2)), c);
+}
+
+/* [1cd1] */
+
+type32 pop(void) {
+	type32 c;
+
+	c = read_l(effective(read_reg(15, 2)));
+	write_reg(15, 2, read_reg(15, 2) + 4);
+	return c;
+}
+
+/* check addressing mode and get argument [2e85] */
+
+void get_arg(void) {
+#ifdef LOGEMU
+	out(" %.4X", pc);
+#endif
+	set_info(byte2);
+	arg2 = effective(pc);
+	pc += 2;
+	if (opsize == 2)
+		pc += 2;
+	if (opsize == 0)
+		arg2 += 1;
+	set_arg1();
+}
+
+void set_flags(void) {
+	type16 i;
+	type32 j;
+
+	zflag = nflag = 0;
+	switch (opsize) {
+	case 0:
+		if (arg1[0] > 127)
+			nflag = 0xff;
+		if (arg1[0] == 0)
+			zflag = 0xff;
+		break;
+	case 1:
+		i = read_w(arg1);
+		if (i == 0)
+			zflag = 0xff;
+		if ((i >> 15) > 0)
+			nflag = 0xff;
+		break;
+	case 2:
+		j = read_l(arg1);
+		if (j == 0)
+			zflag = 0xff;
+		if ((j >> 31) > 0)
+			nflag = 0xff;
+		break;
+	}
+}
+
+/* [263a] */
+
+int condition(type8 b) {
+	switch (b & 0x0f) {
+	case 0:
+		return 0xff;
+	case 1:
+		return 0x00;
+	case 2:
+		return (zflag | cflag) ^ 0xff;
+	case 3:
+		return (zflag | cflag);
+	case 4:
+		return cflag ^ 0xff;
+	case 5:
+		return cflag;
+	case 6:
+		return zflag ^ 0xff;
+	case 7:
+		return zflag;
+	case 8:
+		return vflag ^ 0xff;
+	case 9:
+		return vflag;
+	case 10:
+	case 12:
+		return nflag ^ 0xff;
+	case 11:
+	case 13:
+		return nflag;
+	case 14:
+		return (zflag | nflag) ^ 0xff;
+	case 15:
+		return (zflag | nflag);
+	}
+	return 0x00;
+}
+
+/* [26dc] */
+
+void branch(type8 b) {
+	if (b == 0)
+		pc += (type16s) read_w(effective(pc));
+	else
+		pc += (type8s) b;
+#ifdef LOGEMU
+	out(" %.4X", pc);
+#endif
+}
+
+/* [2869] */
+
+void do_add(type8 adda) {
+	if (adda) {
+		if (opsize == 0)
+			write_l(arg1, read_l(arg1) + (type8s) arg2[0]);
+		if (opsize == 1)
+			write_l(arg1, read_l(arg1) + (type16s) read_w(arg2));
+		if (opsize == 2)
+			write_l(arg1, read_l(arg1) + (type32s) read_l(arg2));
+	} else {
+		cflag = 0;
+		if (opsize == 0) {
+			arg1[0] += arg2[0];
+			if (arg2[0] > arg1[0])
+				cflag = 0xff;
+		}
+		if (opsize == 1) {
+			write_w(arg1, (type16)(read_w(arg1) + read_w(arg2)));
+			if (read_w(arg2) > read_w(arg1))
+				cflag = 0xff;
+		}
+		if (opsize == 2) {
+			write_l(arg1, read_l(arg1) + read_l(arg2));
+			if (read_l(arg2) > read_l(arg1))
+				cflag = 0xff;
+		}
+		if (version < 3 || !quick_flag) {
+			/* Corruption onwards */
+			vflag = 0;
+			set_flags();
+		}
+	}
+}
+
+/* [2923] */
+
+void do_sub(type8 suba) {
+	if (suba) {
+		if (opsize == 0)
+			write_l(arg1, read_l(arg1) - (type8s) arg2[0]);
+		if (opsize == 1)
+			write_l(arg1, read_l(arg1) - (type16s) read_w(arg2));
+		if (opsize == 2)
+			write_l(arg1, read_l(arg1) - (type32s) read_l(arg2));
+	} else {
+		cflag = 0;
+		if (opsize == 0) {
+			if (arg2[0] > arg1[0])
+				cflag = 0xff;
+			arg1[0] -= arg2[0];
+		}
+		if (opsize == 1) {
+			if (read_w(arg2) > read_w(arg1))
+				cflag = 0xff;
+			write_w(arg1, (type16)(read_w(arg1) - read_w(arg2)));
+		}
+		if (opsize == 2) {
+			if (read_l(arg2) > read_l(arg1))
+				cflag = 0xff;
+			write_l(arg1, read_l(arg1) - read_l(arg2));
+		}
+		if (version < 3 || !quick_flag) {
+			/* Corruption onwards */
+			vflag = 0;
+			set_flags();
+		}
+	}
+}
+
+/* [283b] */
+
+void do_eor(void) {
+	if (opsize == 0)
+		arg1[0] ^= arg2[0];
+	if (opsize == 1)
+		write_w(arg1, (type16)(read_w(arg1) ^ read_w(arg2)));
+	if (opsize == 2)
+		write_l(arg1, read_l(arg1) ^ read_l(arg2));
+	cflag = vflag = 0;
+	set_flags();
+}
+
+/* [280d] */
+
+void do_and(void) {
+	if (opsize == 0)
+		arg1[0] &= arg2[0];
+	if (opsize == 1)
+		write_w(arg1, (type16)(read_w(arg1) & read_w(arg2)));
+	if (opsize == 2)
+		write_l(arg1, read_l(arg1) & read_l(arg2));
+	cflag = vflag = 0;
+	set_flags();
+}
+
+/* [27df] */
+
+void do_or(void) {
+	if (opsize == 0)
+		arg1[0] |= arg2[0];
+	if (opsize == 1)
+		write_w(arg1, (type16)(read_w(arg1) | read_w(arg2)));
+	if (opsize == 2)
+		write_l(arg1, read_l(arg1) | read_l(arg2));
+	cflag = vflag = 0;
+	set_flags();    /* [1c2b] */
+}
+
+/* [289f] */
+
+void do_cmp(void) {
+	type8 *tmp;
+
+	tmp = arg1;
+	tmparg[0] = arg1[0];
+	tmparg[1] = arg1[1];
+	tmparg[2] = arg1[2];
+	tmparg[3] = arg1[3];
+	arg1 = tmparg;
+	quick_flag = 0;
+	do_sub(0);
+	arg1 = tmp;
+}
+
+/* [2973] */
+
+void do_move(void) {
+
+	if (opsize == 0)
+		arg1[0] = arg2[0];
+	if (opsize == 1)
+		write_w(arg1, read_w(arg2));
+	if (opsize == 2)
+		write_l(arg1, read_l(arg2));
+	if (version < 2 || admode != 1) {
+		/* Jinxter: no flags if destination Ax */
+		cflag = vflag = 0;
+		set_flags();
+	}
+}
+
+type8 do_btst(type8 a) {
+	a &= admode ? 0x7 : 0x1f;
+	while (admode == 0 && a >= 8) {
+		a -= 8;
+		arg1 -= 1;
+	}
+	zflag = 0;
+	if ((arg1[0] & (1 << a)) == 0)
+		zflag = 0xff;
+	return a;
+}
+
+/* bit operation entry point [307c] */
+
+void do_bop(type8 b, type8 a) {
+#ifdef LOGEMU
+	out("bop (%.2x,%.2x) ", (int) b, (int) a);
+#endif
+	b = b & 0xc0;
+	a = do_btst(a);
+#ifdef LOGEMU
+	if (b == 0x00)
+		out("no bop???");
+#endif
+	if (b == 0x40) {
+		arg1[0] ^= (1 << a);    /* bchg */
+#ifdef LOGEMU
+		out("bchg");
+#endif
+	}
+	if (b == 0x80) {
+		arg1[0] &= ((1 << a) ^ 0xff);   /* bclr */
+#ifdef LOGEMU
+		out("bclr");
+#endif
+	}
+	if (b == 0xc0) {
+		arg1[0] |= (1 << a);    /* bset */
+#ifdef LOGEMU
+		out("bset");
+#endif
+	}
+}
+
+void check_btst(void) {
+#ifdef LOGEMU
+	out("btst");
+#endif
+	set_info((type8)(byte2 & 0x3f));
+	set_arg1();
+	set_arg2(1, byte1);
+	do_bop(byte2, arg2[0]);
+}
+
+void check_lea(void) {
+#ifdef LOGEMU
+	out("lea");
+#endif
+	if ((byte2 & 0xc0) == 0xc0) {
+		set_info(byte2);
+		opsize = 2;
+		set_arg1();
+		set_arg2(0, byte1);
+		write_w(arg2, 0);
+		if (is_reversible)
+			write_l(arg2, arg1i);
+		else
+			ms_fatal("illegal addressing mode for LEA");
+	} else {
+		ms_fatal("unimplemented instruction CHK");
+	}
+}
+
+/* [33cc] */
+
+void check_movem(void) {
+	type8 l1c;
+
+#ifdef LOGEMU
+	out("movem");
+#endif
+	set_info((type8)(byte2 - 0x40));
+	read_word();
+	for (l1c = 0; l1c < 8; l1c++) {
+		if (byte2 & 1 << l1c) {
+			set_arg1();
+			if (opsize == 2)
+				write_l(arg1, read_reg(15 - l1c, 2));
+			if (opsize == 1)
+				write_w(arg1, (type16)read_reg(15 - l1c, 1));
+		}
+	}
+	for (l1c = 0; l1c < 8; l1c++) {
+		if (byte1 & 1 << l1c) {
+			set_arg1();
+			if (opsize == 2)
+				write_l(arg1, read_reg(7 - l1c, 2));
+			if (opsize == 1)
+				write_w(arg1, (type16)read_reg(7 - l1c, 1));
+		}
+	}
+}
+
+/* [3357] */
+
+void check_movem2(void) {
+	type8 l1c;
+
+#ifdef LOGEMU
+	out("movem (2)");
+#endif
+	set_info((type8)(byte2 - 0x40));
+	read_word();
+	for (l1c = 0; l1c < 8; l1c++) {
+		if (byte2 & 1 << l1c) {
+			set_arg1();
+			if (opsize == 2)
+				write_reg(l1c, 2, read_l(arg1));
+			if (opsize == 1)
+				write_reg(l1c, 1, read_w(arg1));
+		}
+	}
+	for (l1c = 0; l1c < 8; l1c++) {
+		if (byte1 & 1 << l1c) {
+			set_arg1();
+			if (opsize == 2)
+				write_reg(8 + l1c, 2, read_l(arg1));
+			if (opsize == 1)
+				write_reg(8 + l1c, 1, read_w(arg1));
+		}
+	}
+}
+
+/* [30e4] in Jinxter, ~540 lines of 6510 spaghetti-code */
+/* The mother of all bugs, but hey - no gotos used :-) */
+
+void dict_lookup(void) {
+	type16 dtab, doff, output, output_bak, bank, word, output2;
+	type16 tmp16, i, obj_adj, adjlist, adjlist_bak;
+	type8 c, c2, c3, flag, matchlen, longest, flag2;
+	type8 restartFlag = 0, accept = 0;
+
+	/*
+	   dtab=A5.W                    ;dict_table offset <L22>
+	   output=output_bak=A2.W       ;output <L24>
+	   A5.W=A6.W                    ;input word
+	   doff=A3.W                    ;lookup offset (doff) <L1C>
+	   adjlist=A0.W ;adjlist <L1E>
+	 */
+
+	dtab = (type16)read_reg(8 + 5, 1);  /* used by version>0 */
+	output = (type16)read_reg(8 + 2, 1);
+	write_reg(8 + 5, 1, read_reg(8 + 6, 1));
+	doff = (type16)read_reg(8 + 3, 1);
+	adjlist = (type16)read_reg(8 + 0, 1);
+
+	bank = (type16)read_reg(6, 0);  /* l2d */
+	flag = 0;       /* l2c */
+	word = 0;       /* l26 */
+	matchlen = 0;       /* l2e */
+	longest = 0;        /* 30e2 */
+	write_reg(0, 1, 0); /* apostroph */
+
+	while ((c = sd ? dict[doff] : effective(doff)[0]) != 0x81) {
+		if (c >= 0x80) {
+			if (c == 0x82) {
+				flag = matchlen = 0;
+				word = 0;
+				write_reg(8 + 6, 1, read_reg(8 + 5, 1));
+				bank++;
+				doff++;
+				continue;
+			}
+			c3 = c;
+			c &= 0x5f;
+			c2 = effective(read_reg(8 + 6, 1))[0] & 0x5f;
+			if (c2 == c) {
+				write_reg(8 + 6, 1, read_reg(8 + 6, 1) + 1);
+				c = effective(read_reg(8 + 6, 1))[0];
+				if ((!c) || (c == 0x20) || (c == 0x27) || (!version && (matchlen > 6))) {
+					if (c == 0x27) {
+						write_reg(8 + 6, 1, read_reg(8 + 6, 1) + 1);
+						write_reg(0, 1, 0x200 + effective(read_reg(8 + 6, 1))[0]);
+					}
+					if ((version < 4) || (c3 != 0xa0))
+						accept = 1;
+				} else
+					restartFlag = 1;
+			} else if (!version && matchlen > 6 && !c2)
+				accept = 1;
+			else
+				restartFlag = 1;
+		} else {
+			c &= 0x5f;
+			c2 = effective(read_reg(8 + 6, 1))[0] & 0x5f;
+			if ((c2 == c && c) || (version && !c2 && (c == 0x5f))) {
+				if (version && !c2 && (c == 0x5f))
+					flag = 0x80;
+				matchlen++;
+				write_reg(8 + 6, 1, read_reg(8 + 6, 1) + 1);
+				doff++;
+			} else if (!version && matchlen > 6 && !c2)
+				accept = 1;
+			else
+				restartFlag = 1;
+		}
+		if (accept) {
+			effective(read_reg(8 + 2, 1))[0] = (version) ? flag : 0;
+			effective(read_reg(8 + 2, 1))[1] = (type8)bank;
+			write_w(effective(read_reg(8 + 2, 1) + 2), word);
+			write_reg(8 + 2, 1, read_reg(8 + 2, 1) + 4);
+			if (matchlen >= longest)
+				longest = matchlen;
+			restartFlag = 1;
+			accept = 0;
+		}
+		if (restartFlag) {
+			write_reg(8 + 6, 1, read_reg(8 + 5, 1));
+			flag = matchlen = 0;
+			word++;
+			if (sd)
+				while (dict[doff++] < 0x80);
+			else
+				while (effective(doff++)[0] < 0x80);
+			restartFlag = 0;
+		}
+	}
+	write_w(effective(read_reg(8 + 2, 1)), 0xffff);
+
+	if (version) {
+		/* version > 0 */
+		output_bak = output;    /* check synonyms */
+		while ((c = effective(output)[1]) != 0xff) {
+			if (c == 0x0b) {
+				if (sd)
+					tmp16 = read_w(&dict[dtab + read_w(effective(output + 2)) * 2]);
+				else
+					tmp16 = read_w(effective(dtab + read_w(effective(output + 2)) * 2));
+				effective(output)[1] = tmp16 & 0x1f;
+				write_w(effective(output + 2), (type16)(tmp16 >> 5));
+			}
+			output += 4;
+		}
+		output = output_bak;
+	}
+
+	/* l22 = output2,     l1e = adjlist, l20 = obj_adj, l26 = word, l2f = c2 */
+	/* l1c = adjlist_bak, 333C = i,      l2d = bank,    l2c = flag, l30e3 = flag2 */
+
+	write_reg(1, 1, 0); /* D1.W=0  [32B5] */
+	flag2 = 0;
+	output_bak = output;
+	output2 = output;
+	while ((bank = effective(output2)[1]) != 0xff) {
+		obj_adj = (type16)read_reg(8 + 1, 1);   /* A1.W - obj_adj, ie. adjs for this word */
+		write_reg(1, 0, 0); /* D1.B=0 */
+		flag = effective(output2)[0];   /* flag */
+		word = read_w(effective(output2 + 2));  /* wordnumber */
+		output2 += 4;   /* next match */
+		if ((read_w(effective(obj_adj))) && (bank == 6)) {
+			/* Any adjectives? */
+			if ((i = word) != 0) {
+				/* Find list of valid adjs */
+				do {
+					while (effective(adjlist++)[0]);
+				} while (--i > 0);
+			}
+			adjlist_bak = adjlist;
+			do {
+				adjlist = adjlist_bak;
+				c2 = effective(obj_adj)[1]; /* given adjective */
+				if ((tmp16 = read_w(effective(obj_adj))) != 0) {
+					obj_adj += 2;
+					while ((c = effective(adjlist++)[0]) && (c - 3 != c2));
+					if (c - 3 != c2)
+						write_reg(1, 0, 1); /* invalid adjective */
+				}
+			} while (tmp16 && !read_reg(1, 0));
+			adjlist = (type16)read_reg(8 + 0, 1);
+		}
+		if (!read_reg(1, 0)) {
+			/* invalid_flag */
+			flag2 |= flag;
+			effective(output)[0] = flag2;
+			effective(output)[1] = (type8)bank;
+			write_w(effective(output + 2), word);
+			output += 4;
+		}
+	}
+	write_reg(8 + 2, 1, output);
+	output = output_bak;
+
+	if (flag2 & 0x80) {
+		tmp16 = output;
+		output -= 4;
+		do {
+			output += 4;
+			c = effective(output)[0];
+		} while (!(c & 0x80));
+		write_l(effective(tmp16), read_l(effective(output)) & 0x7fffffff);
+		write_reg(8 + 2, 2, tmp16 + 4);
+		if (longest > 1) {
+			write_reg(8 + 5, 1, read_reg(8 + 5, 1) + longest - 2);
+		}
+	}
+	write_reg(8 + 6, 1, read_reg(8 + 5, 1) + 1);
+}
+
+/* A0=findproperties(D0) [2b86], properties_ptr=[2b78] A0FE */
+
+void do_findprop(void) {
+	type16 tmp;
+
+	if ((version > 2) && ((read_reg(0, 1) & 0x3fff) > fp_size)) {
+		tmp = (type16)(((fp_size - (read_reg(0, 1) & 0x3fff)) ^ 0xffff) << 1);
+		tmp += fp_tab;
+		tmp = read_w(effective(tmp));
+	} else {
+		if (version < 2)
+			write_reg(0, 2, read_reg(0, 2) & 0x7fff);
+		else
+			write_reg(0, 1, read_reg(0, 1) & 0x7fff);
+		tmp = (type16)read_reg(0, 1);
+	}
+	tmp &= 0x3fff;
+	write_reg(8 + 0, 2, tmp * 14 + properties);
+}
+
+void write_string(void) {
+	static type32 offset_bak;
+	static type8 mask_bak;
+	type8 c, b, mask;
+	type16 ptr;
+	type32 offset;
+
+	if (!cflag) {
+		/* new string */
+		ptr = (type16)read_reg(0, 1);
+		if (!ptr)
+			offset = 0;
+		else {
+			offset = read_w(&decode_table[0x100 + 2 * ptr]);
+			if (read_w(&decode_table[0x100])) {
+				if (ptr >= read_w(&decode_table[0x100]))
+					offset += string_size;
+			}
+		}
+		mask = 1;
+	} else {
+		offset = offset_bak;
+		mask = mask_bak;
+	}
+	do {
+		c = 0;
+		while (c < 0x80) {
+			if (offset >= string_size)
+				b = string2[offset - string_size];
+			else {
+				if (offset >= MAX_STRING_SIZE)
+					b = string3[offset - MAX_STRING_SIZE];
+				else
+					b = string[offset];
+			}
+			if (b & mask)
+				c = decode_table[0x80 + c];
+			else
+				c = decode_table[c];
+			mask <<= 1;
+			if (!mask) {
+				mask = 1;
+				offset++;
+			}
+		}
+		c &= 0x7f;
+		if (c && ((c != 0x40) || (lastchar != 0x20)))
+			char_out(c);
+	} while (c && ((c != 0x40) || (lastchar != 0x20)));
+	cflag = c ? 0xff : 0;
+	if (c) {
+		offset_bak = offset;
+		mask_bak = mask;
+	}
+}
+
+void output_number(type16 number) {
+	type16 tens = number / 10;
+
+	if (tens > 0)
+		ms_putchar('0' + tens);
+	number -= (tens * 10);
+	ms_putchar('0' + number);
+}
+
+type16 output_text(const char *text) {
+	type16 i;
+
+	for (i = 0; text[i] != 0; i++)
+		ms_putchar(text[i]);
+	return i;
+}
+
+type16s hint_input(void) {
+	type8 c1, c2, c3;
+
+	output_text(">>");
+	ms_flush();
+
+	do {
+		c1 = ms_getchar(0);
+	} while (c1 == '\n');
+	if (c1 == 1)
+		return -1; /* New game loaded, abort hints */
+
+	c2 = ms_getchar(0);
+	if (c2 == 1)
+		return -1;
+
+	/* Get and discard any remaining characters */
+	c3 = c2;
+	while (c3 != '\n') {
+		c3 = ms_getchar(0);
+		if (c3 == 1)
+			return -1;
+	}
+	ms_putchar('\n');
+
+	if ((c1 >= '0') && (c1 <= '9')) {
+		type16 number = c1 - '0';
+		if ((c2 >= '0') && (c2 <= '9')) {
+			number *= 10;
+			number += c2 - '0';
+		}
+		return number;
+	}
+
+	if ((c1 >= 'A') && (c1 <= 'Z'))
+		c1 = 'a' + (c1 - 'A');
+	if ((c1 >= 'a') && (c1 <= 'z')) {
+		switch (c1) {
+		case 'e':
+			return -2; /* End hints */
+		case 'n':
+			return -3; /* Next hint */
+		case 'p':
+			return -4; /* Show parent hint list */
+		}
+	}
+	return 0;
+}
+
+type16 show_hints_text(ms_hint *hintsData, type16 index) {
+	type16 i = 0, j = 0;
+	type16s input;
+	ms_hint *hint = hintsData + index;
+
+	while (1) {
+		switch (hint->nodetype) {
+
+		case 1: /* folders */
+			output_text("Hint categories:\n");
+			for (i = 0, j = 0; i < hint->elcount; i++) {
+				output_number(i + 1);
+				output_text(". ");
+				j += output_text(hint->content + j) + 1;
+				ms_putchar('\n');
+			}
+			output_text("Enter hint category number, ");
+			if (hint->parent != 0xffff)
+				output_text("P for the parent hint menu, ");
+			output_text("or E to end hintsData.\n");
+
+			input = hint_input();
+			switch (input) {
+			case -1: /* A new game is being loaded */
+				return 1;
+			case -2: /* End hintsData */
+				return 1;
+			case -4: /* Show parent hint list */
+				if (hint->parent != 0xffff)
+					return 0;
+			default:
+				if ((input > 0) && (input <= hint->elcount)) {
+					if (show_hints_text(hintsData, hint->links[input - 1]) == 1)
+						return 1;
+				}
+				break;
+			}
+			break;
+
+		case 2: /* hintsData */
+			if (i < hint->elcount) {
+				output_number(i + 1);
+				output_text(". ");
+				j += output_text(hint->content + j) + 1;
+
+				if (i == hint->elcount - 1) {
+					output_text("\nNo more hintsData.\n");
+					return 0; /* Last hint */
+				} else {
+					output_text("\nEnter N for the next hint, ");
+					output_text("P for the parent hint menu, ");
+					output_text("or E to end hintsData.\n");
+				}
+
+				input = hint_input();
+				switch (input) {
+				case -1: /* A new game is being loaded */
+					return 1;
+				case -2: /* End hintsData */
+					return 1;
+				case -3:
+					i++;
+					break;
+				case -4: /* Show parent hint list */
+					return 0;
+				}
+			} else
+				return 0;
+			break;
+		}
+	}
+	return 0;
+}
+
+void do_line_a(void) {
+	type8 l1c;
+	char *str;
+	type16 ptr, ptr2, tmp16, dtype;
+	type32 a1reg, tmp32;
+
+#ifdef LOGGFX
+	/*
+	    if (((byte2-0xdd) == 4) || ((byte2-0xdd) == 13))
+	        out2("--> %d\n",byte2-0xdd);
+	    else
+	        out2("LINE A %d\n",byte2-0xdd);
+	 */
+#endif
+	if ((byte2 < 0xdd) || (version < 4 && byte2 < 0xe4) || (version < 2 && byte2 < 0xed)) {
+		ms_flush(); /* flush output-buffer */
+		rand_emu(); /* Increase game randomness */
+		l1c = ms_getchar(1);    /* 0 means UNDO */
+		if (l1c == 1)
+			return;
+		if (l1c)
+			write_reg(1, 2, l1c);   /* d1=getkey() */
+		else {
+			if ((l1c = ms_undo()) != 0)
+				output_text(undo_ok);
+			else
+				output_text(undo_fail);
+			if (!l1c)
+				write_reg(1, 2, '\n');
+		}
+	} else
+		switch (byte2 - 0xdd) {
+
+		case 0: /* A0DD - Won't probably be needed at all */
+			break;
+
+		case 1: /* A0DE */
+			write_reg(1, 0, 1); /* Should remove the manual check */
+			break;
+
+		case 2: /* A0DF */
+			a1reg = (type32)read_reg(9, 2);
+			dtype = (code + a1reg + 2)[0];
+
+			switch (dtype) {
+			case 7: /* Picture */
+#ifdef LOGGFX
+				out2("PICTURE IS %s\n", code + a1reg + 3);
+#endif
+				/* gfx mode = normal, df is not called if graphics are off */
+				ms_showpic(a1reg + 3, 2);
+				break;
+
+			case 10: /* Open window commands */
+				switch ((code + a1reg + 3)[0]) {
+				case 4: /* Help/Hints */
+					if (hints != 0) {
+						if (ms_showhints(hints) == 0)
+							show_hints_text(hints, 0);
+					} else
+						output_text(no_hints);
+					break;
+				case 0: /* Carried items */
+				case 1: /* Room items */
+				case 2: /* Map */
+				case 3: /* Compass */
+					output_text(not_supported);
+					break;
+				}
+				break;
+
+			case 13: /* Music */
+				switch ((code + a1reg + 3)[0]) {
+				case 0: /* stop music */
+					ms_playmusic(0, 0, 0);
+					break;
+				default: /* play music */
+#ifdef LOGSND
+					out2("MUSIC IS %s\n", code + a1reg + 3);
+#endif
+					{
+						type32 length = 0;
+						type16 tempo = 0;
+						type8 *midi = sound_extract((const char *)(code + a1reg + 3), &length, &tempo);
+						if (midi != NULL)
+							ms_playmusic(midi, length, tempo);
+					}
+					break;
+				}
+				break;
+			}
+			break;
+
+		case 3: /* A0E0 */
+			/* printf("A0E0 stubbed\n"); */
+			break;
+
+		case 4: /* A0E1 Read from keyboard to (A1), status in D1 (0 for ok) */
+			ms_flush();
+			rand_emu();
+			tmp32 = read_reg(8 + 1, 2);
+			str = (char *)effective(tmp32);
+			tmp16 = 0;
+			do {
+				if (!(l1c = ms_getchar(1))) {
+					if ((l1c = ms_undo()) != 0)
+						output_text(undo_ok);
+					else
+						output_text(undo_fail);
+					if (!l1c) {
+						tmp16 = 0;
+						str[tmp16++] = '\n';
+						l1c = '\n';
+						output_text("\n>");
+					} else {
+						ms_putchar('\n');
+						return;
+					}
+				} else {
+					if (l1c == 1)
+						return;
+					str[tmp16++] = l1c;
+#ifdef LOGGFX
+					out2("%c", l1c);
+#endif
+				}
+			} while (l1c != '\n' && tmp16 < 256);
+			write_reg(8 + 1, 2, tmp32 + tmp16 - 1);
+			if (tmp16 != 256 && tmp16 != 1)
+				write_reg(1, 1, 0);
+			else
+				write_reg(1, 1, 1);
+			break;
+
+		case 5: /* A0E2 */
+			/* printf("A0E2 stubbed\n"); */
+			break;
+
+		case 6: /* A0E3 */
+			if (read_reg(1, 2) == 0) {
+				if ((version < 4) || (read_reg(6, 2) == 0))
+					ms_showpic(0, 0);
+			}
+			/* printf("\nMoves: %u\n",read_reg(0,1)); */
+			break;
+
+		case 7: /* A0E4 sp+=4, RTS */
+			write_reg(8 + 7, 1, read_reg(8 + 7, 1) + 4);
+			pc = pop();
+			break;
+
+		case 8: /* A0E5 set z, RTS */
+		case 9: /* A0E6 clear z, RTS */
+			pc = pop();
+			zflag = (byte2 == 0xe5) ? 0xff : 0;
+			break;
+
+		case 10: /* A0E7 set z */
+			zflag = 0xff;
+			break;
+
+		case 11: /* A0E8 clear z */
+			zflag = 0;
+			break;
+
+		case 12: /* A0E9 [3083 - j] */
+			ptr = (type16)read_reg(8 + 0, 1);
+			ptr2 = (type16)read_reg(8 + 1, 1);
+			do {
+				l1c = dict[ptr2++];
+				effective(ptr++)[0] = l1c;
+			} while ((l1c & 0x80) == 0);
+			write_reg(8 + 0, 1, ptr);
+			write_reg(8 + 1, 1, ptr2);
+			break;
+
+		case 13: /* A0EA A1=write_dictword(A1,D1=output_mode) */
+			ptr = (type16)read_reg(8 + 1, 1);
+			tmp32 = read_reg(3, 0);
+			write_reg(3, 0, read_reg(1, 0));
+			do {
+				l1c = dict[ptr++];
+				char_out(l1c);
+			} while (l1c < 0x80);
+			write_reg(8 + 1, 1, ptr);
+			write_reg(3, 0, tmp32);
+			break;
+
+		case 14: /* A0EB [3037 - j] */
+			dict[read_reg(8 + 1, 1)] = (type8)read_reg(1, 0);
+			break;
+
+		case 15: /* A0EC */
+			write_reg(1, 0, dict[read_reg(8 + 1, 1)]);
+			break;
+
+		case 16:
+			ms_stop();  /* infinite loop A0ED */
+			break;
+		case 17:
+			if (!ms_init(nullptr, nullptr, nullptr, nullptr))
+				ms_stop();  /* restart game ie. pc, sp etc. A0EE */
+			break;
+		case 18:    /* printer A0EF */
+			break;
+		case 19:
+			ms_showpic(read_reg(0, 0), (type8)read_reg(1, 0));  /* Do_picture(D0) A0F0 */
+			break;
+		case 20:
+			ptr = (type16)read_reg(8 + 1, 1);   /* A1=nth_string(A1,D0) A0F1 */
+			tmp32 = read_reg(0, 1);
+			while (tmp32-- > 0) {
+				while (effective(ptr++)[0]);
+			}
+			write_reg(8 + 1, 1, ptr);
+			break;
+
+		case 21: /* [2a43] A0F2 */
+			cflag = 0;
+			write_reg(0, 1, read_reg(2, 1));
+			do_findprop();
+			ptr = (type16)read_reg(8 + 0, 1);
+			while (read_reg(2, 1) > 0) {
+				if (read_w(effective(ptr + 12)) & 0x3fff) {
+					cflag = 0xff;
+					break;
+				}
+				if (read_reg(2, 1) == (read_reg(4, 1) & 0x7fff)) {
+					cflag = 0xff;
+					break;
+				}
+				ptr -= 0x0e;
+				write_reg(2, 1, read_reg(2, 1) - 1);
+			}
+			break;
+
+		case 22:
+			char_out((type8)read_reg(1, 0));    /* A0F3 */
+			break;
+
+		case 23: /* D7=Save_(filename A0) D1 bytes starting from A1  A0F4 */
+			str = (version < 4) ? (char *)effective(read_reg(8 + 0, 1)) : nullptr;
+			write_reg(7, 0, ms_save_file(str, effective(read_reg(8 + 1, 1)),
+			                             (type16)read_reg(1, 1)));
+			break;
+
+		case 24: /* D7=Load_(filename A0) D1 bytes starting from A1  A0F5 */
+			str = (version < 4) ? (char *)effective(read_reg(8 + 0, 1)) : nullptr;
+			write_reg(7, 0, ms_load_file(str, effective(read_reg(8 + 1, 1)),
+			                             (type16)read_reg(1, 1)));
+			break;
+
+		case 25: /* D1=Random(0..D1-1) [3748] A0F6 */
+			l1c = (type8)read_reg(1, 0);
+			write_reg(1, 1, rand_emu() % (l1c ? l1c : 1));
+			break;
+
+		case 26: /* D0=Random(0..255) [3742] A0F7 */
+			tmp16 = (type16)rand_emu();
+			write_reg(0, 0, tmp16 + (tmp16 >> 8));
+			break;
+
+		case 27: /* write string [D0] [2999] A0F8 */
+			write_string();
+			break;
+
+		case 28: /* Z,D0=Get_inventory_item(D0) [2a9e] A0F9 */
+			zflag = 0;
+			ptr = (type16)read_reg(0, 1);
+			do {
+				write_reg(0, 1, ptr);
+				do {
+					do_findprop();
+					ptr2 = (type16)read_reg(8 + 0, 1);  /* object properties */
+					if ((effective(ptr2)[5]) & 1)
+						break;  /* is_described or so */
+					l1c = effective(ptr2)[6];   /* some_flags */
+					tmp16 = read_w(effective(ptr2 + 8));    /* parent_object */
+					if (!l1c) {
+						/* ordinary object? */
+						if (!tmp16)
+							zflag = 0xff;   /* return if parent()=player */
+						break;  /* otherwise try next */
+					}
+					if (l1c & 0xcc)
+						break;  /* skip worn, bodypart, room, hidden */
+					if (tmp16 == 0) {
+						/* return if parent()=player? */
+						zflag = 0xff;
+						break;
+					}
+					write_reg(0, 1, tmp16); /* else look at parent() */
+				} while (1);
+				ptr--;
+			} while ((!zflag) && ptr);
+			write_reg(0, 1, ptr + 1);
+			break;
+
+		case 29: /* [2b18] A0FA */
+			ptr = (type16)read_reg(8, 1);
+			do {
+				if (read_reg(5, 0)) {
+					l1c = ((type32)((read_w(effective(ptr)) & 0x3fff)) == read_reg(2, 1));
+				} else {
+					l1c = (effective(ptr)[0] == read_reg(2, 0));
+				}
+				if (read_reg(3, 1) == read_reg(4, 1)) {
+					cflag = 0;
+					write_reg(8, 1, ptr);
+				} else {
+					write_reg(3, 1, read_reg(3, 1) + 1);
+					ptr += 0x0e;
+					if (l1c) {
+						cflag = 0xff;
+						write_reg(8, 1, ptr);
+					}
+				}
+			} while ((!l1c) && (read_reg(3, 1) != read_reg(4, 1)));
+			break;
+
+		case 30: /* [2bd1] A0FB */
+			ptr = (type16)read_reg(8 + 1, 1);
+			do {
+				if (dict)
+					while (dict[ptr++] < 0x80);
+				else
+					while (effective(ptr++)[0] < 0x80);
+				write_reg(2, 1, read_reg(2, 1) - 1);
+			} while (read_reg(2, 1));
+			write_reg(8 + 1, 1, ptr);
+			break;
+
+		case 31: /* [2c3b] A0FC */
+			ptr = (type16)read_reg(8 + 0, 1);
+			ptr2 = (type16)read_reg(8 + 1, 1);
+			do {
+				if (dict)
+					while (dict[ptr++] < 0x80);
+				else
+					while (effective(ptr++)[0] < 0x80);
+				while (effective(ptr2++)[0]);
+				write_reg(0, 1, read_reg(0, 1) - 1);
+			} while (read_reg(0, 1));
+			write_reg(8 + 0, 1, ptr);
+			write_reg(8 + 1, 1, ptr2);
+			break;
+
+		case 32: /* Set properties pointer from A0 [2b7b] A0FD */
+			properties = (type16)read_reg(8 + 0, 1);
+			if (version > 0)
+				fl_sub = (type16)read_reg(8 + 3, 1);
+			if (version > 1) {
+				fl_tab = (type16)read_reg(8 + 5, 1);
+				fl_size = (type16)read_reg(7, 1) + 1;
+				/* A3 [routine], A5 [table] and D7 [table-size] */
+			}
+			if (version > 2) {
+				fp_tab = (type16)read_reg(8 + 6, 1);
+				fp_size = (type16)read_reg(6, 1);
+			}
+			break;
+
+		case 33: /* A0FE */
+			do_findprop();
+			break;
+
+		case 34: /* Dictionary_lookup A0FF */
+			dict_lookup();
+			break;
+		}
+}
+
+/* emulate an instruction [1b7e] */
+
+type8 ms_rungame(void) {
+	type8 l1c;
+	type16 ptr;
+	type32 tmp32;
+#ifdef LOGEMU
+	static int stat = 0;
+#endif
+
+	if (!running)
+		return running;
+	if (pc == undo_pc)
+		save_undo();
+
+#ifdef LOGEMU
+	if (pc == 0x0000)
+		stat = 0;
+	if (stat) {
+		log_status();
+		fflush(dbg_log);
+	}
+
+	fprintf(dbg_log, "%.8X: ", pc);
+#endif
+	i_count++;
+	read_word();
+	switch (byte1 >> 1) {
+
+	/* 00-0F */
+	case 0x00:
+		if (byte1 == 0x00) {
+			if (byte2 == 0x3c || byte2 == 0x7c) {
+				/* OR immediate to CCR (30D9) */
+				read_word();
+#ifdef LOGEMU
+				out("or_ccr #%.2x", byte2);
+#endif
+				if (byte2 & 0x01)
+					cflag = 0xff;
+				if (byte2 & 0x02)
+					vflag = 0xff;
+				if (byte2 & 0x04)
+					zflag = 0xff;
+				if (byte2 & 0x08)
+					nflag = 0xff;
+			} else {
+				/* OR [27df] */
+#ifdef LOGEMU
+				out("or");
+#endif
+				get_arg();
+				do_or();
+			}
+		} else
+			check_btst();
+		break;
+
+	case 0x01:
+		if (byte1 == 0x02) {
+			if (byte2 == 0x3c || byte2 == 0x7c) {
+				/* AND immediate to CCR */
+				read_word();
+#ifdef LOGEMU
+				out("and_ccr #%.2x", byte2);
+#endif
+				if (!(byte2 & 0x01))
+					cflag = 0;
+				if (!(byte2 & 0x02))
+					vflag = 0;
+				if (!(byte2 & 0x04))
+					zflag = 0;
+				if (!(byte2 & 0x08))
+					nflag = 0;
+			} else {
+				/* AND */
+#ifdef LOGEMU
+				out("and");
+#endif
+				get_arg();
+				do_and();
+			}
+		} else
+			check_btst();
+		break;
+
+	case 0x02:
+		if (byte1 == 0x04) {
+			/* SUB */
+#ifdef LOGEMU
+			out("sub");
+#endif
+			get_arg();
+			do_sub(0);
+		} else
+			check_btst();
+		break;
+
+	case 0x03:
+		if (byte1 == 0x06) {
+			/* ADD */
+#ifdef LOGEMU
+			out("addi");
+#endif
+			get_arg();
+			do_add(0);
+		} else
+			check_btst();
+		break;
+
+	case 0x04:
+		if (byte1 == 0x08) {
+			/* bit operations (immediate) */
+			set_info((type8)(byte2 & 0x3f));
+			l1c = (effective(pc))[1];
+			pc += 2;
+			set_arg1();
+			do_bop(byte2, l1c);
+		} else
+			check_btst();
+		break;
+
+	case 0x05:
+		if (byte1 == 0x0a) {
+			if (byte2 == 0x3c || byte2 == 0x7c) {
+				/* EOR immediate to CCR */
+				read_word();
+#ifdef LOGEMU
+				out("eor_ccr #%.2X", byte2);
+#endif
+				if (byte2 & 0x01)
+					cflag ^= 0xff;
+				if (byte2 & 0x02)
+					vflag ^= 0xff;
+				if (byte2 & 0x04)
+					zflag ^= 0xff;
+				if (byte2 & 0x08)
+					nflag ^= 0xff;
+			} else {
+				/* EOR */
+#ifdef LOGEMU
+				out("eor");
+#endif
+				get_arg();
+				do_eor();
+			}
+		} else
+			check_btst();
+		break;
+
+	case 0x06:
+		if (byte1 == 0x0c) {
+			/* CMP */
+#ifdef LOGEMU
+			out("cmp");
+#endif
+			get_arg();
+			do_cmp();
+		} else
+			check_btst();
+		break;
+
+	case 0x07:
+		check_btst();
+		break;
+
+	/* 10-1F [3327] MOVE.B */
+	case 0x08:
+	case 0x09:
+	case 0x0a:
+	case 0x0b:
+	case 0x0c:
+	case 0x0d:
+	case 0x0e:
+	case 0x0f:
+
+#ifdef LOGEMU
+		out("move.b");
+#endif
+		set_info((type8)(byte2 & 0x3f));
+		set_arg1();
+		swap_args();
+		l1c = (byte1 >> 1 & 0x07) | (byte2 >> 3 & 0x18) | (byte1 << 5 & 0x20);
+		set_info(l1c);
+		set_arg1();
+		do_move();
+		break;
+
+	/* 20-2F [32d1] MOVE.L */
+	case 0x10:
+	case 0x11:
+	case 0x12:
+	case 0x13:
+	case 0x14:
+	case 0x15:
+	case 0x16:
+	case 0x17:
+
+#ifdef LOGEMU
+		out("move.l");
+#endif
+		set_info((type8)((byte2 & 0x3f) | 0x80));
+		set_arg1();
+		swap_args();
+		l1c = (byte1 >> 1 & 0x07) | (byte2 >> 3 & 0x18) | (byte1 << 5 & 0x20);
+		set_info((type8)(l1c | 0x80));
+		set_arg1();
+		do_move();
+		break;
+
+	/* 30-3F [3327] MOVE.W */
+	case 0x18:
+	case 0x19:
+	case 0x1a:
+	case 0x1b:
+	case 0x1c:
+	case 0x1d:
+	case 0x1e:
+	case 0x1f:
+
+#ifdef LOGEMU
+		out("move.w");
+#endif
+		set_info((type8)((byte2 & 0x3f) | 0x40));
+		set_arg1();
+		swap_args();
+		l1c = (byte1 >> 1 & 0x07) | (byte2 >> 3 & 0x18) | (byte1 << 5 & 0x20);
+		set_info((type8)(l1c | 0x40));
+		set_arg1();
+		do_move();
+		break;
+
+	/* 40-4F various commands */
+
+	case 0x20:
+		if (byte1 == 0x40) {
+			/* [31d5] */
+			ms_fatal("unimplemented instructions NEGX and MOVE SR,xx");
+		} else
+			check_lea();
+		break;
+
+	case 0x21:
+		if (byte1 == 0x42) {
+			/* [3188] */
+			if ((byte2 & 0xc0) == 0xc0) {
+				ms_fatal("unimplemented instruction MOVE CCR,xx");
+			} else {
+				/* CLR */
+#ifdef LOGEMU
+				out("clr");
+#endif
+				set_info(byte2);
+				set_arg1();
+				if (opsize == 0)
+					arg1[0] = 0;
+				if (opsize == 1)
+					write_w(arg1, 0);
+				if (opsize == 2)
+					write_l(arg1, 0);
+				nflag = cflag = 0;
+				zflag = 0xff;
+			}
+		} else
+			check_lea();
+		break;
+
+	case 0x22:
+		if (byte1 == 0x44) {
+			/* [31a0] */
+			if ((byte2 & 0xc0) == 0xc0) {
+				/* MOVE to CCR */
+#ifdef LOGEMU
+				out("move_ccr");
+#endif
+				zflag = nflag = cflag = vflag = 0;
+				set_info((type8)(byte2 & 0x7f));
+				set_arg1();
+				byte2 = arg1[1];
+				if (byte2 & 0x01)
+					cflag = 0xff;
+				if (byte2 & 0x02)
+					vflag = 0xff;
+				if (byte2 & 0x04)
+					zflag = 0xff;
+				if (byte2 & 0x08)
+					nflag = 0xff;
+			} else {
+#ifdef LOGEMU
+				out("neg");
+#endif
+				set_info(byte2);    /* NEG */
+				set_arg1();
+				cflag = 0xff;
+				if (opsize == 0) {
+					arg1[0] = (-arg1[0]);
+					cflag = arg1[0] ? 0xff : 0;
+				}
+				if (opsize == 1) {
+					write_w(arg1, (type16)(-1 * read_w(arg1)));
+					cflag = read_w(arg1) ? 0xff : 0;
+				}
+				if (opsize == 2) {
+					write_l(arg1, -1 * read_l(arg1));
+					cflag = read_l(arg1) ? 0xff : 0;
+				}
+				vflag = 0;
+				set_flags();
+			}
+		} else
+			check_lea();
+		break;
+
+	case 0x23:
+		if (byte1 == 0x46) {
+			if ((byte2 & 0xc0) == 0xc0) {
+				ms_fatal("unimplemented instruction MOVE xx,SR");
+			} else {
+#ifdef LOGEMU
+				out("not");
+#endif
+				set_info(byte2);    /* NOT */
+				set_arg1();
+				tmparg[0] = tmparg[1] = tmparg[2] = tmparg[3] = 0xff;
+				arg2 = tmparg;
+				do_eor();
+			}
+		} else
+			check_lea();
+		break;
+
+	case 0x24:
+		if (byte1 == 0x48) {
+			if ((byte2 & 0xf8) == 0x40) {
+#ifdef LOGEMU
+				out("swap");
+#endif
+				opsize = 2; /* SWAP */
+				admode = 0;
+				regnr = byte2 & 0x07;
+				set_arg1();
+				tmp32 = read_w(arg1);
+				write_w(arg1, read_w(arg1 + 2));
+				write_w(arg1 + 2, (type16)tmp32);
+				set_flags();
+			} else if ((byte2 & 0xf8) == 0x80) {
+#ifdef LOGEMU
+				out("ext.w");
+#endif
+				opsize = 1; /* EXT.W */
+				admode = 0;
+				regnr = byte2 & 0x07;
+				set_arg1();
+				if (arg1[1] > 0x7f)
+					arg1[0] = 0xff;
+				else
+					arg1[0] = 0;
+				set_flags();
+			} else if ((byte2 & 0xf8) == 0xc0) {
+#ifdef LOGEMU
+				out("ext.l");
+#endif
+				opsize = 2; /* EXT.L */
+				admode = 0;
+				regnr = byte2 & 0x07;
+				set_arg1();
+				if (read_w(arg1 + 2) > 0x7fff)
+					write_w(arg1, 0xffff);
+				else
+					write_w(arg1, 0);
+				set_flags();
+			} else if ((byte2 & 0xc0) == 0x40) {
+#ifdef LOGEMU
+				out("pea");
+#endif
+				set_info((type8)((byte2 & 0x3f) | 0x80));   /* PEA */
+				set_arg1();
+				if (is_reversible)
+					push(arg1i);
+				else
+					ms_fatal("illegal addressing mode for PEA");
+			} else {
+				check_movem();  /* MOVEM */
+			}
+		} else
+			check_lea();
+		break;
+
+	case 0x25:
+		if (byte1 == 0x4a) {
+			/* [3219] TST */
+			if ((byte2 & 0xc0) == 0xc0) {
+				ms_fatal("unimplemented instruction TAS");
+			} else {
+#ifdef LOGEMU
+				out("tst");
+#endif
+				set_info(byte2);
+				set_arg1();
+				cflag = vflag = 0;
+				set_flags();
+			}
+		} else
+			check_lea();
+		break;
+
+	case 0x26:
+		if (byte1 == 0x4c)
+			check_movem2();     /* [3350] MOVEM.L (Ax)+,A/Dx */
+		else
+			check_lea();    /* LEA */
+		break;
+
+	case 0x27:
+		if (byte1 == 0x4e) {
+			/* [3290] */
+			if (byte2 == 0x75) {
+				/* RTS */
+#ifdef LOGEMU
+				out("rts\n");
+#endif
+				pc = pop();
+			} else if (byte2 == 0x71) {
+				/* NOP */
+#ifdef LOGEMU
+				out("nop");
+#endif
+			} else if ((byte2 & 0xc0) == 0xc0) {
+				/* indir JMP */
+#ifdef LOGEMU
+				out("jmp");
+#endif
+				set_info((type8)(byte2 | 0xc0));
+				set_arg1();
+				if (is_reversible)
+					pc = arg1i;
+				else
+					ms_fatal("illegal addressing mode for JMP");
+			} else if ((byte2 & 0xc0) == 0x80) {
+#ifdef LOGEMU
+				out("jsr");
+#endif
+				set_info((type8)(byte2 | 0xc0));        /* indir JSR */
+				set_arg1();
+				push(pc);
+				if (is_reversible)
+					pc = arg1i;
+				else
+					ms_fatal("illegal addressing mode for JSR");
+			} else {
+				ms_fatal("unimplemented instructions 0x4EXX");
+			}
+		} else
+			check_lea();    /* LEA */
+		break;
+
+	/* 50-5F [2ed5] ADDQ/SUBQ/Scc/DBcc */
+	case 0x28:
+	case 0x29:
+	case 0x2a:
+	case 0x2b:
+	case 0x2c:
+	case 0x2d:
+	case 0x2e:
+	case 0x2f:
+
+		if ((byte2 & 0xc0) == 0xc0) {
+			set_info((type8)(byte2 & 0x3f));
+			set_arg1();
+			if (admode == 1) {
+				/* DBcc */
+#ifdef LOGEMU
+				out("dbcc");
+#endif
+				if (condition(byte1) == 0) {
+					arg1 = (arg1 - (type8 *) areg) + (type8 *) dreg - 1;
+					write_w(arg1, (type16)(read_w(arg1) - 1));
+					if (read_w(arg1) != 0xffff)
+						branch(0);
+					else
+						pc += 2;
+				} else
+					pc += 2;
+			} else {
+				/* Scc */
+#ifdef LOGEMU
+				out("scc");
+#endif
+				arg1[0] = condition(byte1) ? 0xff : 0;
+			}
+		} else {
+			set_info(byte2);
+			set_arg1();
+			quick_flag = (admode == 1) ? 0xff : 0;
+			l1c = byte1 >> 1 & 0x07;
+			tmparg[0] = tmparg[1] = tmparg[2] = 0;
+			tmparg[3] = l1c ? l1c : 8;
+			arg2 = reg_align(tmparg, opsize);
+			{
+#ifdef LOGEMU
+				type32s outnum = 0;
+				switch (opsize) {
+				case 0:
+					outnum = (type8s) arg2[0];
+					break;
+				case 1:
+					outnum = (type16s) read_w(arg2);
+					break;
+				case 2:
+					outnum = (type32s) read_l(arg2);
+					break;
+				}
+#endif
+				if ((byte1 & 0x01) == 1) {
+#ifdef LOGEMU
+					out("subq #%.8X", outnum);
+#endif
+					do_sub(0);  /* SUBQ */
+				} else {
+#ifdef LOGEMU
+					out("addq #%.8X", outnum);
+#endif
+					do_add(0);  /* ADDQ */
+				}
+			}
+		}
+		break;
+
+	/* 60-6F [26ba] Bcc */
+
+	case 0x30:
+		if (byte1 == 0x61) {
+			/* BRA, BSR */
+#ifdef LOGEMU
+			out("bsr");
+#endif
+			if (byte2 == 0)
+				push(pc + 2);
+			else
+				push(pc);
+		}
+#ifdef LOGEMU
+		else
+			out("bra");
+#endif
+		if ((byte1 == 0x60) && (byte2 == 0xfe)) {
+			ms_flush(); /* flush stdout */
+			ms_stop();  /* infinite loop - just exit */
+		}
+		branch(byte2);
+		break;
+
+	case 0x31:
+	case 0x32:
+	case 0x33:
+	case 0x34:
+	case 0x35:
+	case 0x36:
+	case 0x37:
+
+		if (condition(byte1) == 0) {
+#ifdef LOGEMU
+			out("beq.s");
+#endif
+			if (byte2 == 0)
+				pc += 2;
+		} else {
+#ifdef LOGEMU
+			out("bra");
+#endif
+			branch(byte2);
+		}
+		break;
+
+	/* 70-7F [260a] MOVEQ */
+	case 0x38:
+	case 0x39:
+	case 0x3a:
+	case 0x3b:
+	case 0x3c:
+	case 0x3d:
+	case 0x3e:
+	case 0x3f:
+
+#ifdef LOGEMU
+		out("moveq");
+#endif
+		arg1 = (type8 *) & dreg[byte1 >> 1 & 0x07];
+		if (byte2 > 127)
+			nflag = arg1[0] = arg1[1] = arg1[2] = 0xff;
+		else
+			nflag = arg1[0] = arg1[1] = arg1[2] = 0;
+		arg1[3] = byte2;
+		zflag = byte2 ? 0 : 0xff;
+		break;
+
+	/* 80-8F [2f36] */
+	case 0x40:
+	case 0x41:
+	case 0x42:
+	case 0x43:
+	case 0x44:
+	case 0x45:
+	case 0x46:
+	case 0x47:
+
+		if ((byte2 & 0xc0) == 0xc0) {
+			ms_fatal("unimplemented instructions DIVS and DIVU");
+		} else if (((byte2 & 0xf0) == 0) && ((byte1 & 0x01) != 0)) {
+			ms_fatal("unimplemented instruction SBCD");
+		} else {
+#ifdef LOGEMU
+			out("or?");
+#endif
+			set_info(byte2);
+			set_arg1();
+			set_arg2(1, byte1);
+			if ((byte1 & 0x01) == 0)
+				swap_args();
+			do_or();
+		}
+		break;
+
+	/* 90-9F [3005] SUB */
+	case 0x48:
+	case 0x49:
+	case 0x4a:
+	case 0x4b:
+	case 0x4c:
+	case 0x4d:
+	case 0x4e:
+	case 0x4f:
+
+#ifdef LOGEMU
+		out("sub");
+#endif
+		quick_flag = 0;
+		if ((byte2 & 0xc0) == 0xc0) {
+			if ((byte1 & 0x01) == 1)
+				set_info((type8)(byte2 & 0xbf));
+			else
+				set_info((type8)(byte2 & 0x7f));
+			set_arg1();
+			set_arg2_nosize(0, byte1);
+			swap_args();
+			do_sub(1);
+		} else {
+			set_info(byte2);
+			set_arg1();
+			set_arg2(1, byte1);
+			if ((byte1 & 0x01) == 0)
+				swap_args();
+			do_sub(0);
+		}
+		break;
+
+	/* A0-AF various special commands [LINE_A] */
+
+	case 0x50:
+	case 0x56:
+	case 0x57:        /* [2521] */
+		do_line_a();
+#ifdef LOGEMU
+		out("LINE_A A0%.2X", byte2);
+#endif
+		break;
+
+	case 0x51:
+#ifdef LOGEMU
+		out("rts\n");
+#endif
+		pc = pop(); /* RTS */
+		break;
+
+	case 0x52:
+#ifdef LOGEMU
+		out("bsr");
+#endif
+		if (byte2 == 0)
+			push(pc + 2);   /* BSR */
+		else
+			push(pc);
+		branch(byte2);
+		break;
+
+	case 0x53:
+		if ((byte2 & 0xc0) == 0xc0) {
+			/* TST [321d] */
+			ms_fatal("unimplemented instructions LINE_A #$6C0-#$6FF");
+		} else {
+#ifdef LOGEMU
+			out("tst");
+#endif
+			set_info(byte2);
+			set_arg1();
+			cflag = vflag = 0;
+			set_flags();
+		}
+		break;
+
+	case 0x54:
+		check_movem();
+		break;
+
+	case 0x55:
+		check_movem2();
+		break;
+
+	/* B0-BF [2fe4] */
+	case 0x58:
+	case 0x59:
+	case 0x5a:
+	case 0x5b:
+	case 0x5c:
+	case 0x5d:
+	case 0x5e:
+	case 0x5f:
+
+		if ((byte2 & 0xc0) == 0xc0) {
+#ifdef LOGEMU
+			out("cmp");
+#endif
+			if ((byte1 & 0x01) == 1)
+				set_info((type8)(byte2 & 0xbf));
+			else
+				set_info((type8)(byte2 & 0x7f));
+			set_arg1();
+			set_arg2(0, byte1);
+			swap_args();
+			do_cmp();   /* CMP */
+		} else {
+			if ((byte1 & 0x01) == 0) {
+#ifdef LOGEMU
+				out("cmp");
+#endif
+				set_info(byte2);
+				set_arg1();
+				set_arg2(1, byte1);
+				swap_args();
+				do_cmp();   /* CMP */
+			} else {
+#ifdef LOGEMU
+				out("eor");
+#endif
+				set_info(byte2);
+				set_arg1();
+				set_arg2(1, byte1);
+				do_eor();   /* EOR */
+			}
+		}
+		break;
+
+	/* C0-CF [2f52] EXG, AND */
+	case 0x60:
+	case 0x61:
+	case 0x62:
+	case 0x63:
+	case 0x64:
+	case 0x65:
+	case 0x66:
+	case 0x67:
+
+		if ((byte1 & 0x01) == 0) {
+			if ((byte2 & 0xc0) == 0xc0) {
+				ms_fatal("unimplemented instruction MULU");
+			} else {
+				/* AND */
+#ifdef LOGEMU
+				out("and");
+#endif
+				set_info(byte2);
+				set_arg1();
+				set_arg2(1, byte1);
+				if ((byte1 & 0x01) == 0)
+					swap_args();
+				do_and();
+			}
+		} else {
+			if ((byte2 & 0xf8) == 0x40) {
+#ifdef LOGEMU
+				out("exg (dx)");
+#endif
+				opsize = 2; /* EXG Dx,Dx */
+				set_arg2(1, (type8)(byte2 << 1));
+				swap_args();
+				set_arg2(1, byte1);
+				tmp32 = read_l(arg1);
+				write_l(arg1, read_l(arg2));
+				write_l(arg2, tmp32);
+			} else if ((byte2 & 0xf8) == 0x48) {
+				opsize = 2; /* EXG Ax,Ax */
+#ifdef LOGEMU
+				out("exg (ax)");
+#endif
+				set_arg2(0, (type8)(byte2 << 1));
+				swap_args();
+				set_arg2(0, byte1);
+				tmp32 = read_l(arg1);
+				write_l(arg1, read_l(arg2));
+				write_l(arg2, tmp32);
+			} else if ((byte2 & 0xf8) == 0x88) {
+				opsize = 2; /* EXG Dx,Ax */
+#ifdef LOGEMU
+				out("exg (dx,ax)");
+#endif
+				set_arg2(0, (type8)(byte2 << 1));
+				swap_args();
+				set_arg2(1, byte1);
+				tmp32 = read_l(arg1);
+				write_l(arg1, read_l(arg2));
+				write_l(arg2, tmp32);
+			} else {
+				if ((byte2 & 0xc0) == 0xc0) {
+					ms_fatal("unimplemented instruction MULS");
+				} else {
+					set_info(byte2);
+					set_arg1();
+					set_arg2(1, byte1);
+					if ((byte1 & 0x01) == 0)
+						swap_args();
+					do_and();
+				}
+			}
+		}
+		break;
+
+	/* D0-DF [2fc8] ADD */
+	case 0x68:
+	case 0x69:
+	case 0x6a:
+	case 0x6b:
+	case 0x6c:
+	case 0x6d:
+	case 0x6e:
+	case 0x6f:
+
+#ifdef LOGEMU
+		out("add");
+#endif
+		quick_flag = 0;
+		if ((byte2 & 0xc0) == 0xc0) {
+			if ((byte1 & 0x01) == 1)
+				set_info((type8)(byte2 & 0xbf));
+			else
+				set_info((type8)(byte2 & 0x7f));
+			set_arg1();
+			set_arg2_nosize(0, byte1);
+			swap_args();
+			do_add(1);
+		} else {
+			set_info(byte2);
+			set_arg1();
+			set_arg2(1, byte1);
+			if ((byte1 & 0x01) == 0)
+				swap_args();
+			do_add(0);
+		}
+		break;
+
+	/* E0-EF [3479] LSR ASL ROR ROL */
+	case 0x70:
+	case 0x71:
+	case 0x72:
+	case 0x73:
+	case 0x74:
+	case 0x75:
+	case 0x76:
+	case 0x77:
+
+#ifdef LOGEMU
+		out("lsr,asl,ror,rol");
+#endif
+		if ((byte2 & 0xc0) == 0xc0) {
+			set_info((type8)(byte2 & 0xbf));        /* OP Dx */
+			set_arg1();
+			l1c = 1;    /* steps=1 */
+			byte2 = (byte1 >> 1) & 0x03;
+		} else {
+			set_info((type8)(byte2 & 0xc7));
+			set_arg1();
+			if ((byte2 & 0x20) == 0) {
+				/* immediate */
+				l1c = (byte1 >> 1) & 0x07;
+				if (l1c == 0)
+					l1c = 8;
+			} else {
+				l1c = (type8)read_reg(byte1 >> 1 & 0x07, 0);
+			}
+			byte2 = (byte2 >> 3) & 0x03;
+		}
+		if ((byte1 & 0x01) == 0) {
+			/* right */
+			while (l1c-- > 0) {
+				if (opsize == 0) {
+					cflag = arg1[0] & 0x01 ? 0xff : 0;
+					arg1[0] >>= 1;
+					if (cflag && (byte2 == 3))
+						arg1[0] |= 0x80;
+				}
+				if (opsize == 1) {
+					cflag = read_w(arg1) & 0x01 ? 0xff : 0;
+					write_w(arg1, (type16)(read_w(arg1) >> 1));
+					if (cflag && (byte2 == 3))
+						write_w(arg1, (type16)(read_w(arg1) | ((type16) 1 << 15)));
+				}
+				if (opsize == 2) {
+					cflag = read_l(arg1) & 0x01 ? 0xff : 0;
+					write_l(arg1, read_l(arg1) >> 1);
+					if (cflag && (byte2 == 3))
+						write_l(arg1, read_l(arg1) | ((type32) 1 << 31));
+				}
+			}
+		} else {
+			/* left */
+			while (l1c-- > 0) {
+				if (opsize == 0) {
+					cflag = arg1[0] & 0x80 ? 0xff : 0;  /* [3527] */
+					arg1[0] <<= 1;
+					if (cflag && (byte2 == 3))
+						arg1[0] |= 0x01;
+				}
+				if (opsize == 1) {
+					cflag = read_w(arg1) & ((type16) 1 << 15) ? 0xff : 0;
+					write_w(arg1, (type16)(read_w(arg1) << 1));
+					if (cflag && (byte2 == 3))
+						write_w(arg1, (type16)(read_w(arg1) | 0x01));
+				}
+				if (opsize == 2) {
+					cflag = read_l(arg1) & ((type32) 1 << 31) ? 0xff : 0;
+					write_l(arg1, read_l(arg1) << 1);
+					if (cflag && (byte2 == 3))
+						write_l(arg1, read_l(arg1) | 0x01);
+				}
+			}
+		}
+		set_flags();
+		break;
+
+	/* F0-FF [24f3] LINE_F */
+	case 0x78:
+	case 0x79:
+	case 0x7a:
+	case 0x7b:
+	case 0x7c:
+	case 0x7d:
+	case 0x7e:
+	case 0x7f:
+
+		if (version == 0) {
+			/* hardcoded jump */
+			char_out(l1c = (type8)read_reg(1, 0));
+		} else if (version == 1) {
+			/* single programmable shortcut */
+			push(pc);
+			pc = fl_sub;
+		} else {
+			/* programmable shortcuts from table */
+#ifdef LOGEMU
+			out("LINK: %.2X,%.2X", byte1, byte2);
+#endif
+			ptr = (byte1 & 7) << 8 | byte2;
+			if (ptr >= fl_size) {
+				if ((byte1 & 8) == 0)
+					push(pc);
+				ptr = byte1 << 8 | byte2 | 0x0800;
+				ptr = fl_tab + 2 * (ptr ^ 0xffff);
+				pc = (type32) ptr + (type16s) read_w(effective(ptr));
+			} else {
+				push(pc);
+				pc = fl_sub;
+			}
+		}
+		break;
+
+	default:
+		ms_fatal("Constants aren't and variables don't");
+		break;
+	}
+#ifdef LOGEMU
+	fprintf(dbg_log, "\n");
+#endif
+	return running;
 }
 
 } // End of namespace Magnetic
diff --git a/engines/glk/magnetic/glk.cpp b/engines/glk/magnetic/glk.cpp
new file mode 100644
index 0000000..db15cd1
--- /dev/null
+++ b/engines/glk/magnetic/glk.cpp
@@ -0,0 +1,5393 @@
+/* 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/magnetic/defs.h"
+#include "glk/magnetic/magnetic.h"
+
+namespace Glk {
+namespace Magnetic {
+
+/*
+ * True and false definitions -- usually defined in glkstart.h, but we need
+ * them early, so we'll define them here too.  We also need NULL, but that's
+ * normally from stdio.h or one of it's cousins.
+ */
+#ifndef FALSE
+# define FALSE false
+#endif
+#ifndef TRUE
+# define TRUE false
+#endif
+
+#define BYTE_MAX 255
+#define CHAR_BIT 8
+#define UINT16_MAX 0xffff
+#define INT32_MAX 0x7fffffff
+
+/*---------------------------------------------------------------------*/
+/*  Module variables, miscellaneous other stuff                        */
+/*---------------------------------------------------------------------*/
+
+/* Glk Magnetic Scrolls port version number. */
+static const glui32 GMS_PORT_VERSION = 0x00010601;
+
+/*
+ * We use a maximum of five Glk windows, one for status, one for pictures,
+ * two for hints, and one for everything else.  The status and pictures
+ * windows may be NULL, depending on user selections and the capabilities
+ * of the Glk library.  The hints windows will normally be NULL, except
+ * when in the hints subsystem.
+ */
+static winid_t gms_main_window = NULL,
+               gms_status_window = NULL,
+               gms_graphics_window = NULL,
+               gms_hint_menu_window = NULL,
+               gms_hint_text_window = NULL;
+
+/*
+ * Transcript stream and input log.  These are NULL if there is no current
+ * collection of these strings.
+ */
+static strid_t gms_transcript_stream = NULL,
+               gms_inputlog_stream = NULL;
+
+/* Input read log stream, for reading back an input log. */
+static strid_t gms_readlog_stream = NULL;
+
+/* Note about whether graphics is possible, or not. */
+static int gms_graphics_possible = TRUE;
+
+/* Options that may be turned off or set by command line flags. */
+static int gms_graphics_enabled = TRUE;
+enum GammaMode {
+	GAMMA_OFF, GAMMA_NORMAL, GAMMA_HIGH
+};
+static GammaMode gms_gamma_mode = GAMMA_NORMAL;
+static int gms_animation_enabled = TRUE,
+           gms_prompt_enabled = TRUE,
+           gms_abbreviations_enabled = TRUE,
+           gms_commands_enabled = TRUE;
+
+/* Magnetic Scrolls standard input prompt string. */
+static const char *const GMS_INPUT_PROMPT = ">";
+
+/* Forward declaration of event wait function. */
+static void gms_event_wait(glui32 wait_type, event_t *event);
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port utility functions                                         */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gms_fatal()
+ *
+ * Fatal error handler.  The function returns, expecting the caller to
+ * abort() or otherwise handle the error.
+ */
+static void gms_fatal(const char *string) {
+	/*
+	 * If the failure happens too early for us to have a window, print
+	 * the message to stderr.
+	 */
+	if (!gms_main_window)
+		error("\n\nINTERNAL ERROR: %s", string);
+
+	/* Cancel all possible pending window input events. */
+	g_vm->glk_cancel_line_event(gms_main_window, NULL);
+	g_vm->glk_cancel_char_event(gms_main_window);
+	if (gms_hint_menu_window) {
+		g_vm->glk_cancel_char_event(gms_hint_menu_window);
+		g_vm->glk_window_close(gms_hint_menu_window, NULL);
+	}
+	if (gms_hint_text_window) {
+		g_vm->glk_cancel_char_event(gms_hint_text_window);
+		g_vm->glk_window_close(gms_hint_text_window, NULL);
+	}
+
+	/* Print a message indicating the error. */
+	g_vm->glk_set_window(gms_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");
+}
+
+
+/*
+ * gms_malloc()
+ * gms_realloc()
+ *
+ * Non-failing malloc and realloc; call gms_fatal and exit if memory
+ * allocation fails.
+ */
+static void *gms_malloc(size_t size) {
+	void *pointer;
+
+	pointer = malloc(size);
+	if (!pointer) {
+		gms_fatal("GLK: Out of system memory");
+		g_vm->glk_exit();
+	}
+
+	return pointer;
+}
+
+static void *gms_realloc(void *ptr, size_t size) {
+	void *pointer;
+
+	pointer = realloc(ptr, size);
+	if (!pointer) {
+		gms_fatal("GLK: Out of system memory");
+		g_vm->glk_exit();
+	}
+
+	return pointer;
+}
+
+
+/*
+ * gms_strncasecmp()
+ * gms_strcasecmp()
+ *
+ * Strncasecmp and strcasecmp are not ANSI functions, so here are local
+ * definitions to do the same jobs.
+ */
+static int gms_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;
+}
+
+static int gms_strcasecmp(const char *s1, const char *s2) {
+	size_t s1len, s2len;
+	int result;
+
+	s1len = strlen(s1);
+	s2len = strlen(s2);
+
+	result = gms_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 CRC functions                                             */
+/*---------------------------------------------------------------------*/
+
+/* CRC table initialization polynomial. */
+static const glui32 GMS_CRC_POLYNOMIAL = 0xedb88320;
+
+
+/*
+ * gms_get_buffer_crc()
+ *
+ * Return the CRC of the bytes in buffer[0..length-1].
+ *
+ * This algorithm is taken from the PNG specification, version 1.0.
+ */
+static glui32 gms_get_buffer_crc(const void *void_buffer, size_t length) {
+	static int is_initialized = FALSE;
+	static glui32 crc_table[BYTE_MAX + 1];
+
+	const char *buffer = (const char *) void_buffer;
+	glui32 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 = (glui32) index;
+			for (bit = 0; bit < CHAR_BIT; bit++)
+				crc = crc & 1 ? GMS_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
+
+			crc_table[index] = crc;
+		}
+
+		is_initialized = TRUE;
+
+		/* CRC lookup table self-test, after is_initialized set -- recursion. */
+		assert(gms_get_buffer_crc("123456789", 9) == 0xcbf43926);
+	}
+
+	/*
+	 * Start with all ones in the crc, then update using table entries.  Xor
+	 * with all ones again, finally, before returning.
+	 */
+	crc = 0xffffffff;
+	for (index = 0; index < length; index++)
+		crc = crc_table[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> CHAR_BIT);
+	return crc ^ 0xffffffff;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  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.  Set on game startup, by
+ * identifying the game from its text file header.
+ */
+static const char *gms_gameid_game_name = NULL;
+
+
+/*
+ * The following game database is built from Generic/games.txt, and is used
+ * to identify the game being run.  Magnetic Scrolls games don't generally
+ * supply a status line, so this data can be used instead.
+ */
+struct gms_game_table_t {
+	const type32 undo_size;   /* Header word at offset 0x22 */
+	const type32 undo_pc;     /* Header word at offset 0x26 */
+	const char *const name;   /* Game title and platform */
+};
+typedef const gms_game_table_t *gms_game_tableref_t;
+
+static const gms_game_table_t GMS_GAME_TABLE[] = {
+	{0x2100, 0x427e, "Corruption v1.11 (Amstrad CPC)"},
+	{0x2100, 0x43a0, "Corruption v1.11 (Archimedes)"},
+	{0x2100, 0x43a0, "Corruption v1.11 (DOS)"},
+	{0x2100, 0x4336, "Corruption v1.11 (Commodore 64)"},
+	{0x2100, 0x4222, "Corruption v1.11 (Spectrum +3)"},
+	{0x2100, 0x4350, "Corruption v1.12 (Archimedes)"},
+	{0x2500, 0x6624, "Corruption v1.12 (DOS, Magnetic Windows)"},
+
+	{0x2300, 0x3fa0, "Fish v1.02 (DOS)"},
+	{0x2400, 0x4364, "Fish v1.03 (Spectrum +3)"},
+	{0x2300, 0x3f72, "Fish v1.07 (Commodore 64)"},
+	{0x2200, 0x3f9c, "Fish v1.08 (Archimedes)"},
+	{0x2a00, 0x583a, "Fish v1.10 (DOS, Magnetic Windows)"},
+
+	{0x5000, 0x6c30, "Guild v1.0 (Amstrad CPC)"},
+	{0x5000, 0x6cac, "Guild v1.0 (Commodore 64)"},
+	{0x5000, 0x6d5c, "Guild v1.1 (DOS)"},
+	{0x3300, 0x698a, "Guild v1.3 (Archimedes)"},
+	{0x3200, 0x6772, "Guild v1.3 (Spectrum +3)"},
+	{0x3400, 0x6528, "Guild v1.3 (DOS, Magnetic Windows)"},
+
+	{0x2b00, 0x488c, "Jinxter v1.05 (Commodore 64)"},
+	{0x2c00, 0x4a08, "Jinxter v1.05 (DOS)"},
+	{0x2c00, 0x487a, "Jinxter v1.05 (Spectrum +3)"},
+	{0x2c00, 0x4a56, "Jinxter v1.10 (DOS)"},
+	{0x2b00, 0x4924, "Jinxter v1.22 (Amstrad CPC)"},
+	{0x2c00, 0x4960, "Jinxter v1.30 (Archimedes)"},
+
+	{0x1600, 0x3940, "Myth v1.0 (Commodore 64)"},
+	{0x1500, 0x3a0a, "Myth v1.0 (DOS)"},
+
+	{0x3600, 0x42cc, "Pawn v2.3 (Amstrad CPC)"},
+	{0x3600, 0x4420, "Pawn v2.3 (Archimedes)"},
+	{0x3600, 0x3fb0, "Pawn v2.3 (Commodore 64)"},
+	{0x3600, 0x4420, "Pawn v2.3 (DOS)"},
+	{0x3900, 0x42e4, "Pawn v2.3 (Spectrum 128)"},
+	{0x3900, 0x42f4, "Pawn v2.4 (Spectrum +3)"},
+
+	{0x3900, 0x75f2, "Wonderland v1.21 (DOS, Magnetic Windows)"},
+	{0x3900, 0x75f8, "Wonderland v1.27 (Archimedes)"},
+	{0, 0, NULL}
+};
+
+
+/*
+ * gms_gameid_lookup_game()
+ *
+ * Look up and return the game table entry given a game's undo size and
+ * undo pc values.  Returns the entry, or NULL if not found.
+ */
+static gms_game_tableref_t gms_gameid_lookup_game(type32 undo_size, type32 undo_pc) {
+	gms_game_tableref_t game;
+
+	for (game = GMS_GAME_TABLE; game->name; game++) {
+		if (game->undo_size == undo_size && game->undo_pc == undo_pc)
+			break;
+	}
+
+	return game->name ? game : NULL;
+}
+
+
+/*
+ * gms_gameid_read_uint32()
+ *
+ * Endian-safe unsigned 32 bit integer read from game text file.  Returns
+ * 0 on error, a known unused table value.
+ */
+static type32 gms_gameid_read_uint32(int offset, Common::SeekableReadStream *stream) {
+	if (!stream->seek(offset))
+		return 0;
+	return stream->readUint32LE();
+}
+
+
+/*
+ * gms_gameid_identify_game()
+ *
+ * Identify a game from its text file header, and cache the game's name for
+ * later queries.  Sets the cache to NULL if not found.
+ */
+static void gms_gameid_identify_game(const char *text_file) {
+	Common::File stream;
+
+	if (stream.open(text_file)) {
+		type32 undo_size, undo_pc;
+		gms_game_tableref_t game;
+
+		/* Read the game's signature undo size and undo pc values. */
+		undo_size = gms_gameid_read_uint32(0x22, &stream);
+		undo_pc = gms_gameid_read_uint32(0x26, &stream);
+		stream.close();
+
+		/* Search for these values in the table, and set game name if found. */
+		game = gms_gameid_lookup_game(undo_size, undo_pc);
+		gms_gameid_game_name = game ? game->name : NULL;
+	} else {
+		gms_gameid_game_name = NULL;
+	}
+}
+
+
+/*
+ * gms_gameid_get_game_name()
+ *
+ * Return the name of the game, or NULL if not identifiable.
+ */
+static const char *gms_gameid_get_game_name(void) {
+	return gms_gameid_game_name;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port picture functions                                         */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Color conversions lookup tables, and a word about gamma corrections.
+ *
+ * When uncorrected, some game pictures can look dark (Corruption, Won-
+ * derland), whereas others look just fine (Guild Of Thieves, Jinxter).
+ *
+ * The standard general-purpose gamma correction is around 2.1, with
+ * specific values, normally, of 2.5-2.7 for IBM PC systems, and 1.8 for
+ * Macintosh.  However, applying even the low end of this range can make
+ * some pictures look washed out, yet improve others nicely.
+ *
+ * To try to solve this, here we'll set up a precalculated table with
+ * discrete gamma values.  On displaying a picture, we'll try to find a
+ * gamma correction that seems to offer a reasonable level of contrast
+ * for the picture.
+ *
+ * Here's an AWK script to create the gamma table:
+ *
+ * BEGIN { max=255.0; step=max/7.0
+ *         for (gamma=0.9; gamma<=2.7; gamma+=0.05) {
+ *             printf "  {\"%2.2f\", {0, ", gamma
+ *             for (i=1; i<8; i++) {
+ *                 printf "%3.0f", (((step*i / max) ^ (1.0/gamma)) * max)
+ *                 printf "%s", (i<7) ? ", " : ""
+ *             }
+ *             printf "}, "
+ *             printf "%s },\n", (gamma>0.99 && gamma<1.01) ? "FALSE" : "TRUE "
+ *         } }
+ *
+ */
+typedef const struct {
+	const char *const level;       /* Gamma correction level. */
+	const unsigned char table[8];  /* Color lookup table. */
+	const int is_corrected;        /* Flag if non-linear. */
+} gms_gamma_t;
+typedef gms_gamma_t *gms_gammaref_t;
+
+static gms_gamma_t GMS_GAMMA_TABLE[] = {
+	{"0.90", {0,  29,  63,  99, 137, 175, 215, 255}, TRUE},
+	{"0.95", {0,  33,  68, 105, 141, 179, 217, 255}, TRUE},
+	{"1.00", {0,  36,  73, 109, 146, 182, 219, 255}, FALSE},
+	{"1.05", {0,  40,  77, 114, 150, 185, 220, 255}, TRUE},
+	{"1.10", {0,  43,  82, 118, 153, 188, 222, 255}, TRUE},
+	{"1.15", {0,  47,  86, 122, 157, 190, 223, 255}, TRUE},
+	{"1.20", {0,  50,  90, 126, 160, 193, 224, 255}, TRUE},
+	{"1.25", {0,  54,  94, 129, 163, 195, 225, 255}, TRUE},
+	{"1.30", {0,  57,  97, 133, 166, 197, 226, 255}, TRUE},
+	{"1.35", {0,  60, 101, 136, 168, 199, 227, 255}, TRUE},
+	{"1.40", {0,  64, 104, 139, 171, 201, 228, 255}, TRUE},
+	{"1.45", {0,  67, 107, 142, 173, 202, 229, 255}, TRUE},
+	{"1.50", {0,  70, 111, 145, 176, 204, 230, 255}, TRUE},
+	{"1.55", {0,  73, 114, 148, 178, 205, 231, 255}, TRUE},
+	{"1.60", {0,  76, 117, 150, 180, 207, 232, 255}, TRUE},
+	{"1.65", {0,  78, 119, 153, 182, 208, 232, 255}, TRUE},
+	{"1.70", {0,  81, 122, 155, 183, 209, 233, 255}, TRUE},
+	{"1.75", {0,  84, 125, 157, 185, 210, 233, 255}, TRUE},
+	{"1.80", {0,  87, 127, 159, 187, 212, 234, 255}, TRUE},
+	{"1.85", {0,  89, 130, 161, 188, 213, 235, 255}, TRUE},
+	{"1.90", {0,  92, 132, 163, 190, 214, 235, 255}, TRUE},
+	{"1.95", {0,  94, 134, 165, 191, 215, 236, 255}, TRUE},
+	{"2.00", {0,  96, 136, 167, 193, 216, 236, 255}, TRUE},
+	{"2.05", {0,  99, 138, 169, 194, 216, 237, 255}, TRUE},
+	{"2.10", {0, 101, 140, 170, 195, 217, 237, 255}, TRUE},
+	{"2.15", {0, 103, 142, 172, 197, 218, 237, 255}, TRUE},
+	{"2.20", {0, 105, 144, 173, 198, 219, 238, 255}, TRUE},
+	{"2.25", {0, 107, 146, 175, 199, 220, 238, 255}, TRUE},
+	{"2.30", {0, 109, 148, 176, 200, 220, 238, 255}, TRUE},
+	{"2.35", {0, 111, 150, 178, 201, 221, 239, 255}, TRUE},
+	{"2.40", {0, 113, 151, 179, 202, 222, 239, 255}, TRUE},
+	{"2.45", {0, 115, 153, 180, 203, 222, 239, 255}, TRUE},
+	{"2.50", {0, 117, 154, 182, 204, 223, 240, 255}, TRUE},
+	{"2.55", {0, 119, 156, 183, 205, 223, 240, 255}, TRUE},
+	{"2.60", {0, 121, 158, 184, 206, 224, 240, 255}, TRUE},
+	{"2.65", {0, 122, 159, 185, 206, 225, 241, 255}, TRUE},
+	{"2.70", {0, 124, 160, 186, 207, 225, 241, 255}, TRUE},
+	{NULL,   {0,   0,   0,   0,   0,   0,   0,   0}, FALSE}
+};
+
+/* R,G,B color triple definition. */
+typedef struct {
+	int red, green, blue;
+} gms_rgb_t;
+typedef gms_rgb_t *gms_rgbref_t;
+
+/*
+ * Weighting values for calculating the luminance of a color.  There are
+ * two commonly used sets of values for these -- 299,587,114, taken from
+ * NTSC (Never The Same Color) 1953 standards, and 212,716,72, which is the
+ * set that modern CRTs tend to match.  The NTSC ones seem to give the best
+ * subjective results.
+ */
+static const gms_rgb_t GMS_LUMINANCE_WEIGHTS = { 299, 587, 114 };
+
+/*
+ * 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 GMS_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 GMS_GRAPHICS_TIMEOUT = 50;
+
+/*
+ * Count of timeouts to wait in between animation paints, and to wait on
+ * repaint request.  Waiting for 2 timeouts of around 50ms, gets us to the
+ * 100ms recommended animation frame rate.  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 GMS_GRAPHICS_ANIMATION_WAIT = 2,
+                 GMS_GRAPHICS_REPAINT_WAIT = 10;
+
+/* Pixel size multiplier for image size scaling. */
+static const int GMS_GRAPHICS_PIXEL = 2;
+
+/* Proportion of the display to use for graphics. */
+static const glui32 GMS_GRAPHICS_PROPORTION = 60;
+
+/*
+ * 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 GMS_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
+                    GMS_GRAPHICS_BORDER_COLOR = 0x00000000;
+static const int GMS_GRAPHICS_BORDER = 1,
+                 GMS_GRAPHICS_SHADING = 2,
+                 GMS_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 GMS_GRAPHICS_UNUSED_PIXEL = 0xff;
+
+/*
+ * The current picture bitmap being displayed, its width, height, palette,
+ * animation flag, and picture id.
+ */
+enum { GMS_PALETTE_SIZE = 16 };
+static type8 *gms_graphics_bitmap = NULL;
+static type16 gms_graphics_width = 0,
+              gms_graphics_height = 0,
+              gms_graphics_palette[GMS_PALETTE_SIZE]; /* = { 0, ... }; */
+static bool gms_graphics_animated = FALSE;
+static type32 gms_graphics_picture = 0;
+
+/*
+ * Flags set on new picture, and on resize or arrange events, and a flag
+ * to indicate whether background repaint is stopped or active.
+ */
+static bool gms_graphics_new_picture = FALSE,
+            gms_graphics_repaint = FALSE,
+            gms_graphics_active = FALSE;
+
+/* Flag to try to monitor the state of interpreter graphics. */
+static bool gms_graphics_interpreter = FALSE;
+
+/*
+ * 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 type8 *gms_graphics_off_screen = NULL,
+              *gms_graphics_on_screen = NULL;
+
+/*
+ * Pointer to the current active gamma table entry.  Because of the way
+ * it's queried, this may not be NULL, otherwise we risk a race, with
+ * admittedly a very low probability, with the updater.  So, it's init-
+ * ialized instead to the gamma table.  The real value in use is inserted
+ * on the first picture update timeout call for a new picture.
+ */
+static gms_gammaref_t gms_graphics_current_gamma = GMS_GAMMA_TABLE;
+
+/*
+ * The number of colors used in the palette by the current picture.  This
+ * value is also at risk of a race with the updater, so it too has a mild
+ * lie for a default value.
+ */
+static int gms_graphics_color_count = GMS_PALETTE_SIZE;
+
+
+/*
+ * gms_graphics_open()
+ *
+ * If it's not open, open the graphics window.  Returns TRUE if graphics
+ * was successfully started, or already on.
+ */
+static int gms_graphics_open(void) {
+	if (!gms_graphics_window) {
+		gms_graphics_window = g_vm->glk_window_open(gms_main_window,
+		                      winmethod_Above
+		                      | winmethod_Proportional,
+		                      GMS_GRAPHICS_PROPORTION,
+		                      wintype_Graphics, 0);
+	}
+
+	return gms_graphics_window != NULL;
+}
+
+
+/*
+ * gms_graphics_close()
+ *
+ * If open, close the graphics window and set back to NULL.
+ */
+static void gms_graphics_close(void) {
+	if (gms_graphics_window) {
+		g_vm->glk_window_close(gms_graphics_window, NULL);
+		gms_graphics_window = NULL;
+	}
+}
+
+
+/*
+ * gms_graphics_start()
+ *
+ * If graphics enabled, start any background picture update processing.
+ */
+static void gms_graphics_start(void) {
+	if (gms_graphics_enabled) {
+		/* If not running, start the updating "thread". */
+		if (!gms_graphics_active) {
+			g_vm->glk_request_timer_events(GMS_GRAPHICS_TIMEOUT);
+			gms_graphics_active = TRUE;
+		}
+	}
+}
+
+
+/*
+ * gms_graphics_stop()
+ *
+ * Stop any background picture update processing.
+ */
+static void gms_graphics_stop(void) {
+	/* If running, stop the updating "thread". */
+	if (gms_graphics_active) {
+		g_vm->glk_request_timer_events(0);
+		gms_graphics_active = FALSE;
+	}
+}
+
+
+/*
+ * gms_graphics_are_displayed()
+ *
+ * Return TRUE if graphics are currently being displayed, FALSE otherwise.
+ */
+static int gms_graphics_are_displayed(void) {
+	return gms_graphics_window != NULL;
+}
+
+
+/*
+ * gms_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 gms_graphics_paint(void) {
+	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+		/* Set the repaint flag, and start graphics. */
+		gms_graphics_repaint = TRUE;
+		gms_graphics_start();
+	}
+}
+
+
+/*
+ * gms_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, on change of gamma color correction policy, and on change
+ * of animation policy.
+ */
+static void gms_graphics_restart(void) {
+	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+		/*
+		 * If the picture is animated, we'll need to be able to re-get the
+		 * first animation frame so that the picture can be treated as if
+		 * it is a new one.  So here, we'll try to re-extract the current
+		 * picture to do this.  Calling ms_extract() is safe because we
+		 * don't get here unless graphics are displayed, and graphics aren't
+		 * displayed until there's a valid picture loaded, and ms_showpic
+		 * only loads a picture after it's called ms_extract and set the
+		 * picture id into gms_graphics_picture.
+		 *
+		 * The bitmap and other picture stuff can be ignored because it's
+		 * the precise same stuff as we already have in picture details
+		 * variables.  If the ms_extract() fails, we'll carry on regardless,
+		 * which may, or may not, result in the ideal picture display.
+		 *
+		 * One or two non-animated pictures return NULL from ms_extract()
+		 * being re-called, so we'll restrict calls to animations only.
+		 * And just to be safe, we'll also call only if we're already
+		 * holding a bitmap (and we should be; how else could the graphics
+		 * animation flag be set?...).
+		 */
+		if (gms_graphics_animated && gms_graphics_bitmap) {
+			type8 animated;
+			type16 width, height, palette[GMS_PALETTE_SIZE];
+
+			/* Extract the bitmap into dummy variables. */
+			(void)ms_extract(gms_graphics_picture, &width, &height, palette, &animated);
+		}
+
+		/* Set the new picture flag, and start graphics. */
+		gms_graphics_new_picture = TRUE;
+		gms_graphics_start();
+	}
+}
+
+
+/*
+ * gms_graphics_count_colors()
+ *
+ * Analyze an image, and return the usage count of each palette color, and
+ * an overall count of how many colors out of the palette are used.  NULL
+ * arguments indicate no interest in the return value.
+ */
+static void gms_graphics_count_colors(type8 bitmap[], type16 width, type16 height,
+                                      int *color_count, long color_usage[]) {
+	int x, y, count;
+	long usage[GMS_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++;
+		}
+	}
+
+	if (color_count)
+		*color_count = count;
+
+	if (color_usage)
+		memcpy(color_usage, usage, sizeof(usage));
+}
+
+
+/*
+ * gms_graphics_game_to_rgb_color()
+ * gms_graphics_split_color()
+ * gms_graphics_combine_color()
+ * gms_graphics_color_luminance()
+ *
+ * General graphics helper functions, to convert between Magnetic Scrolls
+ * and RGB color representations, and between RGB and Glk glui32 color
+ * representations, and to calculate color luminance.
+ */
+static void
+gms_graphics_game_to_rgb_color(type16 color, gms_gammaref_t gamma,
+                               gms_rgbref_t rgb_color) {
+	assert(gamma && rgb_color);
+
+	/*
+	 * Convert Magnetic Scrolls color, through gamma, into RGB.  This splits
+	 * the color into components based on the 3-bits used in the game palette,
+	 * and gamma-corrects and rescales each to the range 0-255, using the given
+	 * correction.
+	 */
+	rgb_color->red   = gamma->table[(color & 0x700) >> 8];
+	rgb_color->green = gamma->table[(color & 0x070) >> 4];
+	rgb_color->blue  = gamma->table[(color & 0x007)];
+}
+
+static void gms_graphics_split_color(glui32 color, gms_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 gms_graphics_combine_color(gms_rgbref_t rgb_color) {
+	glui32 color;
+	assert(rgb_color);
+
+	color = (rgb_color->red << 16) | (rgb_color->green << 8) | rgb_color->blue;
+	return color;
+}
+
+static int gms_graphics_color_luminance(gms_rgbref_t rgb_color) {
+	static int is_initialized = FALSE;
+	static int weighting = 0;
+
+	long luminance;
+
+	/* On the first call, calculate the overall weighting. */
+	if (!is_initialized) {
+		weighting = GMS_LUMINANCE_WEIGHTS.red + GMS_LUMINANCE_WEIGHTS.green
+		            + GMS_LUMINANCE_WEIGHTS.blue;
+
+		is_initialized = TRUE;
+	}
+
+	/* Calculate the luminance and scale back by 1000 to 0-255 before return. */
+	luminance = ((long) rgb_color->red   * (long) GMS_LUMINANCE_WEIGHTS.red
+	             + (long) rgb_color->green * (long) GMS_LUMINANCE_WEIGHTS.green
+	             + (long) rgb_color->blue  * (long) GMS_LUMINANCE_WEIGHTS.blue);
+
+	assert(weighting > 0);
+	return (int)(luminance / weighting);
+}
+
+
+/*
+ * gms_graphics_compare_luminance()
+ * gms_graphics_constrast_variance()
+ *
+ * Calculate the contrast variance of the given palette and color usage, at
+ * the given gamma correction level.  Helper functions for automatic gamma
+ * correction.
+ */
+static int gms_graphics_compare_luminance(const void *void_first,
+        const void *void_second) {
+	long first = *(const long *)void_first;
+	long second = *(const long *)void_second;
+
+	return first > second ? 1 : second > first ? -1 : 0;
+}
+
+static long gms_graphics_contrast_variance(type16 palette[],
+        long color_usage[], gms_gammaref_t gamma) {
+	int index, count, has_black, mean;
+	long sum;
+	int contrast[GMS_PALETTE_SIZE];
+	int luminance[GMS_PALETTE_SIZE + 1];  /* Luminance for each color,
+                                           plus one extra for black */
+
+	/* Calculate the luminance energy of each palette color at this gamma. */
+	has_black = FALSE;
+	for (index = 0, count = 0; index < GMS_PALETTE_SIZE; index++) {
+		if (color_usage[index] > 0) {
+			gms_rgb_t rgb_color;
+
+			/*
+			 * Convert the 16-bit base picture color to RGB using the gamma
+			 * currently under consideration.  Calculate luminance for this
+			 * color and store in the next available luminance array entry.
+			 */
+			gms_graphics_game_to_rgb_color(palette[index], gamma, &rgb_color);
+			luminance[count++] = gms_graphics_color_luminance(&rgb_color);
+
+			/* Note if black is present in the palette. */
+			has_black |= luminance[count - 1] == 0;
+		}
+	}
+
+	/*
+	 * For best results, we want to anchor contrast calculations to black, so
+	 * if black is not represented in the palette, add it as an extra luminance.
+	 */
+	if (!has_black)
+		luminance[count++] = 0;
+
+	/* Sort luminance values so that the darkest color is at index 0. */
+	qsort(luminance, count,
+	      sizeof(*luminance), gms_graphics_compare_luminance);
+
+	/*
+	 * Calculate the difference in luminance between adjacent luminances in
+	 * the sorted array, as contrast, and at the same time sum contrasts to
+	 * calculate the mean.
+	 */
+	sum = 0;
+	for (index = 0; index < count - 1; index++) {
+		contrast[index] = luminance[index + 1] - luminance[index];
+		sum += contrast[index];
+	}
+	mean = sum / (count - 1);
+
+	/* Calculate and return the variance in contrasts. */
+	sum = 0;
+	for (index = 0; index < count - 1; index++)
+		sum += (contrast[index] - mean) * (contrast[index] - mean);
+
+	return sum / (count - 1);
+}
+
+
+/*
+ * gms_graphics_equal_contrast_gamma()
+ *
+ * Try to find a gamma correction for the given palette and color usage that
+ * gives relatively equal contrast among the displayed colors.
+ *
+ * To do this, we search the gamma tables, computing color luminance for each
+ * color in the palette given this gamma.  From luminances, we then compute
+ * the contrasts between the colors, and settle on the gamma correction that
+ * gives the most even and well-distributed picture contrast.  We ignore
+ * colors not used in the palette.
+ *
+ * Note that we don't consider how often a palette color is used, only whether
+ * it's represented, or not.  Some weighting might improve things, but the
+ * simple method seems to work adequately.  In practice, as there are only 16
+ * colors in a palette, most pictures use most colors in a relatively well
+ * distributed manner.  This algorithm probably wouldn't work well on real
+ * photographs, though.
+ */
+static gms_gammaref_t gms_graphics_equal_contrast_gamma(type16 palette[], long color_usage[]) {
+	gms_gammaref_t gamma, result;
+	long lowest_variance;
+	assert(palette && color_usage);
+
+	result = NULL;
+	lowest_variance = INT32_MAX;
+
+	/* Search the gamma table for the entry with the lowest contrast variance. */
+	for (gamma = GMS_GAMMA_TABLE; gamma->level; gamma++) {
+		long variance;
+
+		/* Find the color contrast variance of the palette at this gamma. */
+		variance = gms_graphics_contrast_variance(palette, color_usage, gamma);
+
+		/*
+		 * Compare the variance to the lowest so far, and if it is lower, note
+		 * the gamma entry that produced it as being the current best found.
+		 */
+		if (variance < lowest_variance) {
+			result = gamma;
+			lowest_variance = variance;
+		}
+	}
+
+	assert(result);
+	return result;
+}
+
+
+/*
+ * gms_graphics_select_gamma()
+ *
+ * Select a suitable gamma for the picture, based on the current gamma mode.
+ *
+ * The function returns either the linear gamma, a gamma value half way
+ * between linear and the gamma that gives the most even contrast, or just
+ * the gamma that gives the most even contrast.
+ *
+ * In the normal case, a value half way to the extreme case of making color
+ * contrast equal for all colors is, subjectively, a reasonable value to use.
+ * The problem cases are the darkest pictures, and selecting this value
+ * brightens them while at the same time not making them look overbright or
+ * too "sunny".
+ */
+static gms_gammaref_t gms_graphics_select_gamma(type8 bitmap[],
+        type16 width, type16 height, type16 palette[]) {
+	static int is_initialized = FALSE;
+	static gms_gammaref_t linear_gamma = NULL;
+
+	long color_usage[GMS_PALETTE_SIZE];
+	int color_count;
+	gms_gammaref_t contrast_gamma;
+
+	/* On first call, find and cache the uncorrected gamma table entry. */
+	if (!is_initialized) {
+		gms_gammaref_t gamma;
+
+		for (gamma = GMS_GAMMA_TABLE; gamma->level; gamma++) {
+			if (!gamma->is_corrected) {
+				linear_gamma = gamma;
+				break;
+			}
+		}
+
+		is_initialized = TRUE;
+	}
+	assert(linear_gamma);
+
+	/*
+	 * Check to see if automated correction is turned off; if it is, return
+	 * the linear gamma.
+	 */
+	if (gms_gamma_mode == GAMMA_OFF)
+		return linear_gamma;
+
+	/*
+	 * Get the color usage and count of total colors represented.  For a
+	 * degenerate picture with one color or less, return the linear gamma.
+	 */
+	gms_graphics_count_colors(bitmap, width, height, &color_count, color_usage);
+	if (color_count <= 1)
+		return linear_gamma;
+
+	/*
+	 * Now calculate a gamma setting to give the most equal contrast across the
+	 * picture colors.  We'll return either half this gamma, or all of it.
+	 */
+	contrast_gamma = gms_graphics_equal_contrast_gamma(palette, color_usage);
+
+	/*
+	 * For normal automated correction, return a gamma value half way between
+	 * the linear gamma and the equal contrast gamma.
+	 */
+	if (gms_gamma_mode == GAMMA_NORMAL)
+		return linear_gamma + (contrast_gamma - linear_gamma) / 2;
+
+	/* Correction must be high; return the equal contrast gamma. */
+	assert(gms_gamma_mode == GAMMA_HIGH);
+	return contrast_gamma;
+}
+
+
+/*
+ * gms_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 gms_graphics_clear_and_border(winid_t glk_window,
+        int x_offset, int y_offset, int pixel_size, type16 width, type16 height) {
+	glui32 background, fade_color, shading_color;
+	gms_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(gms_main_window,
+	                             style_Normal, stylehint_BackColor, &background)) {
+		/*
+		 * Unable to get the main window background, so assume, and default
+		 * graphics to white.
+		 */
+		background = GMS_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);
+
+	/*
+	 * 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 * GMS_GRAPHICS_SHADE_STEPS
+	        || height < 2 * GMS_GRAPHICS_SHADE_STEPS) {
+		/* Paint a rectangle bigger than the picture by border pixels. */
+		g_vm->glk_window_fill_rect(glk_window,
+		                           GMS_GRAPHICS_BORDER_COLOR,
+		                           x_offset - GMS_GRAPHICS_BORDER,
+		                           y_offset - GMS_GRAPHICS_BORDER,
+		                           width * pixel_size + GMS_GRAPHICS_BORDER * 2,
+		                           height * pixel_size + GMS_GRAPHICS_BORDER * 2);
+		return;
+	}
+
+	/*
+	 * 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,
+	                           GMS_GRAPHICS_BORDER_COLOR,
+	                           x_offset - GMS_GRAPHICS_BORDER,
+	                           y_offset - GMS_GRAPHICS_BORDER,
+	                           width * pixel_size + GMS_GRAPHICS_BORDER * 2
+	                           + GMS_GRAPHICS_SHADING,
+	                           height * pixel_size + GMS_GRAPHICS_BORDER * 2
+	                           + GMS_GRAPHICS_SHADING);
+
+	/*
+	 * Split the main window background color and the border color into
+	 * components.
+	 */
+	gms_graphics_split_color(background, &rgb_background);
+	gms_graphics_split_color(GMS_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)
+	               / GMS_GRAPHICS_SHADE_STEPS;
+	rgb_fade.green = (rgb_background.green - rgb_border.green)
+	                 / GMS_GRAPHICS_SHADE_STEPS;
+	rgb_fade.blue = (rgb_background.blue - rgb_border.blue)
+	                / GMS_GRAPHICS_SHADE_STEPS;
+
+	/* Combine RGB fade into a single incremental Glk color. */
+	fade_color = gms_graphics_combine_color(&rgb_fade);
+
+	/* Fade in edge, from background to border, shading in stages. */
+	shading_color = background;
+	for (index = 0; index < GMS_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
+		                           + GMS_GRAPHICS_BORDER,
+		                           y_offset + index - GMS_GRAPHICS_BORDER,
+		                           GMS_GRAPHICS_SHADING, 1);
+		g_vm->glk_window_fill_rect(glk_window, shading_color,
+		                           x_offset + index - GMS_GRAPHICS_BORDER,
+		                           y_offset + height * pixel_size
+		                           + GMS_GRAPHICS_BORDER,
+		                           1, GMS_GRAPHICS_SHADING);
+
+		/* Update the shading color for the fade next iteration. */
+		shading_color -= fade_color;
+	}
+}
+
+
+/*
+ * gms_graphics_convert_palette()
+ *
+ * Convert a Magnetic Scrolls color palette to a Glk one, using the given
+ * gamma corrections.
+ */
+static void gms_graphics_convert_palette(type16 ms_palette[], gms_gammaref_t gamma,
+        glui32 glk_palette[]) {
+	int index;
+	assert(ms_palette && gamma && glk_palette);
+
+	for (index = 0; index < GMS_PALETTE_SIZE; index++) {
+		gms_rgb_t rgb_color;
+
+		/*
+		 * Convert the 16-bit base picture color through gamma to a 32-bit
+		 * RGB color, and combine into a Glk color and store in the Glk palette.
+		 */
+		gms_graphics_game_to_rgb_color(ms_palette[index], gamma, &rgb_color);
+		glk_palette[index] = gms_graphics_combine_color(&rgb_color);
+	}
+}
+
+
+/*
+ * gms_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 gms_graphics_position_picture(winid_t glk_window,
+        int pixel_size, type16 width, type16 height,
+        int *x_offset, int *y_offset) {
+	glui32 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;
+}
+
+
+/*
+ * gms_graphics_apply_animation_frame()
+ *
+ * Apply a single animation frame to the given off-screen image buffer, using
+ * the frame bitmap, width, height and mask, the off-screen buffer, and the
+ * width and height of the main picture.
+ *
+ * Note that 'mask' may be NULL, implying that no frame pixel is transparent.
+ */
+static void gms_graphics_apply_animation_frame(type8 bitmap[],
+        type16 frame_width, type16 frame_height,
+        type8 mask[], int frame_x, int frame_y,
+        type8 off_screen[], type16 width,
+        type16 height) {
+	int mask_width, x, y;
+	type8 mask_hibit;
+	long frame_row, buffer_row, mask_row;
+	assert(bitmap && off_screen);
+
+	/*
+	 * It turns out that the mask isn't quite as described in defs.h, and thanks
+	 * to Torbjorn Andersson and his Gtk port of Magnetic for illuminating this.
+	 * The mask is made up of lines of 16-bit words, so the mask width is always
+	 * even.  Here we'll calculate the real width of a mask, and also set a high
+	 * bit for later on.
+	 */
+	mask_width = (((frame_width - 1) / CHAR_BIT) + 2) & (~1);
+	mask_hibit = 1 << (CHAR_BIT - 1);
+
+	/*
+	 * Initialize row index components; these are optimizations to avoid the
+	 * need for multiplications in the frame iteration loop.
+	 */
+	frame_row = 0;
+	buffer_row = frame_y * width;
+	mask_row = 0;
+
+	/*
+	 * Iterate over each frame row, clipping where y lies outside the main
+	 * picture area.
+	 */
+	for (y = 0; y < frame_height; y++) {
+		/* Clip if y is outside the main picture area. */
+		if (y + frame_y < 0 || y + frame_y >= height) {
+			/* Update optimization variables as if not clipped. */
+			frame_row += frame_width;
+			buffer_row += width;
+			mask_row += mask_width;
+			continue;
+		}
+
+		/* Iterate over each frame column, clipping again. */
+		for (x = 0; x < frame_width; x++) {
+			long frame_index, buffer_index;
+
+			/* Clip if x is outside the main picture area. */
+			if (x + frame_x < 0 || x + frame_x >= width)
+				continue;
+
+			/*
+			 * If there's a mask, check the bit associated with this x,y, and
+			 * ignore any transparent pixels.
+			 */
+			if (mask) {
+				type8 mask_byte;
+
+				/* Isolate the mask byte, and test the transparency bit. */
+				mask_byte = mask[mask_row + (x / CHAR_BIT)];
+				if ((mask_byte & (mask_hibit >> (x % CHAR_BIT))) != 0)
+					continue;
+			}
+
+			/*
+			 * Calculate indexes for this pixel into the frame, and into the
+			 * main off-screen buffer, and transfer the frame pixel into the
+			 * off-screen buffer.
+			 */
+			frame_index = frame_row + x;
+			buffer_index = buffer_row + x + frame_x;
+			off_screen[buffer_index] = bitmap[frame_index];
+		}
+
+		/* Update row index components on change of y. */
+		frame_row += frame_width;
+		buffer_row += width;
+		mask_row += mask_width;
+	}
+}
+
+
+/*
+ * gms_graphics_animate()
+ *
+ * This function finds and applies the next set of animation frames to the
+ * given off-screen image buffer.  It's handed the width and height of the
+ * main picture, and the off-screen buffer.
+ *
+ * It returns FALSE if at the end of animations, TRUE if more animations
+ * remain.
+ */
+static int gms_graphics_animate(type8 off_screen[], type16 width, type16 height) {
+	struct ms_position *positions;
+	type16 count;
+	type8 status;
+	int frame;
+	assert(off_screen);
+
+	/* Search for more animation frames, and return zero if none. */
+	status = ms_animate(&positions, &count);
+	if (status == 0)
+		return FALSE;
+
+	/* Apply each animation frame to the off-screen buffer. */
+	for (frame = 0; frame < count; frame++) {
+		type8 *bitmap, *mask;
+		type16 frame_width, frame_height;
+
+		/*
+		 * Get the bitmap and other details for this frame.  If we can't get
+		 * this animation frame, skip it and see if any others are available.
+		 */
+		bitmap = ms_get_anim_frame(positions[frame].number,
+		                           &frame_width, &frame_height, &mask);
+		if (bitmap) {
+			gms_graphics_apply_animation_frame(bitmap,
+			                                   frame_width, frame_height, mask,
+			                                   positions[frame].x,
+			                                   positions[frame].y,
+			                                   off_screen, width, height);
+		}
+	}
+
+	/* Return TRUE since more animation frames remain. */
+	return TRUE;
+}
+
+#ifndef GARGLK
+/*
+ * gms_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 gms_graphics_is_vertex(type8 off_screen[], type16 width, type16 height,
+                                  int x, int y) {
+	type8 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 < GMS_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()
+ * gms_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, as it could be
+ * in some animation frames) 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.
+ */
+typedef struct {
+	long complexity;  /* Count of vertices for this color. */
+	long usage;       /* Color usage count. */
+	int color;        /* Color index into palette. */
+} gms_layering_t;
+
+static int gms_graphics_compare_layering_inverted(const void *void_first,
+        const void *void_second) {
+	gms_layering_t *first = (gms_layering_t *) void_first;
+	gms_layering_t *second = (gms_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 gms_graphics_assign_layers(type8 off_screen[], type8 on_screen[],
+                                       type16 width, type16 height,
+                                       int layers[], long layer_usage[]) {
+	int index, x, y;
+	long index_row;
+	gms_layering_t layering[GMS_PALETTE_SIZE];
+	assert(off_screen && on_screen && layers && layer_usage);
+
+	/* Clear initial complexity and usage counts, and set initial colors. */
+	for (index = 0; index < GMS_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 index 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 (gms_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, GMS_PALETTE_SIZE,
+	      sizeof(*layering), gms_graphics_compare_layering_inverted);
+
+	/*
+	 * Assign a layer to each palette color, and also return the layer usage
+	 * for each layer.
+	 */
+	for (index = 0; index < GMS_PALETTE_SIZE; index++) {
+		layers[layering[index].color] = index;
+		layer_usage[index] = layering[index].usage;
+	}
+}
+
+/*
+ * gms_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 gms_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
+                                      type8 off_screen[], type8 on_screen[],
+                                      int x, int y, int x_offset, int y_offset,
+                                      int pixel_size, type16 width, type16 height) {
+	type8 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 < GMS_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;
+	}
+}
+#endif
+
+static void gms_graphics_paint_everything(winid_t glk_window,
+        glui32 palette[],
+        type8 off_screen[],
+        int x_offset, int y_offset,
+        type16 width, type16 height) {
+	type8       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 * GMS_GRAPHICS_PIXEL + x_offset,
+			                           y * GMS_GRAPHICS_PIXEL + y_offset,
+			                           GMS_GRAPHICS_PIXEL, GMS_GRAPHICS_PIXEL);
+		}
+	}
+}
+
+/*
+ * gms_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 gms_graphics_timeout() {
+	static glui32 palette[GMS_PALETTE_SIZE];   /* Precomputed Glk palette */
+#ifndef GARGLK
+	static int layers[GMS_PALETTE_SIZE];       /* Assigned image layers */
+	static long layer_usage[GMS_PALETTE_SIZE]; /* Image layer occupancies */
+#endif
+
+	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 */
+#ifndef GARGLK
+	static int saved_layer;                    /* Saved current layer */
+	static int saved_x, saved_y;               /* Saved x,y coord */
+	static int total_regions;                  /* Debug statistic */
+#endif
+
+	type8 *on_screen;                          /* On-screen image buffer */
+	type8 *off_screen;                         /* Off-screen image buffer */
+	long picture_size;                         /* Picture size in pixels */
+//  int layer;                                 /* Image layer iterator */
+//  int x, y;                                  /* Image iterators */
+//  int regions;                               /* Count of regions painted */
+
+	/* Ignore the call if the current graphics state is inactive. */
+	if (!gms_graphics_active)
+		return;
+	assert(gms_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 (gms_graphics_repaint) {
+		deferred_repaint = TRUE;
+		gms_graphics_repaint = FALSE;
+		ignore_counter = GMS_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 animation timing and
+	 * 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 = gms_graphics_width * gms_graphics_height;
+	off_screen = gms_graphics_off_screen;
+	on_screen = gms_graphics_on_screen;
+
+	/*
+	 * If we received a new picture, set up the local static variables for that
+	 * picture -- decide on gamma correction, convert the color palette, and
+	 * initialize the off_screen buffer to be the base picture.
+	 */
+	if (gms_graphics_new_picture) {
+		/* Initialize the off_screen buffer to be a copy of the base picture. */
+		free(off_screen);
+		off_screen = (type8 *)gms_malloc(picture_size * sizeof(*off_screen));
+		memcpy(off_screen, gms_graphics_bitmap,
+		       picture_size * sizeof(*off_screen));
+
+		/* Note the buffer for freeing on cleanup. */
+		gms_graphics_off_screen = off_screen;
+
+		/*
+		 * If the picture is animated, apply the first animation frames now.
+		 * This is important, since they form an intrinsic part of the first
+		 * displayed image (in type2 animation cases, perhaps _all_ of the
+		 * first displayed image).
+		 */
+		if (gms_graphics_animated) {
+			gms_graphics_animate(off_screen,
+			                     gms_graphics_width, gms_graphics_height);
+		}
+
+		/*
+		 * Select a suitable gamma for the picture, taking care to use the
+		 * off-screen buffer.
+		 */
+		gms_graphics_current_gamma =
+		    gms_graphics_select_gamma(off_screen,
+		                              gms_graphics_width,
+		                              gms_graphics_height,
+		                              gms_graphics_palette);
+
+		/*
+		 * Pre-convert all the picture palette colors into their corresponding
+		 * Glk colors.
+		 */
+		gms_graphics_convert_palette(gms_graphics_palette,
+		                             gms_graphics_current_gamma, palette);
+
+		/* Save the color count for possible queries later. */
+		gms_graphics_count_colors(off_screen,
+		                          gms_graphics_width, gms_graphics_height,
+		                          &gms_graphics_color_count, NULL);
+	}
+
+	/*
+	 * 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 (gms_graphics_new_picture || deferred_repaint) {
+		/*
+		 * Calculate the x and y offset to center the picture in the graphics
+		 * window.
+		 */
+		gms_graphics_position_picture(gms_graphics_window,
+		                              GMS_GRAPHICS_PIXEL,
+		                              gms_graphics_width, gms_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 = (type8 *)gms_malloc(picture_size * sizeof(*on_screen));
+		memset(on_screen, GMS_GRAPHICS_UNUSED_PIXEL,
+		       picture_size * sizeof(*on_screen));
+
+		/* Note the buffer for freeing on cleanup. */
+		gms_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
+		gms_graphics_assign_layers(off_screen, on_screen,
+		                           gms_graphics_width, gms_graphics_height,
+		                           layers, layer_usage);
+
+		saved_layer = 0;
+		saved_x = 0;
+		saved_y = 0;
+		total_regions = 0;
+#endif
+
+		/* Clear the graphics window. */
+		gms_graphics_clear_and_border(gms_graphics_window,
+		                              x_offset, y_offset,
+		                              GMS_GRAPHICS_PIXEL,
+		                              gms_graphics_width, gms_graphics_height);
+
+		/* Start a fresh picture rendering pass. */
+		yield_counter = 0;
+
+		/* Clear the new picture and deferred repaint flags. */
+		gms_graphics_new_picture = FALSE;
+		deferred_repaint = FALSE;
+	}
+
+#ifndef GARGLK
+	/*
+	 * 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 < GMS_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 * gms_graphics_width;
+		for (y = saved_y; y < gms_graphics_height; y++) {
+			for (x = saved_x; x < gms_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.
+					 */
+					gms_graphics_paint_region(gms_graphics_window,
+					                          palette, layers,
+					                          off_screen, on_screen,
+					                          x, y, x_offset, y_offset,
+					                          GMS_GRAPHICS_PIXEL,
+					                          gms_graphics_width,
+					                          gms_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 >= GMS_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 += gms_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 < GMS_REPAINT_LIMIT);
+	total_regions += regions;
+
+#else
+	gms_graphics_paint_everything
+	(gms_graphics_window,
+	 palette, off_screen,
+	 x_offset, y_offset,
+	 gms_graphics_width,
+	 gms_graphics_height);
+#endif
+
+	/*
+	 * If animated, and if animations are enabled, handle further animation
+	 * frames, if any.
+	 */
+	if (gms_animation_enabled && gms_graphics_animated) {
+		int more_animation;
+
+		/*
+		 * Reset the off-screen buffer to a copy of the base picture.  This is
+		 * the correct state for applying animation frames.
+		 */
+		memcpy(off_screen, gms_graphics_bitmap,
+		       picture_size * sizeof(*off_screen));
+
+		/*
+		 * Apply any further animations.  If none, then stop the graphics
+		 * "thread" and return.  There's no more to be done until something
+		 * restarts us.
+		 */
+		more_animation = gms_graphics_animate(off_screen,
+		                                      gms_graphics_width,
+		                                      gms_graphics_height);
+		if (!more_animation) {
+			/*
+			 * There's one extra wrinkle here.  The base picture we've just put
+			 * into the off-screen buffer isn't really complete (and for type2
+			 * animations, might be pure garbage), so if we happen to get a
+			 * repaint after an animation has ended, the off-screen data we'll
+			 * be painting could well look wrong.
+			 *
+			 * So... here we want to set the off-screen buffer to contain the
+			 * final animation frame.  Fortunately, we still have it in the
+			 * on-screen buffer.
+			 */
+			memcpy(off_screen, on_screen, picture_size * sizeof(*off_screen));
+			gms_graphics_stop();
+			return;
+		}
+
+		/*
+		 * Re-assign layers based on animation changes to the off-screen
+		 * buffer.
+		 */
+#ifndef GARGLK
+		gms_graphics_assign_layers(off_screen, on_screen,
+		                           gms_graphics_width, gms_graphics_height,
+		                           layers, layer_usage);
+#endif
+
+		/*
+		 * Set up an animation wait, adjusted here by the number of times we
+		 * had to yield while rendering, as we're now that late with animations,
+		 * and capped at zero, as we can't do anything to compensate for being
+		 * too late.  In practice, we're running too close to the edge to have
+		 * much of an effect here, but nevertheless...
+		 */
+		ignore_counter = GMS_GRAPHICS_ANIMATION_WAIT - 1;
+		if (yield_counter > ignore_counter)
+			ignore_counter = 0;
+		else
+			ignore_counter -= yield_counter;
+
+		/* Start a fresh picture rendering pass. */
+		yield_counter = 0;
+#ifndef GARGLK
+		saved_layer = 0;
+		saved_x = 0;
+		saved_y = 0;
+		total_regions = 0;
+#endif
+	} else {
+		/*
+		 * Not an animated picture, so just stop graphics, as again, there's
+		 * no more to be done until something restarts us.
+		 */
+		gms_graphics_stop();
+	}
+}
+
+
+/*
+ * ms_showpic()
+ *
+ * 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.
+ *
+ * The graphics window is opened if required, or closed if mode is zero.
+ *
+ * The function checks for changes of actual picture by calculating the
+ * CRC for picture data; this helps to prevent unnecessary repaints in
+ * cases where the interpreter passes us the same picture as we're already
+ * displaying.  There is a less than 1 in 4,294,967,296 chance that a new
+ * picture will be missed.  We'll live with that.
+ *
+ * Why use CRCs, rather than simply storing the values of picture passed in
+ * a static variable?  Because some games, typically Magnetic Windows, use
+ * the picture argument as a form of string pointer, and can pass in the
+ * same value for several, perhaps all, game pictures.  If we just checked
+ * for a change in the picture argument, we'd never see one.  So we must
+ * instead look for changes in the real picture data.
+ */
+void ms_showpic(type32 picture, type8 mode) {
+	static glui32 current_crc = 0;  /* CRC of the current picture */
+
+	type8 *bitmap, animated;
+	type16 width, height, palette[GMS_PALETTE_SIZE];
+	long picture_bytes;
+	glui32 crc;
+
+	/* See if the mode indicates no graphics. */
+	if (mode == 0) {
+		/* Note that the interpreter turned graphics off. */
+		gms_graphics_interpreter = FALSE;
+
+		/*
+		 * If we are currently displaying the graphics window, stop any update
+		 * "thread" and turn off graphics.
+		 */
+		if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+			gms_graphics_stop();
+			gms_graphics_close();
+		}
+
+		/* Nothing more to do now graphics are off. */
+		return;
+	}
+
+	/* Note that the interpreter turned graphics on. */
+	gms_graphics_interpreter = TRUE;
+
+	/*
+	 * Obtain the image details for the requested picture.  The call returns
+	 * NULL if there's a problem with the picture.
+	 */
+	bitmap = ms_extract(picture, &width, &height, palette, &animated);
+	if (!bitmap)
+		return;
+
+	/* Note the last thing passed to ms_extract, in case of graphics restarts. */
+	gms_graphics_picture = picture;
+
+	/* Calculate the picture size, and the CRC for the bitmap data. */
+	picture_bytes = width * height * sizeof(*bitmap);
+	crc = gms_get_buffer_crc(bitmap, picture_bytes);
+
+	/*
+	 * If there is no change of picture, we might be able to largely ignore the
+	 * call.  Check for a change, and if we don't see one, and if graphics are
+	 * enabled and being displayed, we can safely ignore the call.
+	 */
+	if (width == gms_graphics_width
+	        && height == gms_graphics_height
+	        && crc == current_crc
+	        && gms_graphics_enabled && gms_graphics_are_displayed())
+		return;
+
+	/*
+	 * We know now that this is either a genuine change of picture, or graphics
+	 * were off and have been turned on.  So, record picture details, ensure
+	 * graphics is on, set the flags, and start the background graphics update.
+	 */
+
+	/*
+	 * Save the picture details for the update code.  Here we take a complete
+	 * local copy of the bitmap, since the interpreter core may reuse part of
+	 * its memory for animations.
+	 */
+	free(gms_graphics_bitmap);
+	gms_graphics_bitmap = (type8 *)gms_malloc(picture_bytes);
+	memcpy(gms_graphics_bitmap, bitmap, picture_bytes);
+	gms_graphics_width = width;
+	gms_graphics_height = height;
+	memcpy(gms_graphics_palette, palette, sizeof(palette));
+	gms_graphics_animated = animated;
+
+	/* Retain the new picture CRC. */
+	current_crc = crc;
+
+	/*
+	 * If graphics are enabled, 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 (gms_graphics_enabled) {
+		/*
+		 * Ensure graphics on, then set the new picture flag and start the
+		 * updating "thread".
+		 */
+		if (gms_graphics_open()) {
+			gms_graphics_new_picture = TRUE;
+			gms_graphics_start();
+		}
+	}
+}
+
+
+/*
+ * gms_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
+gms_graphics_picture_is_available(void) {
+	return gms_graphics_bitmap != NULL;
+}
+
+
+/*
+ * gms_graphics_get_picture_details()
+ *
+ * Return the width, height, and animation flag 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 gms_graphics_get_picture_details(int *width, int *height, int *is_animated) {
+	if (gms_graphics_picture_is_available()) {
+		if (width)
+			*width = gms_graphics_width;
+		if (height)
+			*height = gms_graphics_height;
+		if (is_animated)
+			*is_animated = gms_graphics_animated;
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+/*
+ * gms_graphics_get_rendering_details()
+ *
+ * Returns the current level of applied gamma correction, 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 gamma, 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
+ * gamma and color count values are returned.
+ */
+static int gms_graphics_get_rendering_details(const char **gamma, int *color_count,
+        int *is_active) {
+	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+		/*
+		 * Return the string representing the gamma correction.  If racing
+		 * with timeouts, we might return the gamma for the last picture.
+		 */
+		if (gamma) {
+			assert(gms_graphics_current_gamma);
+			*gamma = gms_graphics_current_gamma->level;
+		}
+
+		/*
+		 * Return the color count noted by timeouts on the first timeout
+		 * following a new picture.  Again, we might return the one for
+		 * the prior picture.
+		 */
+		if (color_count)
+			*color_count = gms_graphics_color_count;
+
+		/* Return graphics active flag. */
+		if (is_active)
+			*is_active = gms_graphics_active;
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+/*
+ * gms_graphics_interpreter_enabled()
+ *
+ * Return TRUE if it looks like interpreter graphics are turned on, FALSE
+ * otherwise.
+ */
+static int gms_graphics_interpreter_enabled(void) {
+	return gms_graphics_interpreter;
+}
+
+
+/*
+ * gms_graphics_cleanup()
+ *
+ * Free memory resources allocated by graphics functions.  Called on game
+ * end.
+ */
+static void gms_graphics_cleanup(void) {
+	free(gms_graphics_bitmap);
+	gms_graphics_bitmap = NULL;
+	free(gms_graphics_off_screen);
+	gms_graphics_off_screen = NULL;
+	free(gms_graphics_on_screen);
+	gms_graphics_on_screen = NULL;
+
+	gms_graphics_animated = FALSE;
+	gms_graphics_picture = 0;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port status line functions                                     */
+/*---------------------------------------------------------------------*/
+
+/*
+ * The interpreter feeds us status line characters one at a time, with Tab
+ * indicating right justify, and CR indicating the line is complete.  To get
+ * this to fit with the Glk event and redraw model, here we'll buffer each
+ * completed status line, so we have a stable string to output when needed.
+ * It's also handy to have this buffer for Glk libraries that don't support
+ * separate windows.
+ */
+enum { GMS_STATBUFFER_LENGTH = 1024 };
+static char gms_status_buffer[GMS_STATBUFFER_LENGTH];
+static int gms_status_length = 0;
+
+/* Default width used for non-windowing Glk status lines. */
+static const int GMS_DEFAULT_STATUS_WIDTH = 74;
+
+
+/*
+ * ms_statuschar()
+ *
+ * Receive one status character from the interpreter.  Characters are
+ * buffered internally, and on CR, the buffer is copied to the main static
+ * status buffer for use by the status line printing function.
+ */
+void ms_statuschar(type8 c) {
+	static char buffer[GMS_STATBUFFER_LENGTH];
+	static int length = 0;
+
+	/*
+	 * If the status character is newline, transfer locally buffered data to
+	 * the common buffer, empty the local buffer; otherwise, if space permits,
+	 * buffer the character.
+	 */
+	if (c == '\n') {
+		memcpy(gms_status_buffer, buffer, length);
+		gms_status_length = length;
+
+		length = 0;
+	} else {
+		if (length < (int)sizeof(buffer))
+			buffer[length++] = c;
+	}
+}
+
+
+/*
+ * gms_status_update()
+ *
+ * Update the information in the status window with the current contents of
+ * the completed status line buffer, or a default string if no completed
+ * status line.
+ */
+static void gms_status_update(void) {
+	glui32 width, height;
+	int index;
+	assert(gms_status_window);
+
+	g_vm->glk_window_get_size(gms_status_window, &width, &height);
+	if (height > 0) {
+		g_vm->glk_window_clear(gms_status_window);
+		g_vm->glk_window_move_cursor(gms_status_window, 0, 0);
+		g_vm->glk_set_window(gms_status_window);
+
+		g_vm->glk_set_style(style_User1);
+		for (index = 0; index < (int)width; index++)
+			g_vm->glk_put_char(' ');
+		g_vm->glk_window_move_cursor(gms_status_window, 1, 0);
+
+		if (gms_status_length > 0) {
+			/*
+			 * Output each character from the status line buffer.  If the
+			 * character is Tab, position the cursor to eleven characters shy
+			 * of the status window right.
+			 */
+			for (index = 0; index < gms_status_length; index++) {
+				if (gms_status_buffer[index] == '\t')
+					g_vm->glk_window_move_cursor(gms_status_window, width - 11, 0);
+				else
+					g_vm->glk_put_char(gms_status_buffer[index]);
+			}
+		} else {
+			const char *game_name;
+
+			/*
+			 * We have no status line to display, so print the game's name, or
+			 * a standard message if unable to identify the game.  Having no
+			 * status line is common with Magnetic Windows games, which don't,
+			 * in general, seem to use one.
+			 */
+			game_name = gms_gameid_get_game_name();
+			g_vm->glk_put_string(game_name ? game_name : "ScummVM Magnetic version 2.3");
+		}
+
+		g_vm->glk_set_window(gms_main_window);
+	}
+}
+
+
+/*
+ * gms_status_print()
+ *
+ * Print the current contents of the completed status line buffer out in the
+ * main window, if it has changed since the last call.  This is for non-
+ * windowing Glk libraries.
+ */
+static void gms_status_print(void) {
+	static char buffer[GMS_STATBUFFER_LENGTH];
+	static int length = 0;
+
+	int index, column;
+
+	/*
+	 * Do nothing if there is no status line to print, or if the status
+	 * line hasn't changed since last printed.
+	 */
+	if (gms_status_length == 0
+	        || (gms_status_length == length
+	            && strncmp(buffer, gms_status_buffer, length)) == 0)
+		return;
+
+	/* Set fixed width font to try to preserve status line formatting. */
+	g_vm->glk_set_style(style_Preformatted);
+
+	/* Bracket, and output the status line buffer. */
+	g_vm->glk_put_string("[ ");
+	column = 1;
+	for (index = 0; index < gms_status_length; index++) {
+		/*
+		 * If the character is Tab, position the cursor to eleven characters
+		 * shy of the right edge.  In the absence of the real window dimensions,
+		 * we'll select 74 characters, which gives us a 78 character status
+		 * line; pretty standard.
+		 */
+		if (gms_status_buffer[index] == '\t') {
+			while (column <= GMS_DEFAULT_STATUS_WIDTH - 11) {
+				g_vm->glk_put_char(' ');
+				column++;
+			}
+		} else {
+			g_vm->glk_put_char(gms_status_buffer[index]);
+			column++;
+		}
+	}
+
+	while (column <= GMS_DEFAULT_STATUS_WIDTH) {
+		g_vm->glk_put_char(' ');
+		column++;
+	}
+	g_vm->glk_put_string(" ]\n");
+
+	/* Save the details of the printed status buffer. */
+	memcpy(buffer, gms_status_buffer, gms_status_length);
+	length = gms_status_length;
+}
+
+
+/*
+ * gms_status_notify()
+ *
+ * Front end function for updating status.  Either updates the status window
+ * or prints the status line to the main window.
+ */
+static void gms_status_notify(void) {
+	if (gms_status_window)
+		gms_status_update();
+	else
+		gms_status_print();
+}
+
+
+/*
+ * gms_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 gms_status_redraw(void) {
+	if (gms_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(gms_status_window);
+		g_vm->glk_window_set_arrangement(parent,
+		                                 winmethod_Above | winmethod_Fixed, 1, NULL);
+
+		gms_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 gms_help_requested = FALSE,
+           gms_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 *gms_output_buffer = NULL;
+static int gms_output_allocation = 0,
+           gms_output_length = 0;
+
+/*
+ * Flag to indicate if the last buffer flushed looked like it ended in a
+ * ">" prompt.
+ */
+static int gms_output_prompt = FALSE;
+
+
+/*
+ * gms_output_register_help_request()
+ * gms_output_silence_help_hints()
+ * gms_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 gms_output_register_help_request(void) {
+	gms_help_requested = TRUE;
+}
+
+static void gms_output_silence_help_hints(void) {
+	gms_help_hints_silenced = TRUE;
+}
+
+static void gms_output_provide_help_hint(void) {
+	if (gms_help_requested && !gms_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");
+
+		gms_help_requested = FALSE;
+		g_vm->glk_set_style(style_Normal);
+	}
+}
+
+
+/*
+ * gms_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 gms_game_prompted(void) {
+	int result;
+
+	result = gms_output_prompt;
+	gms_output_prompt = FALSE;
+
+	return result;
+}
+
+
+/*
+ * gms_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 gms_detect_game_prompt(void) {
+	int index;
+
+	gms_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 = gms_output_length - 1;
+	        index >= 0 && gms_output_buffer[index] != '\n'; index--) {
+		if (gms_output_buffer[index] != ' ') {
+			gms_output_prompt = TRUE;
+			break;
+		}
+	}
+}
+
+
+/*
+ * gms_output_delete()
+ *
+ * Delete all buffered output text.  Free all malloc'ed buffer memory, and
+ * return the buffer variables to their initial values.
+ */
+static void gms_output_delete(void) {
+	free(gms_output_buffer);
+	gms_output_buffer = NULL;
+	gms_output_allocation = gms_output_length = 0;
+}
+
+
+/*
+ * gms_output_flush()
+ *
+ * Flush any buffered output text to the Glk main window, and clear the
+ * buffer.
+ */
+static void gms_output_flush(void) {
+	assert(g_vm->glk_stream_get_current());
+
+	if (gms_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).
+		 */
+		gms_detect_game_prompt();
+		g_vm->glk_set_style(style_Normal);
+
+		if (gms_output_prompt) {
+			int index;
+
+			for (index = gms_output_length - 1;
+			        index >= 0 && gms_output_buffer[index] != '\n';)
+				index--;
+
+			g_vm->glk_put_buffer(gms_output_buffer, index + 1);
+			gms_output_provide_help_hint();
+			g_vm->glk_put_buffer(gms_output_buffer + index + 1,
+			                     gms_output_length - index - 1);
+		} else {
+			g_vm->glk_put_buffer(gms_output_buffer, gms_output_length);
+			gms_output_provide_help_hint();
+		}
+
+		gms_output_delete();
+	}
+}
+
+
+/*
+ * ms_putchar()
+ *
+ * Buffer a character for eventual printing to the main window.
+ */
+void ms_putchar(type8 c) {
+	int bytes;
+	assert(gms_output_length <= gms_output_allocation);
+
+	/*
+	 * See if the character is a backspace.  Magnetic Scrolls games can send
+	 * backspace characters to the display.  We'll need to handle such
+	 * characters specially, by taking the last character out of the buffer.
+	 */
+	if (c == '\b') {
+		if (gms_output_length > 0)
+			gms_output_length--;
+
+		return;
+	}
+
+	/* Grow the output buffer if necessary, then add the character. */
+	for (bytes = gms_output_allocation; bytes < gms_output_length + 1;)
+		bytes = bytes == 0 ? 1 : bytes << 1;
+
+	if (bytes > gms_output_allocation) {
+		gms_output_buffer = (char *)gms_realloc(gms_output_buffer, bytes);
+		gms_output_allocation = bytes;
+	}
+
+	gms_output_buffer[gms_output_length++] = c;
+}
+
+
+/*
+ * gms_styled_string()
+ * gms_styled_char()
+ * gms_standout_string()
+ * gms_standout_char()
+ * gms_normal_string()
+ * gms_normal_char()
+ * gms_header_string()
+ * gms_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 gms_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 gms_styled_char(glui32 style, char c) {
+	char buffer[2];
+
+	buffer[0] = c;
+	buffer[1] = '\0';
+	gms_styled_string(style, buffer);
+}
+
+static void gms_standout_string(const char *message) {
+	gms_styled_string(style_Emphasized, message);
+}
+
+#if 0
+static void gms_standout_char(char c) {
+	gms_styled_char(style_Emphasized, c);
+}
+#endif
+
+static void gms_normal_string(const char *message) {
+	gms_styled_string(style_Normal, message);
+}
+
+static void gms_normal_char(char c) {
+	gms_styled_char(style_Normal, c);
+}
+
+static void gms_header_string(const char *message) {
+	gms_styled_string(style_Header, message);
+}
+
+static void gms_banner_string(const char *message) {
+	gms_styled_string(style_Subheader, message);
+}
+
+/*
+ * ms_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 gms_output_flush() when reading line input.
+ *
+ * Taking ms_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 ms_flush(void) {
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port hint functions                                            */
+/*---------------------------------------------------------------------*/
+
+/* Hint type definitions. */
+enum {
+	GMS_HINT_TYPE_FOLDER = 1,
+	GMS_HINT_TYPE_TEXT = 2
+};
+
+/* Success and fail return codes from hint functions. */
+static const type8 GMS_HINT_SUCCESS = 1,
+                   GMS_HINT_ERROR = 0;
+
+/* Default window sizes for non-windowing Glk libraries. */
+static const glui32 GMS_HINT_DEFAULT_WIDTH = 72,
+                    GMS_HINT_DEFAULT_HEIGHT = 25;
+
+/*
+ * Special hint nodes indicating the root hint node, and a value to signal
+ * quit from hints subsystem.
+ */
+static const type16 GMS_HINT_ROOT_NODE = 0,
+                    GMS_HINTS_DONE = UINT16_MAX;
+
+/* Generic hint topic for the root hints node. */
+static const char *const GMS_GENERIC_TOPIC = "Hints Menu";
+
+/*
+ * Note of the interpreter's hints array.  Note that keeping its address
+ * like this assumes that it's either static or heap in the interpreter.
+ */
+static struct ms_hint *gms_hints = NULL;
+
+/* Details of the current hint node on display from the hints array. */
+static type16 gms_current_hint_node = 0;
+
+/*
+ * Array of cursors for each hint.  The cursor indicates the current hint
+ * position in a folder, and the last hint shown in text hints.  Space
+ * is allocated as needed for a given set of hints, and needs to be freed
+ * on interpreter exit.
+ */
+static int *gms_hint_cursor = NULL;
+
+
+/*
+ * gms_get_hint_max_node()
+ *
+ * Return the maximum hint node referred to by the tree under the given node.
+ * The result is the largest index found, or node, if greater.  Because the
+ * interpreter doesn't supply it, we need to uncover it the hard way.  The
+ * function is recursive, and since it is a tree search, assumes that hints
+ * is a tree, not a graph.
+ */
+static type16 gms_get_hint_max_node(const struct ms_hint hints[], type16 node) {
+	const struct ms_hint *hint;
+	int index;
+	type16 max_node;
+	assert(hints);
+
+	hint = hints + node;
+	max_node = node;
+
+	switch (hint->nodetype) {
+	case GMS_HINT_TYPE_TEXT:
+		break;
+
+	case GMS_HINT_TYPE_FOLDER:
+		/*
+		 * Recursively find the maximum node reference for each link, and keep
+		 * the largest value found.
+		 */
+		for (index = 0; index < hint->elcount; index++) {
+			type16 link_max;
+
+			link_max = gms_get_hint_max_node(hints, hint->links[index]);
+			if (link_max > max_node)
+				max_node = link_max;
+		}
+		break;
+
+	default:
+		gms_fatal("GLK: Invalid hints node type encountered");
+		g_vm->glk_exit();
+	}
+
+	/*
+	 * Return the largest node reference found, capped to avoid overlapping the
+	 * special end-hints value.
+	 */
+	return max_node < GMS_HINTS_DONE ? max_node : GMS_HINTS_DONE - 1;
+}
+
+
+/*
+ * gms_get_hint_content()
+ *
+ * Return the content string for a given hint number within a given node.
+ * This counts over 'number' ASCII NULs in the node's content, returning
+ * the address of the string located this way.
+ */
+static const char *gms_get_hint_content(const struct ms_hint hints[], type16 node, int number) {
+	const struct ms_hint *hint;
+	int offset, index;
+	assert(hints);
+
+	hint = hints + node;
+
+	/* Run through content until 'number' strings found. */
+	offset = 0;
+	for (index = 0; index < number; index++)
+		offset += strlen(hint->content + offset) + 1;
+
+	/* Return the start of the number'th string encountered. */
+	return hint->content + offset;
+}
+
+
+/*
+ * gms_get_hint_topic()
+ *
+ * Return the topic string for a given hint node.  This is found by searching
+ * the parent node for a link to the node handed in.  For the root node, the
+ * string is defaulted, since the root node has no parent.
+ */
+static const char *gms_get_hint_topic(const struct ms_hint hints[], type16 node) {
+	assert(hints);
+
+	if (node == GMS_HINT_ROOT_NODE) {
+		/* If the node is the root node, return a generic string. */
+		return GMS_GENERIC_TOPIC;
+	} else {
+		type16 parent;
+		int index;
+		const char *topic;
+
+		/*
+		 * Search the parent for a link to node, and use that as the hint topic;
+		 * NULL if none found.
+		 */
+		parent = hints[node].parent;
+
+		topic = NULL;
+		for (index = 0; index < hints[parent].elcount; index++) {
+			if (hints[parent].links[index] == node) {
+				topic = gms_get_hint_content(hints, parent, index);
+				break;
+			}
+		}
+
+		return topic ? topic : GMS_GENERIC_TOPIC;
+	}
+}
+
+
+/*
+ * gms_hint_open()
+ *
+ * If not already open, open the hints windows.  Returns TRUE if the windows
+ * opened, or were already open.
+ *
+ * The function creates two hints windows -- a text grid on top, for menus,
+ * and a text buffer below for hints.
+ */
+static int gms_hint_open(void) {
+	if (!gms_hint_menu_window) {
+		assert(!gms_hint_text_window);
+
+		/*
+		 * Open the hint menu window.  The initial size is two lines, but we'll
+		 * change this later to suit the hint.
+		 */
+		gms_hint_menu_window = g_vm->glk_window_open(gms_main_window,
+		                       winmethod_Above | winmethod_Fixed,
+		                       2, wintype_TextGrid, 0);
+		if (!gms_hint_menu_window)
+			return FALSE;
+
+		/*
+		 * Now open the hints text window.  This is set to be 100% of the size
+		 * of the main window, so should cover what remains of it completely.
+		 */
+		gms_hint_text_window = g_vm->glk_window_open(gms_main_window,
+		                       winmethod_Above
+		                       | winmethod_Proportional,
+		                       100, wintype_TextBuffer, 0);
+		if (!gms_hint_text_window) {
+			g_vm->glk_window_close(gms_hint_menu_window, NULL);
+			gms_hint_menu_window = NULL;
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+
+/*
+ * gms_hint_close()
+ *
+ * If open, close the hints windows.
+ */
+static void gms_hint_close(void) {
+	if (gms_hint_menu_window) {
+		assert(gms_hint_text_window);
+
+		g_vm->glk_window_close(gms_hint_menu_window, NULL);
+		gms_hint_menu_window = NULL;
+		g_vm->glk_window_close(gms_hint_text_window, NULL);
+		gms_hint_text_window = NULL;
+	}
+}
+
+
+/*
+ * gms_hint_windows_available()
+ *
+ * Return TRUE if hints windows are available.  If they're not, the hints
+ * system will need to use alternative output methods.
+ */
+static int gms_hint_windows_available(void) {
+	return (gms_hint_menu_window && gms_hint_text_window);
+}
+
+
+/*
+ * gms_hint_menu_print()
+ * gms_hint_menu_header()
+ * gms_hint_menu_justify()
+ * gms_hint_text_print()
+ * gms_hint_menutext_done()
+ * gms_hint_menutext_start()
+ *
+ * Output functions for writing hints.  These functions will write to hints
+ * windows where available, and to the main window where not.  When writing
+ * to hints windows, they also take care not to line wrap in the menu window.
+ * Limited formatting is available.
+ */
+static void gms_hint_menu_print(int line, int column, const char *string,
+                                glui32 width, glui32 height) {
+	assert(string);
+
+	/* Ignore the call if the text position is outside the window. */
+	if (!(line > (int)height || column > (int)width)) {
+		if (gms_hint_windows_available()) {
+			int posn, index;
+
+			g_vm->glk_window_move_cursor(gms_hint_menu_window, column, line);
+			g_vm->glk_set_window(gms_hint_menu_window);
+
+			/* Write until the end of the string, or the end of the window. */
+			for (posn = column, index = 0;
+			        posn < (int)width && index < (int)strlen(string); posn++, index++) {
+				g_vm->glk_put_char(string[index]);
+			}
+
+			g_vm->glk_set_window(gms_main_window);
+		} else {
+			static int current_line = 0;    /* Retained line number */
+			static int current_column = 0;  /* Retained col number */
+
+			int index;
+
+			/*
+			 * Check the line number against the last one output.  If it is less,
+			 * assume the start of a new block.  In this case, perform a hokey
+			 * type of screen clear.
+			 */
+			if (line < current_line) {
+				for (index = 0; index < (int)height; index++)
+					gms_normal_char('\n');
+
+				current_line = 0;
+				current_column = 0;
+			}
+
+			/* Print blank lines until the target line is reached. */
+			for (; current_line < line; current_line++) {
+				gms_normal_char('\n');
+				current_column = 0;
+			}
+
+			/* Now print spaces until the target column is reached. */
+			for (; current_column < column; current_column++)
+				gms_normal_char(' ');
+
+			/*
+			 * Write characters until the end of the string, or the end of the
+			 * (self-imposed not-really-there) window.
+			 */
+			for (index = 0;
+			        current_column < (int)width && index < (int)strlen(string);
+			        current_column++, index++) {
+				gms_normal_char(string[index]);
+			}
+		}
+	}
+}
+
+static void gms_hint_menu_header(int line, const char *string,
+                                 glui32 width, glui32 height) {
+	int posn, length;
+	assert(string);
+
+	/* Output the text in the approximate line center. */
+	length = strlen(string);
+	posn = length < (int)width ? (width - length) / 2 : 0;
+	gms_hint_menu_print(line, posn, string, width, height);
+}
+
+static void gms_hint_menu_justify(int line,
+                                  const char *left_string, const char *right_string,
+                                  glui32 width, glui32 height) {
+	int posn, length;
+	assert(left_string && right_string);
+
+	/* Write left text normally to window left. */
+	gms_hint_menu_print(line, 0, left_string, width, height);
+
+	/* Output the right text flush with the right of the window. */
+	length = strlen(right_string);
+	posn = length < (int)width ? width - length : 0;
+	gms_hint_menu_print(line, posn, right_string, width, height);
+}
+
+static void gms_hint_text_print(const char *string) {
+	assert(string);
+
+	if (gms_hint_windows_available()) {
+		g_vm->glk_set_window(gms_hint_text_window);
+		g_vm->glk_put_string(string);
+		g_vm->glk_set_window(gms_main_window);
+	} else
+		gms_normal_string(string);
+}
+
+static void gms_hint_menutext_start(void) {
+	/*
+	 * Twiddle for non-windowing libraries; 'clear' the main window by writing
+	 * a null string at line 1, then a null string at line 0.  This works
+	 * because we know the current output line in gms_hint_menu_print() is zero,
+	 * since we set it that way with gms_hint_menutext_done(), or if this is
+	 * the first call, then that's its initial value.
+	 */
+	if (!gms_hint_windows_available()) {
+		gms_hint_menu_print(1, 0, "",
+		                    GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
+		gms_hint_menu_print(0, 0, "",
+		                    GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
+	}
+}
+
+static void gms_hint_menutext_done(void) {
+	/*
+	 * Twiddle for non-windowing libraries; 'clear' the main window by writing
+	 * an empty string to line zero.  For windowing Glk libraries, this function
+	 * does nothing.
+	 */
+	if (!gms_hint_windows_available()) {
+		gms_hint_menu_print(0, 0, "",
+		                    GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
+	}
+}
+
+
+/*
+ * gms_hint_menutext_char_event()
+ *
+ * Request and return a character event from the hints windows.  In practice,
+ * this means either of the hints windows if available, or the main window
+ * if not.
+ */
+static void gms_hint_menutext_char_event(event_t *event) {
+	assert(event);
+
+	if (gms_hint_windows_available()) {
+		g_vm->glk_request_char_event(gms_hint_menu_window);
+		g_vm->glk_request_char_event(gms_hint_text_window);
+
+		gms_event_wait(evtype_CharInput, event);
+		assert(event->window == gms_hint_menu_window
+		       || event->window == gms_hint_text_window);
+
+		g_vm->glk_cancel_char_event(gms_hint_menu_window);
+		g_vm->glk_cancel_char_event(gms_hint_text_window);
+	} else {
+		g_vm->glk_request_char_event(gms_main_window);
+		gms_event_wait(evtype_CharInput, event);
+	}
+}
+
+
+/*
+ * gms_hint_arrange_windows()
+ *
+ * Arrange the hints windows so that the hint menu window has the requested
+ * number of lines.  Returns the actual hint menu window width and height,
+ * or defaults if no hints windows are available.
+ */
+static void gms_hint_arrange_windows(int requested_lines, glui32 *width, glui32 *height) {
+	if (gms_hint_windows_available()) {
+		winid_t parent;
+
+		/* Resize the hint menu window to fit the current hint. */
+		parent = g_vm->glk_window_get_parent(gms_hint_menu_window);
+		g_vm->glk_window_set_arrangement(parent,
+		                                 winmethod_Above | winmethod_Fixed,
+		                                 requested_lines, NULL);
+
+		/* Measure, and return the size of the hint menu window. */
+		g_vm->glk_window_get_size(gms_hint_menu_window, width, height);
+
+		/* Clear both the hint menu and the hint text window. */
+		g_vm->glk_window_clear(gms_hint_menu_window);
+		g_vm->glk_window_clear(gms_hint_text_window);
+	} else {
+		/*
+		 * No hints windows, so default width and height.  The hints output
+		 * functions will cope with this.
+		 */
+		if (width)
+			*width = GMS_HINT_DEFAULT_WIDTH;
+		if (height)
+			*height = GMS_HINT_DEFAULT_HEIGHT;
+	}
+}
+
+
+/*
+ * gms_hint_display_folder()
+ *
+ * Update the hints windows for the given folder hint node.
+ */
+static void gms_hint_display_folder(const struct ms_hint hints[],
+                                    const int cursor[], type16 node) {
+	glui32 width, height;
+	int line, index;
+	assert(hints && cursor);
+
+	/*
+	 * Arrange windows to suit the hint folder.  For a folder menu window we
+	 * use one line for each element, three for the controls, and two spacers,
+	 * making a total of five additional lines.  Width and height receive the
+	 * actual menu window dimensions.
+	 */
+	gms_hint_arrange_windows(hints[node].elcount + 5, &width, &height);
+
+	/* Paint in the menu header. */
+	line = 0;
+	gms_hint_menu_header(line++,
+	                     gms_get_hint_topic(hints, node),
+	                     width, height);
+	gms_hint_menu_justify(line++,
+	                      " N = next subject  ", "  P = previous ",
+	                      width, height);
+	gms_hint_menu_justify(line++,
+	                      " RETURN = read subject  ",
+	                      node == GMS_HINT_ROOT_NODE
+	                      ? "  Q = resume game " : "  Q = previous menu ",
+	                      width, height);
+
+	/*
+	 * Output a blank line, then the menu for the node's folder hint.  The folder
+	 * text for the selected hint is preceded by a '>' pointer.
+	 */
+	line++;
+	for (index = 0; index < hints[node].elcount; index++) {
+		gms_hint_menu_print(line, 3,
+		                    index == cursor[node] ? ">" : " ",
+		                    width, height);
+		gms_hint_menu_print(line++, 5,
+		                    gms_get_hint_content(hints, node, index),
+		                    width, height);
+	}
+
+	/*
+	 * Terminate with a blank line; using a single space here improves cursor
+	 * positioning for optimized output libraries (for example, without it,
+	 * curses output will leave the cursor at the end of the previous line).
+	 */
+	gms_hint_menu_print(line, 0, " ", width, height);
+}
+
+
+/*
+ * gms_hint_display_text()
+ *
+ * Update the hints windows for the given text hint node.
+ */
+static void gms_hint_display_text(const struct ms_hint hints[],
+                                  const int cursor[], type16 node) {
+	glui32 width, height;
+	int line, index;
+	assert(hints && cursor);
+
+	/*
+	 * Arrange windows to suit the hint text.  For a hint menu, we use a simple
+	 * two-line set of controls; everything else is in the hints text window.
+	 * Width and height receive the actual menu window dimensions.
+	 */
+	gms_hint_arrange_windows(2, &width, &height);
+
+	/* Paint in a short menu header. */
+	line = 0;
+	gms_hint_menu_header(line++,
+	                     gms_get_hint_topic(hints, node),
+	                     width, height);
+	gms_hint_menu_justify(line++,
+	                      " RETURN = read hint  ", "  Q = previous menu ",
+	                      width, height);
+
+	/*
+	 * Output hints to the hints text window.  Hints not yet exposed are
+	 * indicated by the cursor for the hint, and are displayed as a dash.
+	 */
+	gms_hint_text_print("\n");
+	for (index = 0; index < hints[node].elcount; index++) {
+		char buffer[16];
+
+		sprintf(buffer, "%3d.  ", index + 1);
+		gms_hint_text_print(buffer);
+
+		gms_hint_text_print(index < cursor[node]
+		                    ? gms_get_hint_content(hints, node, index) : "-");
+		gms_hint_text_print("\n");
+	}
+}
+
+
+/*
+ * gms_hint_display()
+ *
+ * Display the given hint using the appropriate display function.
+ */
+static void gms_hint_display(const struct ms_hint hints[],
+                             const int cursor[], type16 node) {
+	assert(hints && cursor);
+
+	switch (hints[node].nodetype) {
+	case GMS_HINT_TYPE_TEXT:
+		gms_hint_display_text(hints, cursor, node);
+		break;
+
+	case GMS_HINT_TYPE_FOLDER:
+		gms_hint_display_folder(hints, cursor, node);
+		break;
+
+	default:
+		gms_fatal("GLK: Invalid hints node type encountered");
+		g_vm->glk_exit();
+	}
+}
+
+
+/*
+ * gms_hint_handle_folder()
+ *
+ * Handle a Glk keycode for the given folder hint.  Return the next node to
+ * handle, or the special end-hints on Quit at the root node.
+ */
+static type16 gms_hint_handle_folder(const struct ms_hint hints[],
+                                     int cursor[], type16 node, glui32 keycode) {
+	unsigned char response;
+	type16 next_node;
+	assert(hints && cursor);
+
+	/* Convert key code into a single response character. */
+	switch (keycode) {
+	case keycode_Down:
+		response = 'N';
+		break;
+	case keycode_Up:
+		response = 'P';
+		break;
+	case keycode_Right:
+	case keycode_Return:
+		response = '\n';
+		break;
+	case keycode_Left:
+	case keycode_Escape:
+		response = 'Q';
+		break;
+	default:
+		response = keycode <= BYTE_MAX ? g_vm->glk_char_to_upper(keycode) : 0;
+		break;
+	}
+
+	/*
+	 * Now handle the response character.  We'll default the next node to be
+	 * this node, but a response case can change it.
+	 */
+	next_node = node;
+	switch (response) {
+	case 'N':
+		/* Advance the hint cursor, wrapping at the folder end. */
+		if (cursor[node] < hints[node].elcount - 1)
+			cursor[node]++;
+		else
+			cursor[node] = 0;
+		break;
+
+	case 'P':
+		/* Regress the hint cursor, wrapping at the folder start. */
+		if (cursor[node] > 0)
+			cursor[node]--;
+		else
+			cursor[node] = hints[node].elcount - 1;
+		break;
+
+	case '\n':
+		/* The next node is the hint node at the cursor position. */
+		next_node = hints[node].links[cursor[node]];
+		break;
+
+	case 'Q':
+		/* If root, we're done; if not, next node is node's parent. */
+		next_node = node == GMS_HINT_ROOT_NODE
+		            ? GMS_HINTS_DONE : hints[node].parent;
+		break;
+
+	default:
+		break;
+	}
+
+	return next_node;
+}
+
+
+/*
+ * gms_hint_handle_text()
+ *
+ * Handle a Glk keycode for the given text hint.  Return the next node to
+ * handle.
+ */
+static type16 gms_hint_handle_text(const struct ms_hint hints[],
+                                   int cursor[], type16 node, glui32 keycode) {
+	unsigned char response;
+	type16 next_node;
+	assert(hints && cursor);
+
+	/* Convert key code into a single response character. */
+	switch (keycode) {
+	case keycode_Right:
+	case keycode_Return:
+		response = '\n';
+		break;
+	case keycode_Left:
+	case keycode_Escape:
+		response = 'Q';
+		break;
+	default:
+		response = keycode <= BYTE_MAX ? g_vm->glk_char_to_upper(keycode) : 0;
+		break;
+	}
+
+	/*
+	 * Now handle the response character.  We'll default the next node to be
+	 * this node, but a response case can change it.
+	 */
+	next_node = node;
+	switch (response) {
+	case '\n':
+		/* If not at end of the hint, advance the hint cursor. */
+		if (cursor[node] < hints[node].elcount)
+			cursor[node]++;
+		break;
+
+	case 'Q':
+		/* Done with this hint node, so next node is its parent. */
+		next_node = hints[node].parent;
+		break;
+
+	default:
+		break;
+	}
+
+	return next_node;
+}
+
+
+/*
+ * gms_hint_handle()
+ *
+ * Handle a Glk keycode for the given hint using the appropriate handler
+ * function.  Return the next node to handle.
+ */
+static type16 gms_hint_handle(const struct ms_hint hints[],
+                              int cursor[], type16 node, glui32 keycode) {
+	type16 next_node;
+	assert(hints && cursor);
+
+	next_node = GMS_HINT_ROOT_NODE;
+	switch (hints[node].nodetype) {
+	case GMS_HINT_TYPE_TEXT:
+		next_node = gms_hint_handle_text(hints, cursor, node, keycode);
+		break;
+
+	case GMS_HINT_TYPE_FOLDER:
+		next_node = gms_hint_handle_folder(hints, cursor, node, keycode);
+		break;
+
+	default:
+		gms_fatal("GLK: Invalid hints node type encountered");
+		g_vm->glk_exit();
+	}
+
+	return next_node;
+}
+
+
+/*
+ * ms_showhints()
+ *
+ * Start game hints.  These are modal, though there's no overriding Glk
+ * reason why.  It's just that this matches the way they're implemented by
+ * most Inform games.  This may not be the best way of doing help, but at
+ * least it's likely to be familiar, and anything more ambitious may be
+ * beyond the current Glk capabilities.
+ *
+ * This function uses CRCs to detect any change of hints data.  Normally,
+ * we'd expect none, at least within a given game run, but we can probably
+ * handle it okay if it happens.
+ */
+type8 ms_showhints(struct ms_hint *hints) {
+	static int is_initialized = FALSE;
+	static glui32 current_crc = 0;
+
+	type16 hint_count;
+	glui32 crc;
+	assert(hints);
+
+	/*
+	 * Find the number of hints in the array.  To do this, we'll visit every
+	 * node in a tree search, starting at the root, to locate the maximum node
+	 * number found, then add one to that.  It's a pity that the interpreter
+	 * doesn't hand us this information directly.
+	 */
+	hint_count = gms_get_hint_max_node(hints, GMS_HINT_ROOT_NODE) + 1;
+
+	/*
+	 * Calculate a CRC for the hints array data.  If the CRC has changed, or
+	 * this is the first call, assign a new cursor array.
+	 */
+	crc = gms_get_buffer_crc(hints, hint_count * sizeof(*hints));
+	if (crc != current_crc || !is_initialized) {
+		int bytes;
+
+		/* Allocate new cursors, and set all to zero initial state. */
+		free(gms_hint_cursor);
+		bytes = hint_count * sizeof(*gms_hint_cursor);
+		gms_hint_cursor = (int *)gms_malloc(bytes);
+		memset(gms_hint_cursor, 0, bytes);
+
+		/*
+		 * Retain the hints CRC, for later comparisons, and set is_initialized
+		 * flag.
+		 */
+		current_crc = crc;
+		is_initialized = TRUE;
+	}
+
+	/*
+	 * Save the hints array passed in.  This is done here since even if the data
+	 * remains the same (found by the CRC check above), the pointer to it might
+	 * have changed.
+	 */
+	gms_hints = hints;
+
+	/*
+	 * Try to create the hints windows.  If they can't be created, perhaps
+	 * because the Glk library doesn't support it, the output functions will
+	 * work around this.
+	 */
+	gms_hint_open();
+	gms_hint_menutext_start();
+
+	/*
+	 * Begin hints display at the root node, and navigate until the user exits
+	 * hints.
+	 */
+	gms_current_hint_node = GMS_HINT_ROOT_NODE;
+	while (gms_current_hint_node != GMS_HINTS_DONE) {
+		event_t event;
+
+		assert(gms_current_hint_node < hint_count);
+		gms_hint_display(gms_hints, gms_hint_cursor, gms_current_hint_node);
+
+		/* Get and handle a character key event for hint navigation. */
+		gms_hint_menutext_char_event(&event);
+		assert(event.type == evtype_CharInput);
+		gms_current_hint_node = gms_hint_handle(gms_hints,
+		                                        gms_hint_cursor,
+		                                        gms_current_hint_node,
+		                                        event.val1);
+	}
+
+	/* Done with hint windows. */
+	gms_hint_menutext_done();
+	gms_hint_close();
+
+	return GMS_HINT_SUCCESS;
+}
+
+
+/*
+ * gms_hint_redraw()
+ *
+ * Update the hints windows for the current hint.  This function should be
+ * called from the event handler on resize events, to repaint the hints
+ * display.  It does nothing if no hints windows have been opened, since
+ * in this case, there's no resize action required -- either we're not in
+ * the hints subsystem, or hints are being displayed in the main game
+ * window, for whatever reason.
+ */
+static void gms_hint_redraw(void) {
+	if (gms_hint_windows_available()) {
+		assert(gms_hints && gms_hint_cursor);
+		gms_hint_display(gms_hints, gms_hint_cursor, gms_current_hint_node);
+	}
+}
+
+
+/*
+ * gms_hints_cleanup()
+ *
+ * Free memory resources allocated by hints functions.  Called on game
+ * end.
+ */
+static void gms_hints_cleanup(void) {
+	free(gms_hint_cursor);
+	gms_hint_cursor = NULL;
+
+	gms_hints = NULL;
+	gms_current_hint_node = 0;
+}
+
+
+void ms_playmusic(type8 *midi_data, type32 length, type16 tempo) {
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk command escape functions                                       */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gms_command_undo()
+ *
+ * Stub function for the undo command.  The real work is to return the
+ * undo code to the input functions.
+ */
+static void gms_command_undo(const char *argument) {
+	assert(argument);
+}
+
+
+/*
+ * gms_command_script()
+ *
+ * Turn game output scripting (logging) on and off.
+ */
+static void gms_command_script(const char *argument) {
+	assert(argument);
+
+	if (gms_strcasecmp(argument, "on") == 0) {
+		frefid_t fileref;
+
+		if (gms_transcript_stream) {
+			gms_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) {
+			gms_standout_string("Glk transcript failed.\n");
+			return;
+		}
+
+		gms_transcript_stream = g_vm->glk_stream_open_file(fileref,
+		                        filemode_WriteAppend, 0);
+		g_vm->glk_fileref_destroy(fileref);
+		if (!gms_transcript_stream) {
+			gms_standout_string("Glk transcript failed.\n");
+			return;
+		}
+
+		g_vm->glk_window_set_echo_stream(gms_main_window, gms_transcript_stream);
+
+		gms_normal_string("Glk transcript is now on.\n");
+	}
+
+	else if (gms_strcasecmp(argument, "off") == 0) {
+		if (!gms_transcript_stream) {
+			gms_normal_string("Glk transcript is already off.\n");
+			return;
+		}
+
+		g_vm->glk_stream_close(gms_transcript_stream, NULL);
+		gms_transcript_stream = NULL;
+
+		g_vm->glk_window_set_echo_stream(gms_main_window, NULL);
+
+		gms_normal_string("Glk transcript is now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gms_normal_string("Glk transcript is ");
+		gms_normal_string(gms_transcript_stream ? "on" : "off");
+		gms_normal_string(".\n");
+	}
+
+	else {
+		gms_normal_string("Glk transcript can be ");
+		gms_standout_string("on");
+		gms_normal_string(", or ");
+		gms_standout_string("off");
+		gms_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gms_command_inputlog()
+ *
+ * Turn game input logging on and off.
+ */
+static void gms_command_inputlog(const char *argument) {
+	assert(argument);
+
+	if (gms_strcasecmp(argument, "on") == 0) {
+		frefid_t fileref;
+
+		if (gms_inputlog_stream) {
+			gms_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) {
+			gms_standout_string("Glk input logging failed.\n");
+			return;
+		}
+
+		gms_inputlog_stream = g_vm->glk_stream_open_file(fileref,
+		                      filemode_WriteAppend, 0);
+		g_vm->glk_fileref_destroy(fileref);
+		if (!gms_inputlog_stream) {
+			gms_standout_string("Glk input logging failed.\n");
+			return;
+		}
+
+		gms_normal_string("Glk input logging is now on.\n");
+	}
+
+	else if (gms_strcasecmp(argument, "off") == 0) {
+		if (!gms_inputlog_stream) {
+			gms_normal_string("Glk input logging is already off.\n");
+			return;
+		}
+
+		g_vm->glk_stream_close(gms_inputlog_stream, NULL);
+		gms_inputlog_stream = NULL;
+
+		gms_normal_string("Glk input log is now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gms_normal_string("Glk input logging is ");
+		gms_normal_string(gms_inputlog_stream ? "on" : "off");
+		gms_normal_string(".\n");
+	}
+
+	else {
+		gms_normal_string("Glk input logging can be ");
+		gms_standout_string("on");
+		gms_normal_string(", or ");
+		gms_standout_string("off");
+		gms_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gms_command_readlog()
+ *
+ * Set the game input log, to read input from a file.
+ */
+static void gms_command_readlog(const char *argument) {
+	assert(argument);
+
+	if (gms_strcasecmp(argument, "on") == 0) {
+		frefid_t fileref;
+
+		if (gms_readlog_stream) {
+			gms_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) {
+			gms_standout_string("Glk read log failed.\n");
+			return;
+		}
+
+		if (!g_vm->glk_fileref_does_file_exist(fileref)) {
+			g_vm->glk_fileref_destroy(fileref);
+			gms_standout_string("Glk read log failed.\n");
+			return;
+		}
+
+		gms_readlog_stream = g_vm->glk_stream_open_file(fileref, filemode_Read, 0);
+		g_vm->glk_fileref_destroy(fileref);
+		if (!gms_readlog_stream) {
+			gms_standout_string("Glk read log failed.\n");
+			return;
+		}
+
+		gms_normal_string("Glk read log is now on.\n");
+	}
+
+	else if (gms_strcasecmp(argument, "off") == 0) {
+		if (!gms_readlog_stream) {
+			gms_normal_string("Glk read log is already off.\n");
+			return;
+		}
+
+		g_vm->glk_stream_close(gms_readlog_stream, NULL);
+		gms_readlog_stream = NULL;
+
+		gms_normal_string("Glk read log is now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gms_normal_string("Glk read log is ");
+		gms_normal_string(gms_readlog_stream ? "on" : "off");
+		gms_normal_string(".\n");
+	}
+
+	else {
+		gms_normal_string("Glk read log can be ");
+		gms_standout_string("on");
+		gms_normal_string(", or ");
+		gms_standout_string("off");
+		gms_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gms_command_abbreviations()
+ *
+ * Turn abbreviation expansions on and off.
+ */
+static void gms_command_abbreviations(const char *argument) {
+	assert(argument);
+
+	if (gms_strcasecmp(argument, "on") == 0) {
+		if (gms_abbreviations_enabled) {
+			gms_normal_string("Glk abbreviation expansions are already on.\n");
+			return;
+		}
+
+		gms_abbreviations_enabled = TRUE;
+		gms_normal_string("Glk abbreviation expansions are now on.\n");
+	}
+
+	else if (gms_strcasecmp(argument, "off") == 0) {
+		if (!gms_abbreviations_enabled) {
+			gms_normal_string("Glk abbreviation expansions are already off.\n");
+			return;
+		}
+
+		gms_abbreviations_enabled = FALSE;
+		gms_normal_string("Glk abbreviation expansions are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gms_normal_string("Glk abbreviation expansions are ");
+		gms_normal_string(gms_abbreviations_enabled ? "on" : "off");
+		gms_normal_string(".\n");
+	}
+
+	else {
+		gms_normal_string("Glk abbreviation expansions can be ");
+		gms_standout_string("on");
+		gms_normal_string(", or ");
+		gms_standout_string("off");
+		gms_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gms_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 gms_command_graphics(const char *argument) {
+	assert(argument);
+
+	if (!gms_graphics_possible) {
+		gms_normal_string("Glk graphics are not available.\n");
+		return;
+	}
+
+	if (gms_strcasecmp(argument, "on") == 0) {
+		if (gms_graphics_enabled) {
+			gms_normal_string("Glk graphics are already on.\n");
+			return;
+		}
+
+		gms_graphics_enabled = TRUE;
+
+		/* If a picture is loaded, call the restart function to repaint it. */
+		if (gms_graphics_picture_is_available()) {
+			if (!gms_graphics_open()) {
+				gms_normal_string("Glk graphics error.\n");
+				return;
+			}
+			gms_graphics_restart();
+		}
+
+		gms_normal_string("Glk graphics are now on.\n");
+	}
+
+	else if (gms_strcasecmp(argument, "off") == 0) {
+		if (!gms_graphics_enabled) {
+			gms_normal_string("Glk graphics are already off.\n");
+			return;
+		}
+
+		/*
+		 * Set graphics to disabled, and stop any graphics processing.  Close
+		 * the graphics window.
+		 */
+		gms_graphics_enabled = FALSE;
+		gms_graphics_stop();
+		gms_graphics_close();
+
+		gms_normal_string("Glk graphics are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gms_normal_string("Glk graphics are available,");
+		gms_normal_string(gms_graphics_enabled
+		                  ? " and enabled.\n" : " but disabled.\n");
+
+		if (gms_graphics_picture_is_available()) {
+			int width, height, is_animated;
+
+			if (gms_graphics_get_picture_details(&width, &height, &is_animated)) {
+				char buffer[16];
+
+				gms_normal_string("There is ");
+				gms_normal_string(is_animated ? "an animated" : "a");
+				gms_normal_string(" picture loaded, ");
+
+				sprintf(buffer, "%d", width);
+				gms_normal_string(buffer);
+				gms_normal_string(" by ");
+
+				sprintf(buffer, "%d", height);
+				gms_normal_string(buffer);
+
+				gms_normal_string(" pixels.\n");
+			}
+		}
+
+		if (!gms_graphics_interpreter_enabled())
+			gms_normal_string("Interpreter graphics are disabled.\n");
+
+		if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+			int color_count, is_active;
+			const char *gamma;
+
+			if (gms_graphics_get_rendering_details(&gamma, &color_count,
+			                                       &is_active)) {
+				char buffer[16];
+
+				gms_normal_string("Graphics are ");
+				gms_normal_string(is_active ? "active, " : "displayed, ");
+
+				sprintf(buffer, "%d", color_count);
+				gms_normal_string(buffer);
+				gms_normal_string(" colours");
+
+				if (gms_gamma_mode == GAMMA_OFF)
+					gms_normal_string(", without gamma correction");
+				else {
+					gms_normal_string(", with gamma ");
+					gms_normal_string(gamma);
+					gms_normal_string(" correction");
+				}
+				gms_normal_string(".\n");
+			} else
+				gms_normal_string("Graphics are being displayed.\n");
+		}
+
+		if (gms_graphics_enabled && !gms_graphics_are_displayed())
+			gms_normal_string("Graphics are not being displayed.\n");
+	}
+
+	else {
+		gms_normal_string("Glk graphics can be ");
+		gms_standout_string("on");
+		gms_normal_string(", or ");
+		gms_standout_string("off");
+		gms_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gms_command_gamma()
+ *
+ * Enable or disable picture gamma corrections.
+ */
+static void gms_command_gamma(const char *argument) {
+	assert(argument);
+
+	if (!gms_graphics_possible) {
+		gms_normal_string("Glk automatic gamma correction is not available.\n");
+		return;
+	}
+
+	if (gms_strcasecmp(argument, "high") == 0) {
+		if (gms_gamma_mode == GAMMA_HIGH) {
+			gms_normal_string("Glk automatic gamma correction mode is"
+			                  " already 'high'.\n");
+			return;
+		}
+
+		gms_gamma_mode = GAMMA_HIGH;
+		gms_graphics_restart();
+
+		gms_normal_string("Glk automatic gamma correction mode is"
+		                  " now 'high'.\n");
+	}
+
+	else if (gms_strcasecmp(argument, "normal") == 0
+	         || gms_strcasecmp(argument, "on") == 0) {
+		if (gms_gamma_mode == GAMMA_NORMAL) {
+			gms_normal_string("Glk automatic gamma correction mode is"
+			                  " already 'normal'.\n");
+			return;
+		}
+
+		gms_gamma_mode = GAMMA_NORMAL;
+		gms_graphics_restart();
+
+		gms_normal_string("Glk automatic gamma correction mode is"
+		                  " now 'normal'.\n");
+	}
+
+	else if (gms_strcasecmp(argument, "none") == 0
+	         || gms_strcasecmp(argument, "off") == 0) {
+		if (gms_gamma_mode == GAMMA_OFF) {
+			gms_normal_string("Glk automatic gamma correction mode is"
+			                  " already 'off'.\n");
+			return;
+		}
+
+		gms_gamma_mode = GAMMA_OFF;
+		gms_graphics_restart();
+
+		gms_normal_string("Glk automatic gamma correction mode is"
+		                  " now 'off'.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gms_normal_string("Glk automatic gamma correction mode is '");
+		switch (gms_gamma_mode) {
+		case GAMMA_OFF:
+			gms_normal_string("off");
+			break;
+		case GAMMA_NORMAL:
+			gms_normal_string("normal");
+			break;
+		case GAMMA_HIGH:
+			gms_normal_string("high");
+			break;
+		}
+		gms_normal_string("'.\n");
+	}
+
+	else {
+		gms_normal_string("Glk automatic gamma correction mode can be ");
+		gms_standout_string("high");
+		gms_normal_string(", ");
+		gms_standout_string("normal");
+		gms_normal_string(", or ");
+		gms_standout_string("off");
+		gms_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gms_command_animations()
+ *
+ * Enable or disable picture animations.
+ */
+static void gms_command_animations(const char *argument) {
+	assert(argument);
+
+	if (!gms_graphics_possible) {
+		gms_normal_string("Glk graphics animations are not available.\n");
+		return;
+	}
+
+	if (gms_strcasecmp(argument, "on") == 0) {
+		int is_animated;
+
+		if (gms_animation_enabled) {
+			gms_normal_string("Glk graphics animations are already on.\n");
+			return;
+		}
+
+		/*
+		 * Set animation to on, and restart graphics if the current picture
+		 * is animated; if it isn't, we can leave it displayed as is, since
+		 * changing animation mode doesn't affect this picture.
+		 */
+		gms_animation_enabled = TRUE;
+		if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
+			if (is_animated)
+				gms_graphics_restart();
+		}
+
+		gms_normal_string("Glk graphics animations are now on.\n");
+	}
+
+	else if (gms_strcasecmp(argument, "off") == 0) {
+		int is_animated;
+
+		if (!gms_animation_enabled) {
+			gms_normal_string("Glk graphics animations are already off.\n");
+			return;
+		}
+
+		gms_animation_enabled = FALSE;
+		if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
+			if (is_animated)
+				gms_graphics_restart();
+		}
+
+		gms_normal_string("Glk graphics animations are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gms_normal_string("Glk graphics animations are ");
+		gms_normal_string(gms_animation_enabled ? "on" : "off");
+		gms_normal_string(".\n");
+	}
+
+	else {
+		gms_normal_string("Glk graphics animations can be ");
+		gms_standout_string("on");
+		gms_normal_string(", or ");
+		gms_standout_string("off");
+		gms_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gms_command_prompts()
+ *
+ * Turn the extra "> " prompt output on and off.
+ */
+static void gms_command_prompts(const char *argument) {
+	assert(argument);
+
+	if (gms_strcasecmp(argument, "on") == 0) {
+		if (gms_prompt_enabled) {
+			gms_normal_string("Glk extra prompts are already on.\n");
+			return;
+		}
+
+		gms_prompt_enabled = TRUE;
+		gms_normal_string("Glk extra prompts are now on.\n");
+
+		/* Check for a game prompt to clear the flag. */
+		gms_game_prompted();
+	}
+
+	else if (gms_strcasecmp(argument, "off") == 0) {
+		if (!gms_prompt_enabled) {
+			gms_normal_string("Glk extra prompts are already off.\n");
+			return;
+		}
+
+		gms_prompt_enabled = FALSE;
+		gms_normal_string("Glk extra prompts are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gms_normal_string("Glk extra prompts are ");
+		gms_normal_string(gms_prompt_enabled ? "on" : "off");
+		gms_normal_string(".\n");
+	}
+
+	else {
+		gms_normal_string("Glk extra prompts can be ");
+		gms_standout_string("on");
+		gms_normal_string(", or ");
+		gms_standout_string("off");
+		gms_normal_string(".\n");
+	}
+}
+
+
+/*
+ * gms_command_print_version_number()
+ * gms_command_version()
+ *
+ * Print out the Glk library version number.
+ */
+static void gms_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);
+	gms_normal_string(buffer);
+}
+
+static void
+gms_command_version(const char *argument) {
+	glui32 version;
+	assert(argument);
+
+	gms_normal_string("This is version ");
+	gms_command_print_version_number(GMS_PORT_VERSION);
+	gms_normal_string(" of the Glk Magnetic port.\n");
+
+	version = g_vm->glk_gestalt(gestalt_Version, 0);
+	gms_normal_string("The Glk library version is ");
+	gms_command_print_version_number(version);
+	gms_normal_string(".\n");
+}
+
+
+/*
+ * gms_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 gms_command_commands(const char *argument) {
+	assert(argument);
+
+	if (gms_strcasecmp(argument, "on") == 0) {
+		gms_normal_string("Glk commands are already on.\n");
+	}
+
+	else if (gms_strcasecmp(argument, "off") == 0) {
+		gms_commands_enabled = FALSE;
+		gms_normal_string("Glk commands are now off.\n");
+	}
+
+	else if (strlen(argument) == 0) {
+		gms_normal_string("Glk commands are ");
+		gms_normal_string(gms_commands_enabled ? "on" : "off");
+		gms_normal_string(".\n");
+	}
+
+	else {
+		gms_normal_string("Glk commands can be ");
+		gms_standout_string("on");
+		gms_normal_string(", or ");
+		gms_standout_string("off");
+		gms_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. */
+	const int undo_return;                          /* "Undo" return value. */
+} gms_command_t;
+typedef gms_command_t *gms_commandref_t;
+
+static void gms_command_summary(const char *argument);
+static void gms_command_help(const char *argument);
+
+static gms_command_t GMS_COMMAND_TABLE[] = {
+	{"summary",        gms_command_summary,        FALSE, FALSE},
+	{"undo",           gms_command_undo,           FALSE, TRUE},
+	{"script",         gms_command_script,         TRUE,  FALSE},
+	{"inputlog",       gms_command_inputlog,       TRUE,  FALSE},
+	{"readlog",        gms_command_readlog,        TRUE,  FALSE},
+	{"abbreviations",  gms_command_abbreviations,  TRUE,  FALSE},
+	{"graphics",       gms_command_graphics,       TRUE,  FALSE},
+	{"gamma",          gms_command_gamma,          TRUE,  FALSE},
+	{"animations",     gms_command_animations,     TRUE,  FALSE},
+	{"prompts",        gms_command_prompts,        TRUE,  FALSE},
+	{"version",        gms_command_version,        FALSE, FALSE},
+	{"commands",       gms_command_commands,       TRUE,  FALSE},
+	{"help",           gms_command_help,           TRUE,  FALSE},
+	{NULL, NULL, FALSE, FALSE}
+};
+
+
+/*
+ * gms_command_summary()
+ *
+ * Report all current Glk settings.
+ */
+static void gms_command_summary(const char *argument) {
+	gms_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 = GMS_COMMAND_TABLE; entry->command; entry++) {
+		if (entry->handler == gms_command_summary
+		        || entry->handler == gms_command_undo
+		        || entry->handler == gms_command_help)
+			continue;
+
+		entry->handler("");
+	}
+}
+
+
+/*
+ * gms_command_help()
+ *
+ * Document the available Glk commands.
+ */
+static void gms_command_help(const char *command) {
+	gms_commandref_t entry, matched;
+	assert(command);
+
+	if (strlen(command) == 0) {
+		gms_normal_string("Glk commands are");
+		for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
+			gms_commandref_t next;
+
+			next = entry + 1;
+			gms_normal_string(next->command ? " " : " and ");
+			gms_standout_string(entry->command);
+			gms_normal_string(next->command ? "," : ".\n\n");
+		}
+
+		gms_normal_string("Glk commands may be abbreviated, as long as"
+		                  " the abbreviation is unambiguous.  Use ");
+		gms_standout_string("glk help");
+		gms_normal_string(" followed by a Glk command name for help on that"
+		                  " command.\n");
+		return;
+	}
+
+	matched = NULL;
+	for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
+		if (gms_strncasecmp(command, entry->command, strlen(command)) == 0) {
+			if (matched) {
+				gms_normal_string("The Glk command ");
+				gms_standout_string(command);
+				gms_normal_string(" is ambiguous.  Try ");
+				gms_standout_string("glk help");
+				gms_normal_string(" for more information.\n");
+				return;
+			}
+			matched = entry;
+		}
+	}
+	if (!matched) {
+		gms_normal_string("The Glk command ");
+		gms_standout_string(command);
+		gms_normal_string(" is not valid.  Try ");
+		gms_standout_string("glk help");
+		gms_normal_string(" for more information.\n");
+		return;
+	}
+
+	if (matched->handler == gms_command_summary) {
+		gms_normal_string("Prints a summary of all the current Glk Magnetic"
+		                  " settings.\n");
+	}
+
+	else if (matched->handler == gms_command_undo) {
+		gms_normal_string("Undoes a single game turn.\n\nEquivalent to the"
+		                  " standalone game 'undo' command.\n");
+	}
+
+	else if (matched->handler == gms_command_script) {
+		gms_normal_string("Logs the game's output to a file.\n\nUse ");
+		gms_standout_string("glk script on");
+		gms_normal_string(" to begin logging game output, and ");
+		gms_standout_string("glk script off");
+		gms_normal_string(" to end it.  Glk Magnetic will ask you for a file"
+		                  " when you turn scripts on.\n");
+	}
+
+	else if (matched->handler == gms_command_inputlog) {
+		gms_normal_string("Records the commands you type into a game.\n\nUse ");
+		gms_standout_string("glk inputlog on");
+		gms_normal_string(", to begin recording your commands, and ");
+		gms_standout_string("glk inputlog off");
+		gms_normal_string(" to turn off input logs.  You can play back"
+		                  " recorded commands into a game with the ");
+		gms_standout_string("glk readlog");
+		gms_normal_string(" command.\n");
+	}
+
+	else if (matched->handler == gms_command_readlog) {
+		gms_normal_string("Plays back commands recorded with ");
+		gms_standout_string("glk inputlog on");
+		gms_normal_string(".\n\nUse ");
+		gms_standout_string("glk readlog on");
+		gms_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 == gms_command_abbreviations) {
+		gms_normal_string("Controls abbreviation expansion.\n\nGlk Magnetic"
+		                  " automatically expands several standard single"
+		                  " letter abbreviations for you; for example, \"x\""
+		                  " becomes \"examine\".  Use ");
+		gms_standout_string("glk abbreviations on");
+		gms_normal_string(" to turn this feature on, and ");
+		gms_standout_string("glk abbreviations off");
+		gms_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 == gms_command_graphics) {
+		gms_normal_string("Turns interpreter graphics on and off.\n\nUse ");
+		gms_standout_string("glk graphics on");
+		gms_normal_string(" to enable interpreter graphics, and ");
+		gms_standout_string("glk graphics off");
+		gms_normal_string(" to turn graphics off and close the graphics window."
+		                  "  This control works slightly differently to the"
+		                  " 'graphics' command in Magnetic Windows and Magnetic"
+		                  " Scrolls 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 == gms_command_gamma) {
+		gms_normal_string("Sets the level of automatic gamma correction applied"
+		                  " to game graphics.\n\nUse ");
+		gms_standout_string("glk gamma normal");
+		gms_normal_string(" to set moderate automatic colour contrast"
+		                  " correction, ");
+		gms_standout_string("glk gamma high");
+		gms_normal_string(" to set high automatic colour contrast correction,"
+		                  " or ");
+		gms_standout_string("glk gamma off");
+		gms_normal_string(" to turn off all automatic gamma correction.\n");
+	}
+
+	else if (matched->handler == gms_command_animations) {
+		gms_normal_string("Turns graphic animations on and off.\n\nUse ");
+		gms_standout_string("glk animation on");
+		gms_normal_string(" to enable animations, or ");
+		gms_standout_string("glk animation off");
+		gms_normal_string(" to turn animations off.  Not all game graphics are"
+		                  " animated, so this control works only on graphics"
+		                  " that are animated.  When animation is off, Glk"
+		                  " Magnetic displays only the static portions of a"
+		                  " game's pictures.\n");
+	}
+
+	else if (matched->handler == gms_command_prompts) {
+		gms_normal_string("Controls extra input prompting.\n\n"
+		                  "Glk Magnetic can issue a replacement '>' input"
+		                  " prompt if it detects that the game hasn't prompted"
+		                  " after, say, an empty input line.  Use ");
+		gms_standout_string("glk prompts on");
+		gms_normal_string(" to turn this feature on, and ");
+		gms_standout_string("glk prompts off");
+		gms_normal_string(" to turn it off.\n");
+	}
+
+	else if (matched->handler == gms_command_version) {
+		gms_normal_string("Prints the version numbers of the Glk library"
+		                  " and the Glk Magnetic port.\n");
+	}
+
+	else if (matched->handler == gms_command_commands) {
+		gms_normal_string("Turn off Glk commands.\n\nUse ");
+		gms_standout_string("glk commands off");
+		gms_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 == gms_command_help)
+		gms_command_help("");
+
+	else
+		gms_normal_string("There is no help available on that Glk command."
+		                  "  Sorry.\n");
+}
+
+
+/*
+ * gms_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.
+ *
+ * On unambiguous returns, it will also set the value for undo_command to the
+ * table undo return value.
+ */
+static int gms_command_escape(const char *string, int *undo_command) {
+	int posn;
+	char *string_copy, *command, *argument;
+	assert(string && undo_command);
+
+	/*
+	 * Return FALSE if the string doesn't begin with the Glk command escape
+	 * introducer.
+	 */
+	posn = strspn(string, "\t ");
+	if (gms_strncasecmp(string + posn, "glk", strlen("glk")) != 0)
+		return FALSE;
+
+	/* Take a copy of the string, without any leading space or introducer. */
+	string_copy = (char *)gms_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) {
+		gms_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 = GMS_COMMAND_TABLE; entry->command; entry++) {
+			if (gms_strncasecmp(command, entry->command, strlen(command)) == 0) {
+				matches++;
+				matched = entry;
+			}
+		}
+
+		/* If the match was unambiguous, call the command handler. */
+		if (matches == 1) {
+			if (!matched->undo_return)
+				gms_normal_char('\n');
+			matched->handler(argument);
+
+			if (!matched->takes_argument && strlen(argument) > 0) {
+				gms_normal_string("[The ");
+				gms_standout_string(matched->command);
+				gms_normal_string(" command ignores arguments.]\n");
+			}
+
+			*undo_command = matched->undo_return;
+		}
+
+		/* No match, or the command was ambiguous. */
+		else {
+			gms_normal_string("\nThe Glk command ");
+			gms_standout_string(command);
+			gms_normal_string(" is ");
+			gms_normal_string(matches == 0 ? "not valid" : "ambiguous");
+			gms_normal_string(".  Try ");
+			gms_standout_string("glk help");
+			gms_normal_string(" for more information.\n");
+		}
+	} else {
+		gms_normal_char('\n');
+		gms_command_help("");
+	}
+
+	/* The string contained a Glk command; return TRUE. */
+	free(string_copy);
+	return TRUE;
+}
+
+
+/*
+ * gms_command_undo_special()
+ *
+ * This function makes a special case of the input line containing the single
+ * word "undo", treating it as if it is "glk undo".  This makes life a bit
+ * more convenient for the player, since it's the same behavior that most
+ * other IF systems have.  It returns TRUE if "undo" found, FALSE otherwise.
+ */
+static int gms_command_undo_special(const char *string) {
+	int posn, end;
+	assert(string);
+
+	/* Find the start and end of the first string word. */
+	posn = strspn(string, "\t ");
+	end = posn + strcspn(string + posn, "\t ");
+
+	/* See if string contains an "undo" request, with nothing following. */
+	if (end - posn == (int)strlen("undo")
+	        && gms_strncasecmp(string + posn, "undo", end - posn) == 0) {
+		posn = end + strspn(string + end, "\t ");
+		if (string[posn] == '\0')
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Glk port input functions                                           */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Input buffer allocated for reading input lines.  The buffer is filled
+ * from either an input log, if one is currently being read, or from Glk
+ * line input.  We also need an "undo" notification flag.
+ */
+enum { GMS_INPUTBUFFER_LENGTH = 256 };
+static char gms_input_buffer[GMS_INPUTBUFFER_LENGTH];
+static int gms_input_length = 0,
+           gms_input_cursor = 0,
+           gms_undo_notification = FALSE;
+
+/* Table of single-character command abbreviations. */
+typedef const struct {
+	const char abbreviation;       /* Abbreviation character. */
+	const char *const expansion;   /* Expansion string. */
+} gms_abbreviation_t;
+typedef gms_abbreviation_t *gms_abbreviationref_t;
+
+static gms_abbreviation_t GMS_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}
+};
+
+
+/*
+ * gms_expand_abbreviations()
+ *
+ * Expand a few common one-character abbreviations commonly found in other
+ * game systems, but not always normal in Magnetic Scrolls games.
+ */
+static void gms_expand_abbreviations(char *buffer, int size) {
+	char *command, abbreviation;
+	const char *expansion;
+	gms_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 = GMS_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 ((int)(strlen(buffer) + strlen(expansion)) - 1 >= size)
+			return;
+
+		memmove(command + strlen(expansion) - 1, command, strlen(command) + 1);
+		memcpy(command, expansion, strlen(expansion));
+
+#if 0
+		gms_standout_string("[");
+		gms_standout_char(abbreviation);
+		gms_standout_string(" -> ");
+		gms_standout_string(expansion);
+		gms_standout_string("]\n");
+#endif
+	}
+}
+
+
+/*
+ * gms_buffer_input
+ *
+ * Read and buffer a line of input.  If there is an input log active, then
+ * data is taken by reading this first.  Otherwise, the function gets a
+ * line from Glk.
+ *
+ * It also makes special cases of some lines read from the user, either
+ * handling commands inside them directly, or expanding abbreviations as
+ * appropriate.  This is not reflected in the buffer, which is adjusted as
+ * required before returning.
+ */
+static void gms_buffer_input(void) {
+	event_t event;
+
+	/*
+	 * Update the current status line display, and flush any pending buffered
+	 * output.
+	 */
+	gms_status_notify();
+	gms_output_flush();
+
+	/*
+	 * Magnetic Windows games tend not to issue a prompt after reading an empty
+	 * line of input.  This can make for a very blank looking screen.
+	 *
+	 * To slightly improve things, if it looks like we didn't get a prompt from
+	 * the game, do our own.
+	 */
+	if (gms_prompt_enabled && !gms_game_prompted()) {
+		gms_normal_char('\n');
+		gms_normal_string(GMS_INPUT_PROMPT);
+	}
+
+	/*
+	 * If we have an input log to read from, use that until it is exhausted.  On
+	 * end of file, close the stream and resume input from line requests.
+	 */
+	if (gms_readlog_stream) {
+		glui32 chars;
+
+		/* Get the next line from the log stream. */
+		chars = g_vm->glk_get_line_stream(gms_readlog_stream,
+		                                  gms_input_buffer, sizeof(gms_input_buffer));
+		if (chars > 0) {
+			/* Echo the line just read in input style. */
+			g_vm->glk_set_style(style_Input);
+			g_vm->glk_put_buffer(gms_input_buffer, chars);
+			g_vm->glk_set_style(style_Normal);
+
+			/* Note how many characters buffered, and return. */
+			gms_input_length = chars;
+			return;
+		}
+
+		/*
+		 * 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(gms_readlog_stream, NULL);
+		gms_readlog_stream = NULL;
+	}
+
+	/*
+	 * No input log being read, or we just hit the end of file on one.  Revert
+	 * to normal line input; start by getting a new line from Glk.
+	 */
+	g_vm->glk_request_line_event(gms_main_window,
+	                             gms_input_buffer, sizeof(gms_input_buffer) - 1, 0);
+	gms_event_wait(evtype_LineInput, &event);
+
+	/* Terminate the input line with a NUL. */
+	assert(event.val1 <= sizeof(gms_input_buffer) - 1);
+	gms_input_buffer[event.val1] = '\0';
+
+	/* Special handling for "undo" commands. */
+	if (gms_command_undo_special(gms_input_buffer)) {
+		/* Write the "undo" to any input log. */
+		if (gms_inputlog_stream) {
+			g_vm->glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
+			g_vm->glk_put_char_stream(gms_inputlog_stream, '\n');
+		}
+
+		/* Overwrite buffer with an empty line if we saw "undo". */
+		gms_input_buffer[0] = '\n';
+		gms_input_length = 1;
+
+		gms_undo_notification = TRUE;
+		return;
+	}
+
+	/*
+	 * If neither abbreviations nor local commands are enabled, use the data
+	 * read above without further massaging.
+	 */
+	if (gms_abbreviations_enabled || gms_commands_enabled) {
+		char *command;
+
+		/*
+		 * If the first non-space input character is a quote, bypass all
+		 * abbreviation expansion and local command recognition, and use the
+		 * unadulterated input, less introductory quote.
+		 */
+		command = gms_input_buffer + strspn(gms_input_buffer, "\t ");
+		if (command[0] == '\'') {
+			/* Delete the quote with memmove(). */
+			memmove(command, command + 1, strlen(command));
+		} else {
+			/* Check for, and expand, any abbreviated commands. */
+			if (gms_abbreviations_enabled) {
+				gms_expand_abbreviations(gms_input_buffer,
+				                         sizeof(gms_input_buffer));
+			}
+
+			/*
+			 * Check for standalone "help", then for Glk port special commands;
+			 * suppress the interpreter's use of this input for Glk commands
+			 * by overwriting the line with a single newline character.
+			 */
+			if (gms_commands_enabled) {
+				int posn;
+
+				posn = strspn(gms_input_buffer, "\t ");
+				if (gms_strncasecmp(gms_input_buffer + posn,
+				                    "help", strlen("help")) == 0) {
+					if (strspn(gms_input_buffer + posn + strlen("help"), "\t ")
+					        == strlen(gms_input_buffer + posn + strlen("help"))) {
+						gms_output_register_help_request();
+					}
+				}
+
+				if (gms_command_escape(gms_input_buffer,
+				                       &gms_undo_notification)) {
+					gms_output_silence_help_hints();
+					gms_input_buffer[0] = '\n';
+					gms_input_length = 1;
+					return;
+				}
+			}
+		}
+	}
+
+	/*
+	 * If there is an input log active, log this input string to it.  Note that
+	 * by logging here we get any abbreviation expansions but we won't log glk
+	 * special commands, nor any input read from a current open input log.
+	 */
+	if (gms_inputlog_stream) {
+		g_vm->glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
+		g_vm->glk_put_char_stream(gms_inputlog_stream, '\n');
+	}
+
+	/*
+	 * Now append a newline to the buffer, since Glk line input doesn't provide
+	 * one, and in any case, abbreviation expansion may have edited the buffer
+	 * contents (and in particular, changed the length).
+	 */
+	gms_input_buffer[strlen(gms_input_buffer) + 1] = '\0';
+	gms_input_buffer[strlen(gms_input_buffer)] = '\n';
+
+	/* Note how many characters are buffered after all of the above. */
+	gms_input_length = strlen(gms_input_buffer);
+}
+
+
+/*
+ * ms_getchar()
+ *
+ * Return the single next character to the interpreter.  This function
+ * extracts characters from the input buffer until empty, when it then
+ * tries to buffer more data.
+ */
+type8 ms_getchar(type8 trans) {
+	/* See if we are at the end of the input buffer. */
+	if (gms_input_cursor == gms_input_length) {
+		/*
+		 * Try to read in more data, and rewind buffer cursor.  As well as
+		 * reading input, this may set an undo notification.
+		 */
+		gms_buffer_input();
+		gms_input_cursor = 0;
+
+		if (gms_undo_notification) {
+			/*
+			 * Clear the undo notification, and discard buffered input (usually
+			 * just the '\n' placed there when the undo command was recognized).
+			 */
+			gms_undo_notification = FALSE;
+			gms_input_length = 0;
+
+			/*
+			 * Return the special 0, or a blank line if no undo is allowed at
+			 * this point.
+			 */
+			return trans ? 0 : '\n';
+		}
+	}
+
+	/* Return the next character from the input buffer. */
+	assert(gms_input_cursor < gms_input_length);
+	return gms_input_buffer[gms_input_cursor++];
+}
+
+#if 0
+/*
+ * gms_confirm()
+ *
+ * Print a confirmation prompt, and read a single input character, taking
+ * only [YyNn] input.  If the character is 'Y' or 'y', return TRUE.
+ */
+static int gms_confirm(const char *prompt) {
+	event_t event;
+	unsigned char response;
+	assert(prompt);
+
+	/*
+	 * Print the confirmation prompt, in a style that hints that it's from the
+	 * interpreter, not the game.
+	 */
+	gms_standout_string(prompt);
+
+	/* Wait for a single 'Y' or 'N' character response. */
+	response = ' ';
+	do {
+		g_vm->glk_request_char_event(gms_main_window);
+		gms_event_wait(evtype_CharInput, &event);
+
+		if (event.val1 <= BYTE_MAX)
+			response = g_vm->glk_char_to_upper(event.val1);
+	} while (!(response == 'Y' || response == 'N'));
+
+	/* Echo the confirmation response, and a blank line. */
+	g_vm->glk_set_style(style_Input);
+	g_vm->glk_put_string(response == 'Y' ? "Yes" : "No");
+	g_vm->glk_set_style(style_Normal);
+	g_vm->glk_put_string("\n\n");
+
+	return response == 'Y';
+}
+#endif
+
+/*---------------------------------------------------------------------*/
+/*  Glk port event functions                                           */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gms_event_wait()
+ *
+ * Process Glk events until one of the expected type arrives.  Return
+ * the event of that type.
+ */
+static void gms_event_wait(glui32 wait_type, event_t *event) {
+	assert(event);
+
+	do {
+		g_vm->glk_select(event);
+
+		switch (event->type) {
+		case evtype_Arrange:
+		case evtype_Redraw:
+			/* Refresh any sensitive windows on size events. */
+			gms_status_redraw();
+			gms_hint_redraw();
+			gms_graphics_paint();
+			break;
+
+		case evtype_Timer:
+			/* Do background graphics updates on timeout. */
+			gms_graphics_timeout();
+			break;
+
+		default:
+			break;
+		}
+	} while (event->type != (EvType)wait_type);
+}
+
+/*---------------------------------------------------------------------*/
+/*  Functions intercepted by link-time wrappers                        */
+/*---------------------------------------------------------------------*/
+
+/*
+ * __wrap_toupper()
+ * __wrap_tolower()
+ *
+ * Wrapper functions around toupper() and tolower().  The Linux linker's
+ * --wrap option will convert calls to mumble() to __wrap_mumble() if we
+ * give it the right options.  We'll use this feature to translate all
+ * toupper() and tolower() calls in the interpreter code into calls to
+ * Glk's versions of these functions.
+ *
+ * It's not critical that we do this.  If a linker, say a non-Linux one,
+ * won't do --wrap, then just do without it.  It's unlikely that there
+ * will be much noticeable difference.
+ */
+int __wrap_toupper(int ch) {
+	unsigned char uch;
+
+	uch = g_vm->glk_char_to_upper((unsigned char) ch);
+	return (int) uch;
+}
+
+int __wrap_tolower(int ch) {
+	unsigned char lch;
+
+	lch = g_vm->glk_char_to_lower((unsigned char) ch);
+	return (int) lch;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  main() and options parsing                                         */
+/*---------------------------------------------------------------------*/
+
+/*
+ * The following values need to be passed between the startup_code and main
+ * functions.
+ */
+static const char *gms_gamefile = NULL,      /* Name of game file. */
+                   *gms_game_message = NULL;  /* Error message. */
+
+
+/*
+ * gms_establish_filenames()
+ *
+ * Given a game name, try to establish three filenames from it - the main game
+ * text file, the (optional) graphics data file, and the (optional) hints
+ * file.  Given an input "file" X, the function looks for X.MAG or X.mag for
+ * game data, X.GFX or X.gfx for graphics, and X.HNT or X.hnt for hints.
+ * If the input file already ends with .MAG, .GFX, or .HNT, the extension
+ * is stripped first.
+ *
+ * The function returns NULL for filenames not available.  It's not fatal if
+ * the graphics filename or hints filename is NULL, but it is if the main game
+ * filename is NULL.  Filenames are malloc'ed, and need to be freed by the
+ * caller.
+ */
+static void gms_establish_filenames(const char *name, char **text, char **graphics, char **hints) {
+	char *base, *text_file, *graphics_file, *hints_file;
+	Common::File stream;
+	assert(name && text && graphics && hints);
+
+	/* Take a destroyable copy of the input filename. */
+	base = (char *)gms_malloc(strlen(name) + 1);
+	strcpy(base, name);
+
+	/* If base has an extension .MAG, .GFX, or .HNT, remove it. */
+	if (strlen(base) > strlen(".XXX")) {
+		if (gms_strcasecmp(base + strlen(base) - strlen(".MAG"), ".MAG") == 0
+		        || gms_strcasecmp(base + strlen(base) - strlen(".GFX"), ".GFX") == 0
+		        || gms_strcasecmp(base + strlen(base) - strlen(".HNT"), ".HNT") == 0)
+			base[strlen(base) - strlen(".XXX")] = '\0';
+	}
+
+	/* Allocate space for the return text file. */
+	text_file = (char *)gms_malloc(strlen(base) + strlen(".MAG") + 1);
+
+	/* Form a candidate text file, by adding a .MAG extension. */
+	strcpy(text_file, base);
+	strcat(text_file, ".MAG");
+
+	if (!stream.open(text_file)) {
+		/* Retry, using a .mag extension instead. */
+		strcpy(text_file, base);
+		strcat(text_file, ".mag");
+
+		if (!stream.open(text_file)) {
+			/*
+			 * No access to a usable game text file.  Return immediately,
+			 * without looking for any associated graphics or hints files.
+			 */
+			*text = NULL;
+			*graphics = NULL;
+			*hints = NULL;
+
+			free(text_file);
+			free(base);
+			return;
+		}
+	}
+	stream.close();
+
+	/* Now allocate space for the return graphics file. */
+	graphics_file = (char *)gms_malloc(strlen(base) + strlen(".GFX") + 1);
+
+	/* As above, form a candidate graphics file, using a .GFX extension. */
+	strcpy(graphics_file, base);
+	strcat(graphics_file, ".GFX");
+
+	if (!stream.open(graphics_file)) {
+		/* Retry, using a .gfx extension instead. */
+		strcpy(graphics_file, base);
+		strcat(graphics_file, ".gfx");
+
+		if (!stream.open(graphics_file)) {
+			/*
+			 * No access to any graphics file.  In this case, free memory and
+			 * reset graphics file to NULL.
+			 */
+			free(graphics_file);
+			graphics_file = NULL;
+		}
+	}
+	stream.close();
+
+	/* Now allocate space for the return hints file. */
+	hints_file = (char *)gms_malloc(strlen(base) + strlen(".HNT") + 1);
+
+	/* As above, form a candidate graphics file, using a .HNT extension. */
+	strcpy(hints_file, base);
+	strcat(hints_file, ".HNT");
+
+	if (!stream.open(hints_file)) {
+		/* Retry, using a .hnt extension instead. */
+		strcpy(hints_file, base);
+		strcat(hints_file, ".hnt");
+
+		if (!stream.open(hints_file)) {
+			/*
+			 * No access to any hints file.  In this case, free memory and
+			 * reset hints file to NULL.
+			 */
+			free(hints_file);
+			hints_file = NULL;
+		}
+	}
+	stream.close();
+
+	/* Return the text file, and graphics and hints, which may be NULL. */
+	*text = text_file;
+	*graphics = graphics_file;
+	*hints = hints_file;
+
+	free(base);
+}
+
+
+/*
+ * gms_startup_code()
+ * gms_main()
+ *
+ * Together, these functions take the place of the original main().  The
+ * first one is called from glkunix_startup_code(), to parse and generally
+ * handle options.  The second is called from g_vm->glk_main(), and does the real
+ * work of running the game.
+ */
+int gms_startup_code(int argc, char *argv[]) {
+	int argv_index;
+
+	/* Handle command line arguments. */
+	for (argv_index = 1;
+	        argv_index < argc && argv[argv_index][0] == '-'; argv_index++) {
+		if (strcmp(argv[argv_index], "-nc") == 0) {
+			gms_commands_enabled = FALSE;
+			continue;
+		}
+		if (strcmp(argv[argv_index], "-na") == 0) {
+			gms_abbreviations_enabled = FALSE;
+			continue;
+		}
+		if (strcmp(argv[argv_index], "-np") == 0) {
+			gms_graphics_enabled = FALSE;
+			continue;
+		}
+		if (strcmp(argv[argv_index], "-ng") == 0) {
+			gms_gamma_mode = GAMMA_OFF;
+			continue;
+		}
+		if (strcmp(argv[argv_index], "-nx") == 0) {
+			gms_animation_enabled = FALSE;
+			continue;
+		}
+		if (strcmp(argv[argv_index], "-ne") == 0) {
+			gms_prompt_enabled = FALSE;
+			continue;
+		}
+		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 pointer to NULL, and
+	 * complain about it later in main.  Passing the message string around
+	 * like this is a nuisance...
+	 */
+	if (argv_index == argc - 1) {
+		gms_gamefile = argv[argv_index];
+		gms_game_message = NULL;
+#ifdef GARGLK
+		{
+			const char *s;
+			s = strrchr(gms_gamefile, '\\');
+			if (s)
+				g_vm->garglk_set_story_name(s + 1);
+			s = strrchr(gms_gamefile, '/');
+			if (s)
+				g_vm->garglk_set_story_name(s + 1);
+		}
+#endif
+	} else {
+		gms_gamefile = NULL;
+		if (argv_index < argc - 1)
+			gms_game_message = "More than one game file was given"
+			                   " on the command line.";
+		else
+			gms_game_message = "No game file was given on the command line.";
+	}
+
+	/* All startup options were handled successfully. */
+	return TRUE;
+}
+
+void gms_main() {
+	char *text_file = NULL, *graphics_file = NULL, *hints_file = NULL;
+	int ms_init_status, is_running;
+
+	/* Ensure Magnetic Scrolls internal types have the right sizes. */
+	if (!(sizeof(type8) == 1 && sizeof(type8s) == 1
+	        && sizeof(type16) == 2 && sizeof(type16s) == 2
+	        && sizeof(type32) == 4 && sizeof(type32s) == 4)) {
+		gms_fatal("GLK: Types sized incorrectly, recompilation is needed");
+		g_vm->glk_exit();
+	}
+
+	/* Create the main Glk window, and set its stream as current. */
+	gms_main_window = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
+	if (!gms_main_window) {
+		gms_fatal("GLK: Can't open main window");
+		g_vm->glk_exit();
+	}
+	g_vm->glk_window_clear(gms_main_window);
+	g_vm->glk_set_window(gms_main_window);
+	g_vm->glk_set_style(style_Normal);
+
+	/* If there's a problem with the game file, complain now. */
+	if (!gms_gamefile) {
+		assert(gms_game_message);
+		gms_header_string("Glk Magnetic Error\n\n");
+		gms_normal_string(gms_game_message);
+		gms_normal_char('\n');
+		g_vm->glk_exit();
+		return;
+	}
+
+	/*
+	 * Given the basic game name, try to come up with usable text, graphics,
+	 * and hints filenames.  The graphics and hints files may be null, but the
+	 * text file may not.
+	 */
+	gms_establish_filenames(gms_gamefile,
+	                        &text_file, &graphics_file, &hints_file);
+	if (!text_file) {
+		assert(!graphics_file && !hints_file);
+		gms_header_string("Glk Magnetic Error\n\n");
+		gms_normal_string("Can't find or open game '");
+		gms_normal_string(gms_gamefile);
+		gms_normal_string("[.mag|.MAG]'");
+
+		gms_normal_char('\n');
+		g_vm->glk_exit();
+	}
+
+	/* Set the possibility of pictures depending on graphics file. */
+	if (graphics_file) {
+		/*
+		 * Check Glk library capabilities, and note pictures are impossible if
+		 * the library can't offer both graphics and timers.  We need timers to
+		 * create the background "thread" for picture updates.
+		 */
+		gms_graphics_possible = g_vm->glk_gestalt(gestalt_Graphics, 0)
+		                        && g_vm->glk_gestalt(gestalt_Timer, 0);
+	} else
+		gms_graphics_possible = FALSE;
+
+
+	/*
+	 * If pictures are impossible, clear pictures enabled flag.  That is, act
+	 * as if -np was given on the command line, even though it may not have
+	 * been.  If pictures are impossible, they can never be enabled.
+	 */
+	if (!gms_graphics_possible)
+		gms_graphics_enabled = FALSE;
+
+	/* Try to create a one-line status window.  We can live without it. */
+	g_vm->glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
+	gms_status_window = g_vm->glk_window_open(gms_main_window,
+	                    winmethod_Above | winmethod_Fixed,
+	                    1, wintype_TextGrid, 0);
+
+	/*
+	 * Load the game.  If no graphics are possible, then passing the NULL to
+	 * ms_init() runs a game without graphics.
+	 */
+	if (gms_graphics_possible) {
+		assert(graphics_file);
+		ms_init_status = ms_init(text_file, graphics_file, hints_file, NULL);
+	} else
+		ms_init_status = ms_init(text_file, NULL, hints_file, NULL);
+
+	/* Look for a complete failure to load the game. */
+	if (ms_init_status == 0) {
+		if (gms_status_window)
+			g_vm->glk_window_close(gms_status_window, NULL);
+		gms_header_string("Glk Magnetic Error\n\n");
+		gms_normal_string("Can't load game '");
+		gms_normal_string(gms_gamefile);
+		gms_normal_char('\'');
+
+		gms_normal_char('\n');
+
+		/*
+		 * Free the text file path, any graphics/hints file path, and
+		 * interpreter allocated memory.
+		 */
+		free(text_file);
+		free(graphics_file);
+		free(hints_file);
+		ms_freemem();
+		g_vm->glk_exit();
+	}
+
+	/* Try to identify the game from its text file header. */
+	gms_gameid_identify_game(text_file);
+
+	/* Print out a short banner. */
+	gms_header_string("\nMagnetic Scrolls Interpreter, version 2.3\n");
+	gms_banner_string("Written by Niclas Karlsson\n"
+	                  "Glk interface by Simon Baldwin\n\n");
+
+	/* Look for failure to load just game graphics. */
+	if (gms_graphics_possible && ms_init_status == 1) {
+		/*
+		 * Output a warning if graphics failed, but the main game text
+		 * initialized okay.
+		 */
+		gms_standout_string("Error: Unable to open graphics file\n"
+		                    "Continuing without pictures...\n\n");
+
+		gms_graphics_possible = FALSE;
+	}
+
+	/* Run the game opcodes -- ms_rungame() returns FALSE on game end. */
+	do {
+		is_running = ms_rungame();
+		g_vm->glk_tick();
+	} while (is_running);
+
+	/* Handle any updated status and pending buffered output. */
+	gms_status_notify();
+	gms_output_flush();
+
+	/* Turn off any background graphics "thread". */
+	gms_graphics_stop();
+
+	/* Free interpreter allocated memory. */
+	ms_freemem();
+
+	/*
+	 * Free any temporary memory that may have been used by graphics and hints.
+	 */
+	gms_graphics_cleanup();
+	gms_hints_cleanup();
+
+	/* Close any open transcript, input log, and/or read log. */
+	if (gms_transcript_stream) {
+		g_vm->glk_stream_close(gms_transcript_stream, NULL);
+		gms_transcript_stream = NULL;
+	}
+	if (gms_inputlog_stream) {
+		g_vm->glk_stream_close(gms_inputlog_stream, NULL);
+		gms_inputlog_stream = NULL;
+	}
+	if (gms_readlog_stream) {
+		g_vm->glk_stream_close(gms_readlog_stream, NULL);
+		gms_readlog_stream = NULL;
+	}
+
+	/* Free the text file path, and any graphics/hints file path. */
+	free(text_file);
+	free(graphics_file);
+	free(hints_file);
+}
+
+
+/*---------------------------------------------------------------------*/
+/*  Linkage between Glk entry/exit calls and the Magnetic interpreter  */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Safety flags, to ensure we always get startup before main, and that
+ * we only get a call to main once.
+ */
+static int gms_startup_called = FALSE,
+           gms_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(void) {
+	assert(gms_startup_called && !gms_main_called);
+	gms_main_called = TRUE;
+
+	/* Call the interpreter main function. */
+	gms_main();
+}
+
+
+/*
+ * Glk arguments for UNIX versions of the Glk interpreter.
+ */
+#if 0
+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 *) "-np", glkunix_arg_NoValue,
+		(char *) "-np        Turn off pictures"
+	},
+	{
+		(char *) "-ng", glkunix_arg_NoValue,
+		(char *) "-ng        Turn off automatic gamma correction on pictures"
+	},
+	{
+		(char *) "-nx", glkunix_arg_NoValue,
+		(char *) "-nx        Turn off picture animations"
+	},
+	{
+		(char *) "-ne", glkunix_arg_NoValue,
+		(char *) "-ne        Turn off additional interpreter prompt"
+	},
+	{
+		(char *) "", glkunix_arg_ValueCanFollow,
+		(char *) "filename   game to run"
+	},
+	{NULL, glkunix_arg_End, NULL}
+};
+#endif
+
+void write(const char *fmt, ...) {
+	va_list ap;
+	va_start(ap, fmt);
+	Common::String s = Common::String::format(fmt, ap);
+	va_end(ap);
+	g_vm->glk_put_buffer(s.c_str(), s.size());
+}
+
+void writeChar(char c) {
+	g_vm->glk_put_char(c);
+}
+
+} // End of namespace Magnetic
+} // End of namespace Glk
diff --git a/engines/glk/magnetic/graphics.cpp b/engines/glk/magnetic/graphics.cpp
deleted file mode 100644
index d9c62a1..0000000
--- a/engines/glk/magnetic/graphics.cpp
+++ /dev/null
@@ -1,507 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "glk/magnetic/magnetic.h"
-
-namespace Glk {
-namespace Magnetic {
-
-byte Magnetic::init_gfx1(size_t size) {
-	if (!(gfx_buf = new byte[MAX_PICTURE_SIZE]))
-		return 1;
-
-	if (!(gfx_data = new byte[size])) {
-		delete[] gfx_buf;
-		gfx_buf = nullptr;
-		return 1;
-	}
-
-	if (_gfxFile.read(gfx_data, size) != size) {
-		delete[] gfx_data;
-		delete[] gfx_buf;
-		gfx_data = gfx_buf = nullptr;
-		return 1;
-	}
-
-	gfx_ver = 1;
-	return 2;
-}
-
-byte Magnetic::init_gfx2(size_t size) {
-	if (!(gfx_buf = new byte[MAX_PICTURE_SIZE])) {
-		return 1;
-	}
-
-	gfx2_hsize = size;
-	if (!(gfx2_hdr = new byte[gfx2_hsize])) {
-		delete[] gfx_buf;
-		gfx_buf = nullptr;
-		return 1;
-	}
-
-	if (_gfxFile.read(gfx2_hdr, gfx2_hsize) != gfx2_hsize) {
-		delete[] gfx_buf;
-		delete[] gfx2_hdr;
-		gfx_buf = nullptr;
-		gfx2_hdr = nullptr;
-		return 1;
-	}
-
-	gfx_ver = 2;
-	return 2;
-}
-
-void Magnetic::ms_showpic(int c, byte mode) {
-	// TODO
-}
-
-bool Magnetic::is_blank(uint16 line, uint16 width) const {
-	int i;
-
-	for (i = line * width; i < (line + 1) * width; i++)
-		if (gfx_buf[i])
-			return false;
-	return true;
-}
-
-byte *Magnetic::ms_extract1(byte pic, uint16 * w, uint16 * h, uint16 * pal) {
-	byte *table, *data, bit, val, *buffer;
-	uint16 tablesize, count;
-	uint32 i, j, upsize, offset;
-	//uint32 datasize;
-
-	offset = READ_LE_UINT32(gfx_data + 4 * pic);
-	buffer = gfx_data + offset - 8;
-
-	for (i = 0; i < 16; i++)
-		pal[i] = READ_LE_UINT16(buffer + 0x1c + 2 * i);
-	w[0] = (uint16)(READ_LE_UINT16(buffer + 4) - READ_LE_UINT16(buffer + 2));
-	h[0] = READ_LE_UINT16(buffer + 6);
-
-	tablesize = READ_LE_UINT16(buffer + 0x3c);
-	//datasize = READ_LE_UINT32(buffer + 0x3e);
-	table = buffer + 0x42;
-	data = table + tablesize * 2 + 2;
-	upsize = h[0] * w[0];
-
-	for (i = 0, j = 0, count = 0, val = 0, bit = 7; i < upsize; i++, count--) {
-		if (!count) {
-			count = tablesize;
-			while (count < 0x80) {
-				if (data[j] & (1 << bit))
-					count = table[2 * count];
-				else
-					count = table[2 * count + 1];
-				if (!bit)
-					j++;
-				bit = (byte)(bit ? bit - 1 : 7);
-			}
-			count &= 0x7f;
-			if (count >= 0x10)
-				count -= 0x10;
-			else
-			{
-				val = (byte)count;
-				count = 1;
-			}
-		}
-		gfx_buf[i] = val;
-	}
-	for (j = w[0]; j < upsize; j++)
-		gfx_buf[j] ^= gfx_buf[j - w[0]];
-
-	for (; h[0] > 0 && is_blank((uint16)(h[0] - 1), w[0]); h[0]--);
-	for (i = 0; h[0] > 0 && is_blank((uint16)i, w[0]); h[0]--, i++);
-	return gfx_buf + i * w[0];
-}
-
-
-byte *Magnetic::ms_extract2(const char *name, uint16 *w, uint16 *h, uint16 *pal, byte *is_anim) {
-	struct picture main_pic;
-	uint32 offset = 0, length = 0, i;
-	int16 header_pos = -1;
-	byte* anim_data;
-	uint32 j;
-
-	if (is_anim != 0)
-		*is_anim = 0;
-	gfx2_name = name;
-
-	pos_table_size = 0;
-
-	// Find the uppercase (no animation) version of the picture first
-	header_pos = find_name_in_header(name, 1);
-
-	if (header_pos < 0)
-		header_pos = find_name_in_header(name, 0);
-	if (header_pos < 0)
-		return 0;
-
-	offset = READ_LE_UINT32(gfx2_hdr + header_pos + 8);
-	length = READ_LE_UINT32(gfx2_hdr + header_pos + 12);
-
-	if (offset != 0) {
-		if (gfx2_buf) {
-			delete[] gfx2_buf;
-			gfx2_buf = nullptr;
-		}
-
-		gfx2_buf = new byte[length];
-		if (!gfx2_buf)
-			return 0;
-
-		if (!_gfxFile.seek(offset) || _gfxFile.read(gfx2_buf, length) != length) {
-			delete[] gfx2_buf;
-			gfx2_buf = nullptr;
-			return 0;
-		}
-
-		for (i = 0; i < 16; i++)
-			pal[i] = READ_LE_UINT16(gfx2_buf + 4 + (2 * i));
-
-		main_pic.data = gfx2_buf + 48;
-		main_pic.data_size = READ_LE_UINT32(gfx2_buf + 38);
-		main_pic.width = READ_LE_UINT16(gfx2_buf + 42);
-		main_pic.height = READ_LE_UINT16(gfx2_buf + 44);
-		main_pic.wbytes = (uint16)(main_pic.data_size / main_pic.height);
-		main_pic.plane_step = (uint16)(main_pic.wbytes / 4);
-		main_pic.mask = (byte*)0;
-		extract_frame(&main_pic);
-
-		*w = main_pic.width;
-		*h = main_pic.height;
-
-		// Check for an animation
-		anim_data = gfx2_buf + 48 + main_pic.data_size;
-		if ((anim_data[0] != 0xD0) || (anim_data[1] != 0x5E)) {
-			byte *current;
-			uint16 frame_count;
-			uint16 value1, value2;
-			//uint16 command_count;
-
-			if (is_anim != 0)
-				*is_anim = 1;
-
-			current = anim_data + 6;
-			frame_count = READ_LE_UINT16(anim_data + 2);
-			if (frame_count > MAX_ANIMS)
-			{
-				error("animation frame array too short");
-				return 0;
-			}
-
-			/* Loop through each animation frame */
-			for (i = 0; i < frame_count; i++)
-			{
-				anim_frame_table[i].data = current + 10;
-				anim_frame_table[i].data_size = READ_LE_UINT32(current);
-				anim_frame_table[i].width = READ_LE_UINT16(current + 4);
-				anim_frame_table[i].height = READ_LE_UINT16(current + 6);
-				anim_frame_table[i].wbytes = (uint16)(anim_frame_table[i].data_size / anim_frame_table[i].height);
-				anim_frame_table[i].plane_step = (uint16)(anim_frame_table[i].wbytes / 4);
-				anim_frame_table[i].mask = (byte*)0;
-
-				current += anim_frame_table[i].data_size + 12;
-				value1 = READ_LE_UINT16(current - 2);
-				value2 = READ_LE_UINT16(current);
-
-				/* Get the mask */
-				if ((value1 == anim_frame_table[i].width) && (value2 == anim_frame_table[i].height))
-				{
-					uint16 skip;
-
-					anim_frame_table[i].mask = (byte*)(current + 4);
-					skip = READ_LE_UINT16(current + 2);
-					current += skip + 6;
-				}
-			}
-
-			/* Get the positioning tables */
-			pos_table_size = READ_LE_UINT16(current - 2);
-			if (pos_table_size > MAX_POSITIONS)
-			{
-				error("animation position array too short");
-				return 0;
-			}
-
-			for (i = 0; i < pos_table_size; i++) {
-				pos_table_count[i] = READ_LE_UINT16(current + 2);
-				current += 4;
-
-				if (pos_table_count[i] > MAX_ANIMS)
-				{
-					error("animation position array too short");
-					return 0;
-				}
-
-				for (j = 0; j < pos_table_count[i]; j++)
-				{
-					pos_table[i][j].x = READ_LE_UINT16(current);
-					pos_table[i][j].y = READ_LE_UINT16(current + 2);
-					pos_table[i][j].number = READ_LE_UINT16(current + 4) - 1;
-					current += 8;
-				}
-			}
-
-			// Get the command sequence table
-			//command_count = READ_LE_UINT16(current);
-			command_table = current + 2;
-
-			for (i = 0; i < MAX_POSITIONS; i++)
-			{
-				anim_table[i].flag = -1;
-				anim_table[i].count = -1;
-			}
-			command_index = 0;
-			anim_repeat = 0;
-			pos_table_index = -1;
-			pos_table_max = -1;
-		}
-
-		return gfx_buf;
-	}
-
-	return nullptr;
-}
-
-int16 Magnetic::find_name_in_header(const Common::String &name, bool upper) {
-	int16 header_pos = 0;
-	Common::String pic_name(name.c_str(), name.c_str() + 6);
-
-	if (upper)
-		pic_name.toUppercase();
-
-	while (header_pos < gfx2_hsize) {
-		const char *hname = (const char *)(gfx2_hdr + header_pos);
-		if (strncmp(hname, pic_name.c_str(), 6) == 0)
-			return header_pos;
-		header_pos += 16;
-	}
-
-	return -1;
-}
-
-void Magnetic::extract_frame(const picture *pic) {
-	uint32 i, x, y, bit_x, mask, ywb, yw, value, values[4];
-	values[0] = values[1] = values[2] = values[3] = 0;
-
-	if (pic->width * pic->height > MAX_PICTURE_SIZE) {
-		error("picture too large");
-		return;
-	}
-
-	for (y = 0; y < pic->height; y++) {
-		ywb = y * pic->wbytes;
-		yw = y * pic->width;
-
-		for (x = 0; x < pic->width; x++) {
-			if ((x % 8) == 0) {
-				for (i = 0; i < 4; i++)
-					values[i] = pic->data[ywb + (x / 8) + (pic->plane_step * i)];
-			}
-
-			bit_x = 7 - (x & 7);
-			mask = 1 << bit_x;
-			value = ((values[0] & mask) >> bit_x) << 0 |
-				((values[1] & mask) >> bit_x) << 1 |
-				((values[2] & mask) >> bit_x) << 2 |
-				((values[3] & mask) >> bit_x) << 3;
-			value &= 15;
-
-			gfx_buf[yw + x] = (byte)value;
-		}
-	}
-}
-
-byte *Magnetic::ms_extract(uint32 pic, uint16 *w, uint16 *h, uint16 *pal, byte *is_anim) {
-	if (is_anim)
-		*is_anim = 0;
-
-	if (gfx_buf) {
-		switch (gfx_ver) {
-		case 1:
-			return ms_extract1((byte)pic, w, h, pal);
-		case 2:
-			return ms_extract2((const char *)(code + pic), w, h, pal, is_anim);
-		}
-	}
-
-	return nullptr;
-}
-
-byte Magnetic::ms_animate(ms_position **positions, uint16 *count) {
-	byte got_anim = 0;
-	uint16 i, j, ttable;
-
-	if ((gfx_buf == 0) || (gfx2_buf == 0) || (gfx_ver != 2))
-		return 0;
-	if ((pos_table_size == 0) || (command_index < 0))
-		return 0;
-
-	*count = 0;
-	*positions = (struct ms_position*)0;
-
-	while (got_anim == 0)
-	{
-		if (pos_table_max >= 0)
-		{
-			if (pos_table_index < pos_table_max)
-			{
-				for (i = 0; i < pos_table_size; i++)
-				{
-					if (anim_table[i].flag > -1)
-					{
-						if (*count >= MAX_FRAMES)
-						{
-							error("returned animation array too short");


Commit: 9af0d1519444f6c82df24fb63be5d3def769f797
    https://github.com/scummvm/scummvm/commit/9af0d1519444f6c82df24fb63be5d3def769f797
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-11-22T18:49:07-08:00

Commit Message:
GLK: MAGNETIC: Startup fixes

Changed paths:
    engines/glk/magnetic/defs.h
    engines/glk/magnetic/emu.cpp
    engines/glk/magnetic/glk.cpp
    engines/glk/magnetic/magnetic.cpp
    engines/glk/magnetic/magnetic.h


diff --git a/engines/glk/magnetic/defs.h b/engines/glk/magnetic/defs.h
index b448200..fc7233f 100644
--- a/engines/glk/magnetic/defs.h
+++ b/engines/glk/magnetic/defs.h
@@ -461,8 +461,6 @@ extern void write(const char *fmt, ...);
 
 extern void writeChar(char c);
 
-extern int gms_startup_code(int argc, char *argv[]);
-
 extern void gms_main();
 
 } // End of namespace Magnetic
diff --git a/engines/glk/magnetic/emu.cpp b/engines/glk/magnetic/emu.cpp
index fe6c66c..d91b7e6 100644
--- a/engines/glk/magnetic/emu.cpp
+++ b/engines/glk/magnetic/emu.cpp
@@ -21,6 +21,7 @@
  */
 
 #include "glk/magnetic/defs.h"
+#include "glk/magnetic/magnetic.h"
 #include "common/file.h"
 
 namespace Glk {
@@ -2386,6 +2387,8 @@ void do_line_a(void) {
 			tmp16 = 0;
 			do {
 				if (!(l1c = ms_getchar(1))) {
+					if (g_vm->shouldQuit())
+						return;
 					if ((l1c = ms_undo()) != 0)
 						output_text(undo_ok);
 					else
diff --git a/engines/glk/magnetic/glk.cpp b/engines/glk/magnetic/glk.cpp
index db15cd1..1801ef9 100644
--- a/engines/glk/magnetic/glk.cpp
+++ b/engines/glk/magnetic/glk.cpp
@@ -35,7 +35,7 @@ namespace Magnetic {
 # define FALSE false
 #endif
 #ifndef TRUE
-# define TRUE false
+# define TRUE true
 #endif
 
 #define BYTE_MAX 255
@@ -76,17 +76,6 @@ static strid_t gms_readlog_stream = NULL;
 /* Note about whether graphics is possible, or not. */
 static int gms_graphics_possible = TRUE;
 
-/* Options that may be turned off or set by command line flags. */
-static int gms_graphics_enabled = TRUE;
-enum GammaMode {
-	GAMMA_OFF, GAMMA_NORMAL, GAMMA_HIGH
-};
-static GammaMode gms_gamma_mode = GAMMA_NORMAL;
-static int gms_animation_enabled = TRUE,
-           gms_prompt_enabled = TRUE,
-           gms_abbreviations_enabled = TRUE,
-           gms_commands_enabled = TRUE;
-
 /* Magnetic Scrolls standard input prompt string. */
 static const char *const GMS_INPUT_PROMPT = ">";
 
@@ -351,7 +340,7 @@ static gms_game_tableref_t gms_gameid_lookup_game(type32 undo_size, type32 undo_
 static type32 gms_gameid_read_uint32(int offset, Common::SeekableReadStream *stream) {
 	if (!stream->seek(offset))
 		return 0;
-	return stream->readUint32LE();
+	return stream->readUint32BE();
 }
 
 
@@ -361,24 +350,22 @@ static type32 gms_gameid_read_uint32(int offset, Common::SeekableReadStream *str
  * Identify a game from its text file header, and cache the game's name for
  * later queries.  Sets the cache to NULL if not found.
  */
-static void gms_gameid_identify_game(const char *text_file) {
+static void gms_gameid_identify_game(const Common::String &text_file) {
 	Common::File stream;
 
-	if (stream.open(text_file)) {
-		type32 undo_size, undo_pc;
-		gms_game_tableref_t game;
+	if (!stream.open(text_file))
+		error("Error opening game file");
 
-		/* Read the game's signature undo size and undo pc values. */
-		undo_size = gms_gameid_read_uint32(0x22, &stream);
-		undo_pc = gms_gameid_read_uint32(0x26, &stream);
-		stream.close();
+	type32 undo_size, undo_pc;
+	gms_game_tableref_t game;
 
-		/* Search for these values in the table, and set game name if found. */
-		game = gms_gameid_lookup_game(undo_size, undo_pc);
-		gms_gameid_game_name = game ? game->name : NULL;
-	} else {
-		gms_gameid_game_name = NULL;
-	}
+	/* Read the game's signature undo size and undo pc values. */
+	undo_size = gms_gameid_read_uint32(0x22, &stream);
+	undo_pc = gms_gameid_read_uint32(0x26, &stream);
+
+	/* Search for these values in the table, and set game name if found. */
+	game = gms_gameid_lookup_game(undo_size, undo_pc);
+	gms_gameid_game_name = game ? game->name : NULL;
 }
 
 
@@ -638,7 +625,7 @@ static void gms_graphics_close(void) {
  * If graphics enabled, start any background picture update processing.
  */
 static void gms_graphics_start(void) {
-	if (gms_graphics_enabled) {
+	if (g_vm->gms_graphics_enabled) {
 		/* If not running, start the updating "thread". */
 		if (!gms_graphics_active) {
 			g_vm->glk_request_timer_events(GMS_GRAPHICS_TIMEOUT);
@@ -680,7 +667,7 @@ static int gms_graphics_are_displayed(void) {
  * arrange events.
  */
 static void gms_graphics_paint(void) {
-	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+	if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
 		/* Set the repaint flag, and start graphics. */
 		gms_graphics_repaint = TRUE;
 		gms_graphics_start();
@@ -697,7 +684,7 @@ static void gms_graphics_paint(void) {
  * of animation policy.
  */
 static void gms_graphics_restart(void) {
-	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+	if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
 		/*
 		 * If the picture is animated, we'll need to be able to re-get the
 		 * first animation frame so that the picture can be treated as if
@@ -1009,7 +996,7 @@ static gms_gammaref_t gms_graphics_select_gamma(type8 bitmap[],
 	 * Check to see if automated correction is turned off; if it is, return
 	 * the linear gamma.
 	 */
-	if (gms_gamma_mode == GAMMA_OFF)
+	if (g_vm->gms_gamma_mode == GAMMA_OFF)
 		return linear_gamma;
 
 	/*
@@ -1030,11 +1017,11 @@ static gms_gammaref_t gms_graphics_select_gamma(type8 bitmap[],
 	 * For normal automated correction, return a gamma value half way between
 	 * the linear gamma and the equal contrast gamma.
 	 */
-	if (gms_gamma_mode == GAMMA_NORMAL)
+	if (g_vm->gms_gamma_mode == GAMMA_NORMAL)
 		return linear_gamma + (contrast_gamma - linear_gamma) / 2;
 
 	/* Correction must be high; return the equal contrast gamma. */
-	assert(gms_gamma_mode == GAMMA_HIGH);
+	assert(g_vm->gms_gamma_mode == GAMMA_HIGH);
 	return contrast_gamma;
 }
 
@@ -1937,7 +1924,7 @@ static void gms_graphics_timeout() {
 	 * If animated, and if animations are enabled, handle further animation
 	 * frames, if any.
 	 */
-	if (gms_animation_enabled && gms_graphics_animated) {
+	if (g_vm->gms_animation_enabled && gms_graphics_animated) {
 		int more_animation;
 
 		/*
@@ -2053,7 +2040,7 @@ void ms_showpic(type32 picture, type8 mode) {
 		 * If we are currently displaying the graphics window, stop any update
 		 * "thread" and turn off graphics.
 		 */
-		if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+		if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
 			gms_graphics_stop();
 			gms_graphics_close();
 		}
@@ -2088,7 +2075,7 @@ void ms_showpic(type32 picture, type8 mode) {
 	if (width == gms_graphics_width
 	        && height == gms_graphics_height
 	        && crc == current_crc
-	        && gms_graphics_enabled && gms_graphics_are_displayed())
+	        && g_vm->gms_graphics_enabled && gms_graphics_are_displayed())
 		return;
 
 	/*
@@ -2119,7 +2106,7 @@ void ms_showpic(type32 picture, type8 mode) {
 	 * the picture details will simply stick around in module variables until
 	 * they are required.
 	 */
-	if (gms_graphics_enabled) {
+	if (g_vm->gms_graphics_enabled) {
 		/*
 		 * Ensure graphics on, then set the new picture flag and start the
 		 * updating "thread".
@@ -2183,7 +2170,7 @@ static int gms_graphics_get_picture_details(int *width, int *height, int *is_ani
  */
 static int gms_graphics_get_rendering_details(const char **gamma, int *color_count,
         int *is_active) {
-	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+	if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
 		/*
 		 * Return the string representing the gamma correction.  If racing
 		 * with timeouts, we might return the gamma for the last picture.
@@ -3763,28 +3750,28 @@ static void gms_command_abbreviations(const char *argument) {
 	assert(argument);
 
 	if (gms_strcasecmp(argument, "on") == 0) {
-		if (gms_abbreviations_enabled) {
+		if (g_vm->gms_abbreviations_enabled) {
 			gms_normal_string("Glk abbreviation expansions are already on.\n");
 			return;
 		}
 
-		gms_abbreviations_enabled = TRUE;
+		g_vm->gms_abbreviations_enabled = TRUE;
 		gms_normal_string("Glk abbreviation expansions are now on.\n");
 	}
 
 	else if (gms_strcasecmp(argument, "off") == 0) {
-		if (!gms_abbreviations_enabled) {
+		if (!g_vm->gms_abbreviations_enabled) {
 			gms_normal_string("Glk abbreviation expansions are already off.\n");
 			return;
 		}
 
-		gms_abbreviations_enabled = FALSE;
+		g_vm->gms_abbreviations_enabled = FALSE;
 		gms_normal_string("Glk abbreviation expansions are now off.\n");
 	}
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk abbreviation expansions are ");
-		gms_normal_string(gms_abbreviations_enabled ? "on" : "off");
+		gms_normal_string(g_vm->gms_abbreviations_enabled ? "on" : "off");
 		gms_normal_string(".\n");
 	}
 
@@ -3814,12 +3801,12 @@ static void gms_command_graphics(const char *argument) {
 	}
 
 	if (gms_strcasecmp(argument, "on") == 0) {
-		if (gms_graphics_enabled) {
+		if (g_vm->gms_graphics_enabled) {
 			gms_normal_string("Glk graphics are already on.\n");
 			return;
 		}
 
-		gms_graphics_enabled = TRUE;
+		g_vm->gms_graphics_enabled = TRUE;
 
 		/* If a picture is loaded, call the restart function to repaint it. */
 		if (gms_graphics_picture_is_available()) {
@@ -3834,7 +3821,7 @@ static void gms_command_graphics(const char *argument) {
 	}
 
 	else if (gms_strcasecmp(argument, "off") == 0) {
-		if (!gms_graphics_enabled) {
+		if (!g_vm->gms_graphics_enabled) {
 			gms_normal_string("Glk graphics are already off.\n");
 			return;
 		}
@@ -3843,7 +3830,7 @@ static void gms_command_graphics(const char *argument) {
 		 * Set graphics to disabled, and stop any graphics processing.  Close
 		 * the graphics window.
 		 */
-		gms_graphics_enabled = FALSE;
+		g_vm->gms_graphics_enabled = FALSE;
 		gms_graphics_stop();
 		gms_graphics_close();
 
@@ -3852,7 +3839,7 @@ static void gms_command_graphics(const char *argument) {
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk graphics are available,");
-		gms_normal_string(gms_graphics_enabled
+		gms_normal_string(g_vm->gms_graphics_enabled
 		                  ? " and enabled.\n" : " but disabled.\n");
 
 		if (gms_graphics_picture_is_available()) {
@@ -3879,7 +3866,7 @@ static void gms_command_graphics(const char *argument) {
 		if (!gms_graphics_interpreter_enabled())
 			gms_normal_string("Interpreter graphics are disabled.\n");
 
-		if (gms_graphics_enabled && gms_graphics_are_displayed()) {
+		if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
 			int color_count, is_active;
 			const char *gamma;
 
@@ -3894,7 +3881,7 @@ static void gms_command_graphics(const char *argument) {
 				gms_normal_string(buffer);
 				gms_normal_string(" colours");
 
-				if (gms_gamma_mode == GAMMA_OFF)
+				if (g_vm->gms_gamma_mode == GAMMA_OFF)
 					gms_normal_string(", without gamma correction");
 				else {
 					gms_normal_string(", with gamma ");
@@ -3906,7 +3893,7 @@ static void gms_command_graphics(const char *argument) {
 				gms_normal_string("Graphics are being displayed.\n");
 		}
 
-		if (gms_graphics_enabled && !gms_graphics_are_displayed())
+		if (g_vm->gms_graphics_enabled && !gms_graphics_are_displayed())
 			gms_normal_string("Graphics are not being displayed.\n");
 	}
 
@@ -3934,13 +3921,13 @@ static void gms_command_gamma(const char *argument) {
 	}
 
 	if (gms_strcasecmp(argument, "high") == 0) {
-		if (gms_gamma_mode == GAMMA_HIGH) {
+		if (g_vm->gms_gamma_mode == GAMMA_HIGH) {
 			gms_normal_string("Glk automatic gamma correction mode is"
 			                  " already 'high'.\n");
 			return;
 		}
 
-		gms_gamma_mode = GAMMA_HIGH;
+		g_vm->gms_gamma_mode = GAMMA_HIGH;
 		gms_graphics_restart();
 
 		gms_normal_string("Glk automatic gamma correction mode is"
@@ -3949,13 +3936,13 @@ static void gms_command_gamma(const char *argument) {
 
 	else if (gms_strcasecmp(argument, "normal") == 0
 	         || gms_strcasecmp(argument, "on") == 0) {
-		if (gms_gamma_mode == GAMMA_NORMAL) {
+		if (g_vm->gms_gamma_mode == GAMMA_NORMAL) {
 			gms_normal_string("Glk automatic gamma correction mode is"
 			                  " already 'normal'.\n");
 			return;
 		}
 
-		gms_gamma_mode = GAMMA_NORMAL;
+		g_vm->gms_gamma_mode = GAMMA_NORMAL;
 		gms_graphics_restart();
 
 		gms_normal_string("Glk automatic gamma correction mode is"
@@ -3964,13 +3951,13 @@ static void gms_command_gamma(const char *argument) {
 
 	else if (gms_strcasecmp(argument, "none") == 0
 	         || gms_strcasecmp(argument, "off") == 0) {
-		if (gms_gamma_mode == GAMMA_OFF) {
+		if (g_vm->gms_gamma_mode == GAMMA_OFF) {
 			gms_normal_string("Glk automatic gamma correction mode is"
 			                  " already 'off'.\n");
 			return;
 		}
 
-		gms_gamma_mode = GAMMA_OFF;
+		g_vm->gms_gamma_mode = GAMMA_OFF;
 		gms_graphics_restart();
 
 		gms_normal_string("Glk automatic gamma correction mode is"
@@ -3979,7 +3966,7 @@ static void gms_command_gamma(const char *argument) {
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk automatic gamma correction mode is '");
-		switch (gms_gamma_mode) {
+		switch (g_vm->gms_gamma_mode) {
 		case GAMMA_OFF:
 			gms_normal_string("off");
 			break;
@@ -4021,7 +4008,7 @@ static void gms_command_animations(const char *argument) {
 	if (gms_strcasecmp(argument, "on") == 0) {
 		int is_animated;
 
-		if (gms_animation_enabled) {
+		if (g_vm->gms_animation_enabled) {
 			gms_normal_string("Glk graphics animations are already on.\n");
 			return;
 		}
@@ -4031,7 +4018,7 @@ static void gms_command_animations(const char *argument) {
 		 * is animated; if it isn't, we can leave it displayed as is, since
 		 * changing animation mode doesn't affect this picture.
 		 */
-		gms_animation_enabled = TRUE;
+		g_vm->gms_animation_enabled = TRUE;
 		if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
 			if (is_animated)
 				gms_graphics_restart();
@@ -4043,12 +4030,12 @@ static void gms_command_animations(const char *argument) {
 	else if (gms_strcasecmp(argument, "off") == 0) {
 		int is_animated;
 
-		if (!gms_animation_enabled) {
+		if (!g_vm->gms_animation_enabled) {
 			gms_normal_string("Glk graphics animations are already off.\n");
 			return;
 		}
 
-		gms_animation_enabled = FALSE;
+		g_vm->gms_animation_enabled = FALSE;
 		if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
 			if (is_animated)
 				gms_graphics_restart();
@@ -4059,7 +4046,7 @@ static void gms_command_animations(const char *argument) {
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk graphics animations are ");
-		gms_normal_string(gms_animation_enabled ? "on" : "off");
+		gms_normal_string(g_vm->gms_animation_enabled ? "on" : "off");
 		gms_normal_string(".\n");
 	}
 
@@ -4082,12 +4069,12 @@ static void gms_command_prompts(const char *argument) {
 	assert(argument);
 
 	if (gms_strcasecmp(argument, "on") == 0) {
-		if (gms_prompt_enabled) {
+		if (g_vm->gms_prompt_enabled) {
 			gms_normal_string("Glk extra prompts are already on.\n");
 			return;
 		}
 
-		gms_prompt_enabled = TRUE;
+		g_vm->gms_prompt_enabled = TRUE;
 		gms_normal_string("Glk extra prompts are now on.\n");
 
 		/* Check for a game prompt to clear the flag. */
@@ -4095,18 +4082,18 @@ static void gms_command_prompts(const char *argument) {
 	}
 
 	else if (gms_strcasecmp(argument, "off") == 0) {
-		if (!gms_prompt_enabled) {
+		if (!g_vm->gms_prompt_enabled) {
 			gms_normal_string("Glk extra prompts are already off.\n");
 			return;
 		}
 
-		gms_prompt_enabled = FALSE;
+		g_vm->gms_prompt_enabled = FALSE;
 		gms_normal_string("Glk extra prompts are now off.\n");
 	}
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk extra prompts are ");
-		gms_normal_string(gms_prompt_enabled ? "on" : "off");
+		gms_normal_string(g_vm->gms_prompt_enabled ? "on" : "off");
 		gms_normal_string(".\n");
 	}
 
@@ -4166,13 +4153,13 @@ static void gms_command_commands(const char *argument) {
 	}
 
 	else if (gms_strcasecmp(argument, "off") == 0) {
-		gms_commands_enabled = FALSE;
+		g_vm->gms_commands_enabled = FALSE;
 		gms_normal_string("Glk commands are now off.\n");
 	}
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk commands are ");
-		gms_normal_string(gms_commands_enabled ? "on" : "off");
+		gms_normal_string(g_vm->gms_commands_enabled ? "on" : "off");
 		gms_normal_string(".\n");
 	}
 
@@ -4658,7 +4645,7 @@ static void gms_buffer_input(void) {
 	 * To slightly improve things, if it looks like we didn't get a prompt from
 	 * the game, do our own.
 	 */
-	if (gms_prompt_enabled && !gms_game_prompted()) {
+	if (g_vm->gms_prompt_enabled && !gms_game_prompted()) {
 		gms_normal_char('\n');
 		gms_normal_string(GMS_INPUT_PROMPT);
 	}
@@ -4699,6 +4686,10 @@ static void gms_buffer_input(void) {
 	g_vm->glk_request_line_event(gms_main_window,
 	                             gms_input_buffer, sizeof(gms_input_buffer) - 1, 0);
 	gms_event_wait(evtype_LineInput, &event);
+	if (g_vm->shouldQuit()) {
+		g_vm->glk_cancel_line_event(gms_main_window, &event);
+		return;
+	}
 
 	/* Terminate the input line with a NUL. */
 	assert(event.val1 <= sizeof(gms_input_buffer) - 1);
@@ -4724,7 +4715,7 @@ static void gms_buffer_input(void) {
 	 * If neither abbreviations nor local commands are enabled, use the data
 	 * read above without further massaging.
 	 */
-	if (gms_abbreviations_enabled || gms_commands_enabled) {
+	if (g_vm->gms_abbreviations_enabled || g_vm->gms_commands_enabled) {
 		char *command;
 
 		/*
@@ -4738,7 +4729,7 @@ static void gms_buffer_input(void) {
 			memmove(command, command + 1, strlen(command));
 		} else {
 			/* Check for, and expand, any abbreviated commands. */
-			if (gms_abbreviations_enabled) {
+			if (g_vm->gms_abbreviations_enabled) {
 				gms_expand_abbreviations(gms_input_buffer,
 				                         sizeof(gms_input_buffer));
 			}
@@ -4748,7 +4739,7 @@ static void gms_buffer_input(void) {
 			 * suppress the interpreter's use of this input for Glk commands
 			 * by overwriting the line with a single newline character.
 			 */
-			if (gms_commands_enabled) {
+			if (g_vm->gms_commands_enabled) {
 				int posn;
 
 				posn = strspn(gms_input_buffer, "\t ");
@@ -4811,6 +4802,9 @@ type8 ms_getchar(type8 trans) {
 		gms_buffer_input();
 		gms_input_cursor = 0;
 
+		if (g_vm->shouldQuit())
+			return '\0';
+		
 		if (gms_undo_notification) {
 			/*
 			 * Clear the undo notification, and discard buffered input (usually
@@ -4900,6 +4894,9 @@ static void gms_event_wait(glui32 wait_type, event_t *event) {
 			gms_graphics_timeout();
 			break;
 
+		case evtype_Quit:
+			return;
+
 		default:
 			break;
 		}
@@ -4947,8 +4944,7 @@ int __wrap_tolower(int ch) {
  * The following values need to be passed between the startup_code and main
  * functions.
  */
-static const char *gms_gamefile = NULL,      /* Name of game file. */
-                   *gms_game_message = NULL;  /* Error message. */
+static const char *gms_game_message = NULL;  /* Error message. */
 
 
 /*
@@ -5065,132 +5061,28 @@ static void gms_establish_filenames(const char *name, char **text, char **graphi
 	free(base);
 }
 
-
-/*
- * gms_startup_code()
- * gms_main()
- *
- * Together, these functions take the place of the original main().  The
- * first one is called from glkunix_startup_code(), to parse and generally
- * handle options.  The second is called from g_vm->glk_main(), and does the real
- * work of running the game.
- */
-int gms_startup_code(int argc, char *argv[]) {
-	int argv_index;
-
-	/* Handle command line arguments. */
-	for (argv_index = 1;
-	        argv_index < argc && argv[argv_index][0] == '-'; argv_index++) {
-		if (strcmp(argv[argv_index], "-nc") == 0) {
-			gms_commands_enabled = FALSE;
-			continue;
-		}
-		if (strcmp(argv[argv_index], "-na") == 0) {
-			gms_abbreviations_enabled = FALSE;
-			continue;
-		}
-		if (strcmp(argv[argv_index], "-np") == 0) {
-			gms_graphics_enabled = FALSE;
-			continue;
-		}
-		if (strcmp(argv[argv_index], "-ng") == 0) {
-			gms_gamma_mode = GAMMA_OFF;
-			continue;
-		}
-		if (strcmp(argv[argv_index], "-nx") == 0) {
-			gms_animation_enabled = FALSE;
-			continue;
-		}
-		if (strcmp(argv[argv_index], "-ne") == 0) {
-			gms_prompt_enabled = FALSE;
-			continue;
-		}
-		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 pointer to NULL, and
-	 * complain about it later in main.  Passing the message string around
-	 * like this is a nuisance...
-	 */
-	if (argv_index == argc - 1) {
-		gms_gamefile = argv[argv_index];
-		gms_game_message = NULL;
-#ifdef GARGLK
-		{
-			const char *s;
-			s = strrchr(gms_gamefile, '\\');
-			if (s)
-				g_vm->garglk_set_story_name(s + 1);
-			s = strrchr(gms_gamefile, '/');
-			if (s)
-				g_vm->garglk_set_story_name(s + 1);
-		}
-#endif
-	} else {
-		gms_gamefile = NULL;
-		if (argv_index < argc - 1)
-			gms_game_message = "More than one game file was given"
-			                   " on the command line.";
-		else
-			gms_game_message = "No game file was given on the command line.";
-	}
-
-	/* All startup options were handled successfully. */
-	return TRUE;
-}
-
 void gms_main() {
 	char *text_file = NULL, *graphics_file = NULL, *hints_file = NULL;
 	int ms_init_status, is_running;
 
-	/* Ensure Magnetic Scrolls internal types have the right sizes. */
-	if (!(sizeof(type8) == 1 && sizeof(type8s) == 1
-	        && sizeof(type16) == 2 && sizeof(type16s) == 2
-	        && sizeof(type32) == 4 && sizeof(type32s) == 4)) {
-		gms_fatal("GLK: Types sized incorrectly, recompilation is needed");
-		g_vm->glk_exit();
-	}
-
 	/* Create the main Glk window, and set its stream as current. */
 	gms_main_window = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
 	if (!gms_main_window) {
 		gms_fatal("GLK: Can't open main window");
 		g_vm->glk_exit();
+		return;
 	}
 	g_vm->glk_window_clear(gms_main_window);
 	g_vm->glk_set_window(gms_main_window);
 	g_vm->glk_set_style(style_Normal);
 
-	/* If there's a problem with the game file, complain now. */
-	if (!gms_gamefile) {
-		assert(gms_game_message);
-		gms_header_string("Glk Magnetic Error\n\n");
-		gms_normal_string(gms_game_message);
-		gms_normal_char('\n');
-		g_vm->glk_exit();
-		return;
-	}
-
 	/*
 	 * Given the basic game name, try to come up with usable text, graphics,
 	 * and hints filenames.  The graphics and hints files may be null, but the
 	 * text file may not.
 	 */
-	gms_establish_filenames(gms_gamefile,
-	                        &text_file, &graphics_file, &hints_file);
-	if (!text_file) {
-		assert(!graphics_file && !hints_file);
-		gms_header_string("Glk Magnetic Error\n\n");
-		gms_normal_string("Can't find or open game '");
-		gms_normal_string(gms_gamefile);
-		gms_normal_string("[.mag|.MAG]'");
-
-		gms_normal_char('\n');
-		g_vm->glk_exit();
-	}
+	Common::String gameFile = g_vm->getFilename();
+	gms_establish_filenames(gameFile.c_str(), &text_file, &graphics_file, &hints_file);
 
 	/* Set the possibility of pictures depending on graphics file. */
 	if (graphics_file) {
@@ -5211,7 +5103,7 @@ void gms_main() {
 	 * been.  If pictures are impossible, they can never be enabled.
 	 */
 	if (!gms_graphics_possible)
-		gms_graphics_enabled = FALSE;
+		g_vm->gms_graphics_enabled = FALSE;
 
 	/* Try to create a one-line status window.  We can live without it. */
 	g_vm->glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
@@ -5235,7 +5127,7 @@ void gms_main() {
 			g_vm->glk_window_close(gms_status_window, NULL);
 		gms_header_string("Glk Magnetic Error\n\n");
 		gms_normal_string("Can't load game '");
-		gms_normal_string(gms_gamefile);
+		gms_normal_string(gameFile.c_str());
 		gms_normal_char('\'');
 
 		gms_normal_char('\n');
@@ -5254,11 +5146,6 @@ void gms_main() {
 	/* Try to identify the game from its text file header. */
 	gms_gameid_identify_game(text_file);
 
-	/* Print out a short banner. */
-	gms_header_string("\nMagnetic Scrolls Interpreter, version 2.3\n");
-	gms_banner_string("Written by Niclas Karlsson\n"
-	                  "Glk interface by Simon Baldwin\n\n");
-
 	/* Look for failure to load just game graphics. */
 	if (gms_graphics_possible && ms_init_status == 1) {
 		/*
@@ -5273,7 +5160,7 @@ void gms_main() {
 
 	/* Run the game opcodes -- ms_rungame() returns FALSE on game end. */
 	do {
-		is_running = ms_rungame();
+		is_running = ms_rungame() && !g_vm->shouldQuit();
 		g_vm->glk_tick();
 	} while (is_running);
 
@@ -5326,12 +5213,12 @@ static int gms_startup_called = FALSE,
            gms_main_called = FALSE;
 
 /*
- * g_vm->glk_main()
+ * glk_main()
  *
  * 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(gms_startup_called && !gms_main_called);
 	gms_main_called = TRUE;
 
@@ -5339,44 +5226,6 @@ void glk_main(void) {
 	gms_main();
 }
 
-
-/*
- * Glk arguments for UNIX versions of the Glk interpreter.
- */
-#if 0
-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 *) "-np", glkunix_arg_NoValue,
-		(char *) "-np        Turn off pictures"
-	},
-	{
-		(char *) "-ng", glkunix_arg_NoValue,
-		(char *) "-ng        Turn off automatic gamma correction on pictures"
-	},
-	{
-		(char *) "-nx", glkunix_arg_NoValue,
-		(char *) "-nx        Turn off picture animations"
-	},
-	{
-		(char *) "-ne", glkunix_arg_NoValue,
-		(char *) "-ne        Turn off additional interpreter prompt"
-	},
-	{
-		(char *) "", glkunix_arg_ValueCanFollow,
-		(char *) "filename   game to run"
-	},
-	{NULL, glkunix_arg_End, NULL}
-};
-#endif
-
 void write(const char *fmt, ...) {
 	va_list ap;
 	va_start(ap, fmt);
diff --git a/engines/glk/magnetic/magnetic.cpp b/engines/glk/magnetic/magnetic.cpp
index 3689668..b4f1b2e 100644
--- a/engines/glk/magnetic/magnetic.cpp
+++ b/engines/glk/magnetic/magnetic.cpp
@@ -22,21 +22,49 @@
 
 #include "glk/magnetic/magnetic.h"
 #include "glk/magnetic/defs.h"
+#include "common/config-manager.h"
 
 namespace Glk {
 namespace Magnetic {
 
 Magnetic *g_vm;
 
-Magnetic::Magnetic(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
+Magnetic::Magnetic(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
+		gms_gamma_mode(GAMMA_NORMAL), gms_animation_enabled(true),
+		gms_prompt_enabled(true), gms_abbreviations_enabled(true), gms_commands_enabled(true),
+		gms_graphics_enabled(false) {
 	g_vm = this;
 }
 
 void Magnetic::runGame() {
-	gms_startup_code(0, nullptr);
+	initialize();
 	gms_main();
 }
 
+void Magnetic::initialize() {
+	// Local handling for Glk special commands
+	if (ConfMan.hasKey("commands_enabled"))
+		gms_commands_enabled = ConfMan.getBool("commands_enabled");
+	// Abbreviation expansions
+	if (ConfMan.hasKey("abbreviations_enabled"))
+		gms_abbreviations_enabled = ConfMan.getBool("abbreviations_enabled");
+	// Pictures enabled
+	if (ConfMan.hasKey("graphics_enabled"))
+		gms_graphics_enabled = ConfMan.getBool("graphics_enabled");
+	// Automatic gamma correction on pictures
+	if (ConfMan.hasKey("gamma_mode") && !ConfMan.getBool("gamma_mode"))
+		gms_gamma_mode = GAMMA_OFF;
+	// Animations
+	if (ConfMan.hasKey("animation_enabled"))
+		gms_animation_enabled = ConfMan.getBool("animation_enabled");
+	// Prompt enabled
+	if (ConfMan.hasKey("prompt_enabled"))
+		gms_prompt_enabled = ConfMan.getBool("prompt_enabled");
+
+	// Close the already opened gamefile, since the Magnetic code will open it on it's own
+	_gameFile.close();
+}
+
 Common::Error Magnetic::readSaveData(Common::SeekableReadStream *rs) {
 	// TODO
 	return Common::kReadingFailed;
diff --git a/engines/glk/magnetic/magnetic.h b/engines/glk/magnetic/magnetic.h
index 33f9a4d..4e81e4f 100644
--- a/engines/glk/magnetic/magnetic.h
+++ b/engines/glk/magnetic/magnetic.h
@@ -32,11 +32,25 @@
 namespace Glk {
 namespace Magnetic {
 
+enum GammaMode {
+	GAMMA_OFF, GAMMA_NORMAL, GAMMA_HIGH
+};
+
 /**
  * Magnetic game interpreter
  */
 class Magnetic : public GlkAPI {
 public:
+	GammaMode gms_gamma_mode;
+	bool gms_animation_enabled, gms_prompt_enabled;
+	bool gms_abbreviations_enabled, gms_commands_enabled;
+	bool gms_graphics_enabled;
+private:
+	/**
+	 * Performs initialization
+	 */
+	void initialize();
+public:
 	/**
 	 * Constructor
 	 */


Commit: 9df3c85184b10a71be27209313efbc23974d1225
    https://github.com/scummvm/scummvm/commit/9df3c85184b10a71be27209313efbc23974d1225
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-11-22T18:49:07-08:00

Commit Message:
GLK: MAGNETIC: Making functions all class methods, statics into class fields

Changed paths:
  A engines/glk/magnetic/magnetic_defs.h
  R engines/glk/magnetic/defs.h
    engines/glk/magnetic/detection.cpp
    engines/glk/magnetic/detection.h
    engines/glk/magnetic/detection_tables.h
    engines/glk/magnetic/emu.cpp
    engines/glk/magnetic/glk.cpp
    engines/glk/magnetic/magnetic.cpp
    engines/glk/magnetic/magnetic.h
    engines/glk/magnetic/magnetic_types.h
    engines/glk/magnetic/main.cpp


diff --git a/engines/glk/magnetic/defs.h b/engines/glk/magnetic/defs.h
deleted file mode 100644
index fc7233f..0000000
--- a/engines/glk/magnetic/defs.h
+++ /dev/null
@@ -1,469 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#ifndef MAGNETIC_DEFS_H
-#define MAGNETIC_DEFS_H
-
-#include "common/scummsys.h"
-#include "glk/magnetic/magnetic_types.h"
-
-namespace Glk {
-namespace Magnetic {
-
-/*****************************************************************************\
-* Type definitions for Magnetic
-*
-* Note: When running into trouble please ensure that these types have the
-*       correct number of bits on your system !!!
-\*****************************************************************************/
-
-typedef byte type8;
-typedef int8 type8s;
-typedef uint16 type16;
-typedef int16 type16s;
-typedef uint32 type32;
-typedef int32 type32s;
-
-/****************************************************************************\
-* Compile time switches
-\****************************************************************************/
-
-/* Switch:  SAVEMEM
-   Purpose: Magnetic loads a complete graphics file into memory by default.
-            Setting this switch you tell Magnetic to load images on request
-            (saving memory, wasting load time)
-
-#define SAVEMEM
-*/
-
-/* Switch:  NO_ANIMATION
-   Purpose: By default Magnetic plays animated graphics.
-            Setting this switch to ignore animations, Magnetic shows the
-            static parts of the images anyway!
-
-#define NO_ANIMATION
-*/
-
-/****************************************************************************\
-* Abstract functions
-*
-* Note: These functions MUST be implemented by each port of Magnetic!
-\****************************************************************************/
-
-/****************************************************************************\
-* Function: ms_load_file
-*
-* Purpose: Load a save game file and restore game
-*
-* Parameters:   type8s* name            zero terminated string of filename
-*                                       typed by player
-*               type8*  ptr             pointer to data to save
-*               type16 size             number of bytes to save
-*
-* Result: 0 is successful
-*
-* Note: You probably want to put in a file requester!
-\****************************************************************************/
-
-extern type8 ms_load_file(const char *name, type8 *ptr, type16 size);
-
-/****************************************************************************\
-* Function: ms_save_file
-*
-* Purpose: Save the current game to file
-*
-* Parameters:   type8s* name            zero terminated string of filename
-*                                       typed by player
-*               type8*  ptr             pointer to data to save
-*               type16  size            number of bytes to save
-*
-* Result: 0 is successful
-*
-* Note: You probably want to put in a file requester!
-\****************************************************************************/
-
-extern type8 ms_save_file(const char *name, type8 *ptr, type16 size);
-
-/****************************************************************************\
-* Function: ms_statuschar
-*
-* Purpose: Output a single character to the status bar
-*
-* Parameters:   type8   c               character to be printed
-*
-* Note: All characters are printed as provided except for:
-*       0x0A resets the x position to zero
-*       0x09 moves the cursor to the right half of the bar, ie 'width-11'
-\****************************************************************************/
-
-extern void ms_statuschar(type8 c);
-
-/****************************************************************************\
-* Function: ms_putchar
-*
-* Purpose: Output a single character to the game/text windows
-*
-* Parameters:   type8   c               character to be printed
-*
-* Note: It is highly recommended to buffer the output, see also ms_flush()
-\****************************************************************************/
-
-extern void ms_putchar(type8 c);
-
-/****************************************************************************\
-* Function: ms_flush
-*
-* Purpose: Flush the output buffer (if applicable)
-*
-* Note: see also ms_putchar
-\****************************************************************************/
-
-extern void ms_flush(void);
-
-/****************************************************************************\
-* Function: ms_getchar
-*
-* Purpose: Read user input, buffered
-*
-* Parameters:   type8   trans           if not 0, translate any #undo
-*                                       input to a return code of 0
-*
-* Return: One character
-*
-* Note: The first time it is called a string should be read and then given
-*       back one byte at a time (ie. one for each call) until a '\n' is
-*       reached (which will be the last byte sent back before it all restarts)
-*       Returning a zero means 'undo' and the rest of the line must then be
-*       ignored.
-*       Returning 1 means that the opcode should return immediately. This is
-*       needed to prevent possible corruption of the game's memory in
-*       interpreters which allow a new game to be loaded without restarting.
-\****************************************************************************/
-
-extern type8 ms_getchar(type8 trans);
-
-/****************************************************************************\
-* Function: ms_showpic
-*
-* Purpose: Displays or hides a picture
-*
-* Parameter:    type32  c       number of image to be displayed
-*               type8   mode    mode == 0 means gfx off,
-*                               mode == 1 gfx on thumbnails,
-*                               mode == 2 gfx on normal.
-*
-* Note: For retrieving the raw data of a picture call ms_extract (see below)
-\****************************************************************************/
-
-extern void ms_showpic(type32 c, type8 mode);
-
-/****************************************************************************\
-* Function: ms_fatal
-*
-* Purpose: Handle fatal interpreter error
-*
-* Parameter:    type8s* txt     message
-\****************************************************************************/
-
-extern void ms_fatal(const char *txt);
-
-/****************************************************************************\
-* Magnetic core functions
-*
-* Note: These functions SHOULD be used somewhere in your port!
-\****************************************************************************/
-
-/****************************************************************************\
-* Function: ms_extract
-*
-* Purpose: Extract a picture and return a pointer to a raw bitmap
-*
-* Parameters:   type32  c               number of the picture
-*               type16* w               width of picture
-*               type16* h               height pf picture
-*               type16* pal             array for the palette (16 colours)
-*               type8*  is_anim         1 if animated picture, otherwise 0
-*                                       OR null (!)
-*
-* Return: Pointer to bitmap data if successful, otherwise null (also if gfx
-*         are disabled!)
-*
-* Note: All pictures have 16 colours and the palette entries use 3-bit RGB
-*       encoded as 00000RRR0GGG0BBB, that is, bits 0-2 give the blue
-*       component, bits 4-6 the green and bits 8-10 the red. The bitmap is
-*       one byte per pixel, with each byte giving the pixel's index into the
-*       palette. The data is given starting from the upper left corner. The
-*       image buffer is reused when the next picture is requested, so take
-*       care! More information on animated pictures are below!
-\****************************************************************************/
-
-extern type8 *ms_extract(type32 c, type16 *w, type16 *h, type16 *pal, type8 *is_anim);
-
-/****************************************************************************\
-* Magnetic animated pictures support
-*
-* Note: Some of the pictures for Wonderland and the Collection Volume 1 games
-* are animations. To detect these, pass a pointer to a type8 as the is_anim
-* argument to ms_extract().
-*
-* There are two types of animated images, however almost all images are type1.
-* A type1 image consists of four main elements:
-* 1) A static picture which is loaded straight at the beginning
-* 2) A set of frames with a mask. These frames are just "small pictures", which
-*    are coded like the normal static pictures. The image mask determines
-*    how the frame is removed after it has been displayed. A mask is exactly
-*    1/8 the size of the image and holds 1 bit per pixel, saying "remove pixel"
-*    or leave pixel set when frame gets removed. It might be a good idea to check
-*    your system documentation for masking operations as your system might be
-*    able to use this mask data directly.
-* 3) Positioning tables. These hold animation sequences consisting of commands
-*    like "Draw frame 12 at (123,456)"
-* 4) A playback script, which determines how to use the positioning tables.
-*    These scripts are handled inside Magnetic, so no need to worry about.
-*    However, details can be found in the ms_animate() function.
-*
-* A type2 image is like a type1 image, but it does not have a static
-* picture, nor does it have frame masking. It just consists of frames.
-*
-* How to support animations?
-* After getting is_anim == 1 you should call ms_animate() immediately, and at
-* regular intervals until ms_animate() returns 0. An appropriate interval
-* between calls is about 100 milliseconds.
-* Each call to ms_animate() will fill in the arguments with the address
-* and size of an array of ms_position structures (see below), each of
-* which holds an an animation frame number and x and y co-ordinates. To
-* display the animation, decode all the animation frames (discussed below)
-* from a single call to ms_animate() and display each one over the main picture.
-* If your port does not support animations, define NO_ANIMATION.
-\****************************************************************************/
-
-/****************************************************************************\
-* Function: ms_animate
-*
-* Purpose: Generate the next frame of an animation
-*
-* Parameters:   ms_position**   positions  array of ms_position structs
-*               type16*         count      size of array
-*
-* Return: 1 if animation continues, 0 if animation is finfished
-*
-* Note: The positions array holds size ms_positions structures. BEFORE calling
-*       ms_animate again, retrieve the frames for all the ms_positions
-*       structures with ms_get_anim_frame and display each one on the static
-*       main picture.
-\****************************************************************************/
-
-extern type8 ms_animate(struct ms_position **positions, type16 *count);
-
-/****************************************************************************\
-* Function: ms_get_anim_frame
-*
-* Purpose: Extracts the bitmap data of a single animation frame
-*
-* Parameters:   type16s   number        number of frame (see ms_position struct)
-*               type16*   width         width of frame
-*               type16*   height        height of frame
-*               type8**   mask          pointer to masking data, might be NULL
-*
-* Return: 1 if animation continues, 0 if animation is finfished
-*
-* Note: The format of the frame is identical to the main pictures' returned by
-*       ms_extract. The mask has one-bit-per-pixel, determing how to handle the
-*       removal of the frame.
-\****************************************************************************/
-
-extern type8 *ms_get_anim_frame(type16s number, type16 *width, type16 *height, type8 **mask);
-
-/****************************************************************************\
-* Function: ms_anim_is_repeating
-*
-* Purpose: Detects whether an animation is repeating
-*
-* Return: True if repeating
-\****************************************************************************/
-
-extern type8 ms_anim_is_repeating(void);
-
-/****************************************************************************\
-* Magnetic Windows hint support
-*
-* The windowed Magnetic Scolls games included online hints. To add support
-* for the hints to your magnetic port, you should implement the ms_showhints
-* function. It retrieves a pointer to an array of ms_hint structs
-* The root element is always hints[0]. The elcount determines the number
-* of items in this topic. You probably want to display those in some kind
-* of list interface. The content pointer points to the actual description of
-* the items, separated by '\0' terminators. The nodetype is 1 if the items are
-* "folders" and 2 if the items are hints. Hints should be displayed one after
-* another. For "folder" items, the links array holds the index of the hint in
-* the array which is to be displayed on selection. One hint block has exactly
-* one type. The parent element determines the "back" target.
-\****************************************************************************/
-
-/****************************************************************************\
-* Function: ms_showhints
-* Purpose: Show the player a hint
-*
-* Parameters:   ms_hint* hints          pointer to array of ms_hint structs
-*
-* Return: 0 on error, 1 on success
-\****************************************************************************/
-
-extern type8 ms_showhints(struct ms_hint *hints);
-
-/****************************************************************************\
-* Magnetic Windows sound support
-*
-* Wonderland contains music scores that are played when entering specific
-* locations in the game. The music data are actually MIDI events and can be
-* played through normal MIDI devices. The original game plays the MIDI score
-* until the end, even if the location is changed while playing. The playback
-* tempo is not included with the MIDI data. The ms_sndextract function
-* returns a recommended tempo, however depending on the MIDI implementation
-* and operating system, you might need to adapt it.
-\****************************************************************************/
-
-/****************************************************************************\
-* Function: ms_playmusic
-*
-* Purpose: Plays (or stops playing) a MIDI music score.
-*
-* Parameter:    type8 * midi_data       the MIDI data to play
-*               type32  length          the length of the MIDI data
-*               type16  tempo           the suggested tempo for playing
-*
-* Note: If midi_data is NULL, all that should happen is that any currently
-* playing music is stopped.
-* Note: The data returned contain a complete MIDI file header, so if pure
-*       memory processing is not applicable you can write the data to a
-*       temporary file and use external players or libraries.
-\****************************************************************************/
-
-extern void ms_playmusic(type8 *midi_data, type32 length, type16 tempo);
-
-/****************************************************************************\
-* Function: ms_init
-*
-* Purpose: Loads the interpreter with a game
-*
-* Parameters:   type8s* name            filename of story file
-*               type8s* gfxname         filename of graphics file (optional)
-*               type8s* hntname         filename of hints file (optional)
-*               type8s* sndname         filename of music file (optional)
-*
-* Return:       0 = failure
-*               1 = success (without graphics or graphics failed)
-*               2 = success (with graphics)
-*
-* Note: You must call this function before starting the ms_rungame loop
-\****************************************************************************/
-
-extern type8 ms_init(const char *name, const char *gfxname, const char *hntname, const char *sndname);
-
-/****************************************************************************\
-* Function: ms_rungame
-*
-* Purpose: Executes an interpreter instruction
-*
-* Return: True if successful
-*
-* Note: You must call this function in a loop like this:
-*       while (running) {running=ms_rungame();}
-\****************************************************************************/
-
-extern type8 ms_rungame(void);
-
-/****************************************************************************\
-* Function: ms_freemen
-*
-* Purpose: Frees all allocated ressources
-\****************************************************************************/
-
-extern void ms_freemem(void);
-
-/****************************************************************************\
-* Function: ms_seed
-*
-* Purpose: Initializes the interpreter's random number generator with
-*          the given seed
-*
-* Parameter:    type32  seed    seed
-\****************************************************************************/
-
-extern void ms_seed(type32 seed);
-
-/****************************************************************************\
-* Function: ms_is_running
-*
-* Purpose: Detects if game is running
-*
-* Return: True, if game is currently running
-\****************************************************************************/
-
-extern type8 ms_is_running(void);
-
-/****************************************************************************\
-* Function: ms_is_magwin
-*
-* Purpose: Detects Magnetic Windows games (Wonderland, Collection)
-*
-* Return: True, if Magnetic Windows game
-\****************************************************************************/
-
-extern type8 ms_is_magwin(void);
-
-/****************************************************************************\
-* Function: ms_stop
-*
-* Purpose: Stops further processing of opcodes
-\****************************************************************************/
-
-extern void ms_stop(void);
-
-/****************************************************************************\
-* Function: ms_status
-*
-* Purpose: Dumps interperetr state to stderr, ie. registers
-\****************************************************************************/
-
-extern void ms_status(void);
-
-/****************************************************************************\
-* Function: ms_count
-*
-* Purpose: Returns the number of executed intructions
-*
-* Return:  Instruction count
-\****************************************************************************/
-
-extern type32 ms_count(void);
-
-extern void write(const char *fmt, ...);
-
-extern void writeChar(char c);
-
-extern void gms_main();
-
-} // End of namespace Magnetic
-} // End of namespace Glk
-
-#endif
diff --git a/engines/glk/magnetic/detection.cpp b/engines/glk/magnetic/detection.cpp
index cc1714e..54a0e7b 100644
--- a/engines/glk/magnetic/detection.cpp
+++ b/engines/glk/magnetic/detection.cpp
@@ -75,7 +75,7 @@ bool MagneticMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames
 		gameFile.close();
 
 		// Check for known games
-		const MagneticGameDescription *p = MAGNETIC_GAMES;
+		const GlkDetectionEntry *p = MAGNETIC_GAMES;
 		while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize))
 			++p;
 
@@ -99,5 +99,16 @@ void MagneticMetaEngine::detectClashes(Common::StringMap &map) {
 	}
 }
 
+const gms_game_table_t *gms_gameid_lookup_game(uint32 undo_size, uint32 undo_pc) {
+	const gms_game_table_t *game;
+
+	for (game = GMS_GAME_TABLE; game->name; game++) {
+		if (game->undo_size == undo_size && game->undo_pc == undo_pc)
+			break;
+	}
+
+	return game->name ? game : nullptr;
+}
+
 } // End of namespace Magnetic
 } // End of namespace Glk
diff --git a/engines/glk/magnetic/detection.h b/engines/glk/magnetic/detection.h
index 28ea70f..80a8b7f 100644
--- a/engines/glk/magnetic/detection.h
+++ b/engines/glk/magnetic/detection.h
@@ -32,6 +32,18 @@ namespace Glk {
 namespace Magnetic {
 
 /**
+ * The following game database is built from Generic/games.txt, and is used
+ * to identify the game being run.  Magnetic Scrolls games don't generally
+ * supply a status line, so this data can be used instead.
+ */
+struct gms_game_table_t {
+	const uint32 undo_size;   ///< Header word at offset 0x22
+	const uint32 undo_pc;     ///< Header word at offset 0x26
+	const char *const name;   ///< Game title and platform
+};
+typedef const gms_game_table_t *gms_game_tableref_t;
+
+/**
  * Meta engine for Magnetic interpreter
  */
 class MagneticMetaEngine {
@@ -57,6 +69,12 @@ public:
 	static void detectClashes(Common::StringMap &map);
 };
 
+/**
+ * Look up and return the game table entry given a game's undo size and
+ * undo pc values.  Returns the entry, or NULL if not found.
+ */
+extern const gms_game_table_t *gms_gameid_lookup_game(uint32 undo_size, uint32 undo_pc);
+
 } // End of namespace Magnetic
 } // End of namespace Glk
 
diff --git a/engines/glk/magnetic/detection_tables.h b/engines/glk/magnetic/detection_tables.h
index 7476c34..c0952d8 100644
--- a/engines/glk/magnetic/detection_tables.h
+++ b/engines/glk/magnetic/detection_tables.h
@@ -27,17 +27,6 @@
 namespace Glk {
 namespace Magnetic {
 
-/**
- * Game description
- */
-struct MagneticGameDescription {
-	const char *const _gameId;
-	const char *const _extra;
-	const char *const _md5;
-	size_t _filesize;
-	Common::Language _language;
-};
-
 const PlainGameDescriptor MAGNETIC_GAME_LIST[] = {
 	{ "magnetic", "Magnetic Scrolls Game" },
 
@@ -51,21 +40,62 @@ const PlainGameDescriptor MAGNETIC_GAME_LIST[] = {
 	{ nullptr, nullptr }
 };
 
-#define ENTRY0(ID, MD5, FILESIZE) { ID, nullptr, MD5, FILESIZE, Common::EN_ANY }
-#define ENTRY1(ID, EXTRA, MD5, FILESIZE) { ID, EXTRA, MD5, FILESIZE, Common::EN_ANY }
-#define TABLE_END_MARKER { nullptr, nullptr, nullptr, 0, Common::EN_ANY }
+const GlkDetectionEntry MAGNETIC_GAMES[] = {
+	DT_ENTRY0("corruption", "313880cbe0f15bfa259ebaf228b4d0e9", 167466),
+	DT_ENTRY1("corruption", "Collection", "6fe35b357fa0311450d3a9c809e60ba8", 177185),
+	DT_ENTRY0("fish", "2efb8118f4cb9a36bb54646ce41a950e", 162858),
+	DT_ENTRY1("fish", "Collection", "cfe333306597d36c8aa3fc64f6be94ba", 172517),
+	DT_ENTRY0("guild", "bab78740d39ee5e058faf4912fdbf33d", 130858),
+	DT_ENTRY1("guild", "Collection", "36af907a4ec9db909148f308287586f1", 141766),
+	DT_ENTRY0("myth", "9c2a5272a9c0b1e173401ba4df32567a", 99370),
+	DT_ENTRY0("pawn", "4a7847980f9e942acd7aa51ea12a6586", 103466),
+	DT_ENTRY0("wonderland", "2cea8fccf42d570be8836416c2802613", 183916),
+	
+	DT_END_MARKER
+};
+
+const gms_game_table_t GMS_GAME_TABLE[] = {
+	{0x2100, 0x427e, "Corruption v1.11 (Amstrad CPC)"},
+	{0x2100, 0x43a0, "Corruption v1.11 (Archimedes)"},
+	{0x2100, 0x43a0, "Corruption v1.11 (DOS)"},
+	{0x2100, 0x4336, "Corruption v1.11 (Commodore 64)"},
+	{0x2100, 0x4222, "Corruption v1.11 (Spectrum +3)"},
+	{0x2100, 0x4350, "Corruption v1.12 (Archimedes)"},
+	{0x2500, 0x6624, "Corruption v1.12 (DOS, Magnetic Windows)"},
+
+	{0x2300, 0x3fa0, "Fish v1.02 (DOS)"},
+	{0x2400, 0x4364, "Fish v1.03 (Spectrum +3)"},
+	{0x2300, 0x3f72, "Fish v1.07 (Commodore 64)"},
+	{0x2200, 0x3f9c, "Fish v1.08 (Archimedes)"},
+	{0x2a00, 0x583a, "Fish v1.10 (DOS, Magnetic Windows)"},
+
+	{0x5000, 0x6c30, "Guild v1.0 (Amstrad CPC)"},
+	{0x5000, 0x6cac, "Guild v1.0 (Commodore 64)"},
+	{0x5000, 0x6d5c, "Guild v1.1 (DOS)"},
+	{0x3300, 0x698a, "Guild v1.3 (Archimedes)"},
+	{0x3200, 0x6772, "Guild v1.3 (Spectrum +3)"},
+	{0x3400, 0x6528, "Guild v1.3 (DOS, Magnetic Windows)"},
+
+	{0x2b00, 0x488c, "Jinxter v1.05 (Commodore 64)"},
+	{0x2c00, 0x4a08, "Jinxter v1.05 (DOS)"},
+	{0x2c00, 0x487a, "Jinxter v1.05 (Spectrum +3)"},
+	{0x2c00, 0x4a56, "Jinxter v1.10 (DOS)"},
+	{0x2b00, 0x4924, "Jinxter v1.22 (Amstrad CPC)"},
+	{0x2c00, 0x4960, "Jinxter v1.30 (Archimedes)"},
+
+	{0x1600, 0x3940, "Myth v1.0 (Commodore 64)"},
+	{0x1500, 0x3a0a, "Myth v1.0 (DOS)"},
+
+	{0x3600, 0x42cc, "Pawn v2.3 (Amstrad CPC)"},
+	{0x3600, 0x4420, "Pawn v2.3 (Archimedes)"},
+	{0x3600, 0x3fb0, "Pawn v2.3 (Commodore 64)"},
+	{0x3600, 0x4420, "Pawn v2.3 (DOS)"},
+	{0x3900, 0x42e4, "Pawn v2.3 (Spectrum 128)"},
+	{0x3900, 0x42f4, "Pawn v2.4 (Spectrum +3)"},
 
-const MagneticGameDescription MAGNETIC_GAMES[] = {
-	ENTRY0("corruption", "313880cbe0f15bfa259ebaf228b4d0e9", 167466),
-	ENTRY1("corruption", "Collection", "6fe35b357fa0311450d3a9c809e60ba8", 177185),
-	ENTRY0("fish", "2efb8118f4cb9a36bb54646ce41a950e", 162858),
-	ENTRY1("fish", "Collection", "cfe333306597d36c8aa3fc64f6be94ba", 172517),
-	ENTRY0("guild", "bab78740d39ee5e058faf4912fdbf33d", 130858),
-	ENTRY1("guild", "Collection", "36af907a4ec9db909148f308287586f1", 141766),
-	ENTRY0("myth", "9c2a5272a9c0b1e173401ba4df32567a", 99370),
-	ENTRY0("pawn", "4a7847980f9e942acd7aa51ea12a6586", 103466),
-	ENTRY0("wonderland", "2cea8fccf42d570be8836416c2802613", 183916),
-	TABLE_END_MARKER
+	{0x3900, 0x75f2, "Wonderland v1.21 (DOS, Magnetic Windows)"},
+	{0x3900, 0x75f8, "Wonderland v1.27 (Archimedes)"},
+	{0, 0, NULL}
 };
 
 } // End of namespace Magnetic
diff --git a/engines/glk/magnetic/emu.cpp b/engines/glk/magnetic/emu.cpp
index d91b7e6..6dd0b11 100644
--- a/engines/glk/magnetic/emu.cpp
+++ b/engines/glk/magnetic/emu.cpp
@@ -20,57 +20,20 @@
  *
  */
 
-#include "glk/magnetic/defs.h"
+#include "glk/magnetic/magnetic_defs.h"
 #include "glk/magnetic/magnetic.h"
 #include "common/file.h"
+#include "common/textconsole.h"
 
 namespace Glk {
 namespace Magnetic {
 
-type32 dreg[8], areg[8], i_count, string_size, rseed = 0, pc, arg1i, mem_size;
-type16 properties, fl_sub, fl_tab, fl_size, fp_tab, fp_size;
-type8 zflag, nflag, cflag, vflag, byte1, byte2, regnr, admode, opsize;
-type8 *arg1, *arg2, is_reversible, running = 0, tmparg[4] = {0, 0, 0, 0};
-type8 lastchar = 0, version = 0, sd = 0;
-type8 *decode_table, *restart = 0, *code = 0, *string = 0, *string2 = 0;
-type8 *string3 = 0, *dict = 0;
-type8 quick_flag = 0, gfx_ver = 0, *gfx_buf = nullptr, *gfx_data = 0;
-type8 *gfx2_hdr = 0, *gfx2_buf = nullptr;
-const char *gfx2_name = nullptr;
-type16 gfx2_hsize = 0;
-Common::File *gfx_fp = nullptr;
-type8 *snd_buf = nullptr, *snd_hdr = nullptr;
-type16 snd_hsize = 0;
-Common::File *snd_fp = nullptr;
-
+const char *const no_hints = "[Hints are not available.]\n";
+const char *const not_supported = "[This function is not supported.]\n";
 const char *const undo_ok = "\n[Previous turn undone.]";
 const char *const undo_fail = "\n[You can't \"undo\" what hasn't been done!]";
-type32 undo_regs[2][18], undo_pc, undo_size;
-type8 *undo[2] = {0, 0}, undo_stat[2] = {0, 0};
-type16 gfxtable = 0, table_dist = 0;
-type16 v4_id = 0, next_table = 1;
-
-#ifndef NO_ANIMATION
-
-struct picture anim_frame_table[MAX_ANIMS];
-type16 pos_table_size = 0;
-type16 pos_table_count[MAX_POSITIONS];
-struct ms_position pos_table[MAX_POSITIONS][MAX_ANIMS];
-type8 *command_table = 0;
-type16s command_index = -1;
-struct lookup anim_table[MAX_POSITIONS];
-type16s pos_table_index = -1;
-type16s pos_table_max = -1;
-struct ms_position pos_array[MAX_FRAMES];
-type8 anim_repeat = 0;
 
-#endif
-
-/* Hint support */
-struct ms_hint *hints = 0;
-type8 *hint_contents = 0;
-const char *const no_hints = "[Hints are not available.]\n";
-const char *const not_supported = "[This function is not supported.]\n";
+#define ms_fatal error
 
 #if defined(LOGEMU) || defined(LOGGFX) || defined(LOGHNT)
 FILE *dbg_log;
@@ -79,14 +42,6 @@ FILE *dbg_log;
 #endif
 #endif
 
-/* prototypes */
-type32 read_reg(int, int);
-void write_reg(int, int, type32);
-
-#define MAX_STRING_SIZE  0xFF00
-#define MAX_PICTURE_SIZE 0xC800
-#define MAX_MUSIC_SIZE   0x4E20
-
 #ifdef LOGEMU
 void out(char *format, ...) {
 	va_list a;
@@ -108,7 +63,7 @@ void out2(char *format, ...) {
 #endif
 
 /* Convert virtual pointer to effective pointer */
-type8 *effective(type32 ptr) {
+type8 *Magnetic::effective(type32 ptr) {
 	if ((version < 4) && (mem_size == 0x10000))
 		return &(code[ptr & 0xffff]);
 	if (ptr >= mem_size) {
@@ -119,16 +74,7 @@ type8 *effective(type32 ptr) {
 	return &(code[ptr]);
 }
 
-type32 read_l(type8 *ptr) {
-
-	return (type32)((type32) ptr[0] << 24 | (type32) ptr[1] << 16 | (type32) ptr[2] << 8 | (type32) ptr[3]);
-}
-
-type16 read_w(type8 *ptr) {
-	return (type16)(ptr[0] << 8 | ptr[1]);
-}
-
-void write_l(type8 *ptr, type32 val) {
+void Magnetic::write_l(type8 *ptr, type32 val) {
 	ptr[3] = (type8) val;
 	val >>= 8;
 	ptr[2] = (type8) val;
@@ -138,32 +84,18 @@ void write_l(type8 *ptr, type32 val) {
 	ptr[0] = (type8) val;
 }
 
-void write_w(type8 *ptr, type16 val) {
+void Magnetic::write_w(type8 *ptr, type16 val) {
 	ptr[1] = (type8) val;
 	val >>= 8;
 	ptr[0] = (type8) val;
 }
 
-type32 read_l2(type8 *ptr) {
-	return ((type32) ptr[1] << 24 | (type32) ptr[0] << 16 | (type32) ptr[3] << 8 | (type32) ptr[2]);
-}
-
-type16 read_w2(type8 *ptr) {
-	return (type16)(ptr[1] << 8 | ptr[0]);
-}
-
-/* Standard rand - for equal cross-platform behaviour */
-
-void ms_seed(type32 seed) {
-	rseed = seed;
-}
-
-type32 rand_emu(void) {
+type32 Magnetic::rand_emu() {
 	rseed = 1103515245L * rseed + 12345L;
 	return rseed & 0x7fffffffL;
 }
 
-void ms_freemem(void) {
+void Magnetic::ms_freemem() {
 	if (code)
 		free(code);
 	if (string)
@@ -219,19 +151,7 @@ void ms_freemem(void) {
 	snd_buf = nullptr;
 }
 
-type8 ms_is_running(void) {
-	return running;
-}
-
-type8 ms_is_magwin(void) {
-	return (version == 4) ? 1 : 0;
-}
-
-void ms_stop(void) {
-	running = 0;
-}
-
-type8 init_gfx1(type8 *header) {
+type8 Magnetic::init_gfx1(type8 *header) {
 #ifdef SAVEMEM
 	type32 i;
 #endif
@@ -278,7 +198,7 @@ type8 init_gfx1(type8 *header) {
 	return 2;
 }
 
-type8 init_gfx2(type8 *header) {
+type8 Magnetic::init_gfx2(type8 *header) {
 	if (!(gfx_buf = (type8 *)malloc(MAX_PICTURE_SIZE))) {
 		delete gfx_fp;
 		gfx_fp = nullptr;
@@ -309,7 +229,7 @@ type8 init_gfx2(type8 *header) {
 	return 2;
 }
 
-type8 init_snd(type8 *header) {
+type8 Magnetic::init_snd(type8 *header) {
 	if (!(snd_buf = (type8 *)malloc(MAX_MUSIC_SIZE))) {
 		delete snd_fp;
 		snd_fp = nullptr;
@@ -339,9 +259,7 @@ type8 init_snd(type8 *header) {
 	return 2;
 }
 
-/* zero all registers and flags and load the game */
-
-type8 ms_init(const char *name, const char *gfxname, const char *hntname, const char *sndname) {
+type8 Magnetic::ms_init(const char *name, const char *gfxname, const char *hntname, const char *sndname) {
 	Common::File fp;
 	type8 header[42], header2[8], header3[4];
 	type32 i, dict_size, string2_size, code_size, dec;
@@ -601,7 +519,7 @@ type8 ms_init(const char *name, const char *gfxname, const char *hntname, const
 	return 1;
 }
 
-type8 is_blank(type16 line, type16 width) {
+type8 Magnetic::is_blank(type16 line, type16 width) {
 	type32s i;
 
 	for (i = line * width; i < (line + 1) * width; i++)
@@ -610,8 +528,8 @@ type8 is_blank(type16 line, type16 width) {
 	return 1;
 }
 
-type8 *ms_extract1(type8 pic, type16 *w, type16 *h, type16 *pal) {
-	type8 *decodeTable, *data, bit, val, *buffer;
+type8 *Magnetic::ms_extract1(type8 pic, type16 *w, type16 *h, type16 *pal) {
+	type8 *decodeTable, *data, bit, val, *buf;
 	type16 tablesize, count;
 	type32 i, j, upsize, offset;
 
@@ -622,22 +540,22 @@ type8 *ms_extract1(type8 pic, type16 *w, type16 *h, type16 *pal) {
 	if (fseek(gfx_fp, offset, SEEK_SET) < 0)
 		return 0;
 	datasize = read_l(gfx_data + 4 * (pic + 1)) - offset;
-	if (!(buffer = (type8 *)malloc(datasize)))
+	if (!(buf = (type8 *)malloc(datasize)))
 		return 0;
-	if (fp.read(buffer, 1, datasize, gfx_fp) != datasize)
+	if (fp.read(buf, 1, datasize, gfx_fp) != datasize)
 		return 0;
 #else
-	buffer = gfx_data + offset - 8;
+	buf = gfx_data + offset - 8;
 #endif
 
 	for (i = 0; i < 16; i++)
-		pal[i] = read_w(buffer + 0x1c + 2 * i);
-	w[0] = (type16)(read_w(buffer + 4) - read_w(buffer + 2));
-	h[0] = read_w(buffer + 6);
+		pal[i] = read_w(buf + 0x1c + 2 * i);
+	w[0] = (type16)(read_w(buf + 4) - read_w(buf + 2));
+	h[0] = read_w(buf + 6);
 
-	tablesize = read_w(buffer + 0x3c);
-	//datasize = read_l(buffer + 0x3e);
-	decodeTable = buffer + 0x42;
+	tablesize = read_w(buf + 0x3c);
+	//datasize = read_l(buf + 0x3e);
+	decodeTable = buf + 0x42;
 	data = decodeTable + tablesize * 2 + 2;
 	upsize = h[0] * w[0];
 
@@ -667,14 +585,14 @@ type8 *ms_extract1(type8 pic, type16 *w, type16 *h, type16 *pal) {
 		gfx_buf[j] ^= gfx_buf[j - w[0]];
 
 #ifdef SAVEMEM
-	free(buffer);
+	free(buf);
 #endif
 	for (; h[0] > 0 && is_blank((type16)(h[0] - 1), w[0]); h[0]--);
 	for (i = 0; h[0] > 0 && is_blank((type16)i, w[0]); h[0]--, i++);
 	return gfx_buf + i * w[0];
 }
 
-type16s find_name_in_header(const char *name, type8 upper) {
+type16s Magnetic::find_name_in_header(const char *name, type8 upper) {
 	type16s header_pos = 0;
 	char pic_name[8];
 	type8 i;
@@ -696,7 +614,7 @@ type16s find_name_in_header(const char *name, type8 upper) {
 	return -1;
 }
 
-void extract_frame(struct picture *pic) {
+void Magnetic::extract_frame(struct picture *pic) {
 	type32 i, x, y, bit_x, mask, ywb, yw, value, values[4];
 
 	if (pic->width * pic->height > MAX_PICTURE_SIZE) {
@@ -727,7 +645,7 @@ void extract_frame(struct picture *pic) {
 	}
 }
 
-type8 *ms_extract2(const char *name, type16 *w, type16 *h, type16 *pal, type8 *is_anim) {
+type8 *Magnetic::ms_extract2(const char *name, type16 *w, type16 *h, type16 *pal, type8 *is_anim) {
 	struct picture main_pic;
 	type32 offset = 0, length = 0, i;
 	type16s header_pos = -1;
@@ -885,7 +803,7 @@ type8 *ms_extract2(const char *name, type16 *w, type16 *h, type16 *pal, type8 *i
 	return 0;
 }
 
-type8 *ms_extract(type32 pic, type16 *w, type16 *h, type16 *pal, type8 *is_anim) {
+type8 *Magnetic::ms_extract(type32 pic, type16 *w, type16 *h, type16 *pal, type8 *is_anim) {
 	if (is_anim)
 		*is_anim = 0;
 
@@ -902,7 +820,7 @@ type8 *ms_extract(type32 pic, type16 *w, type16 *h, type16 *pal, type8 *is_anim)
 	return 0;
 }
 
-type8 ms_animate(struct ms_position **positions, type16 *count) {
+type8 Magnetic::ms_animate(struct ms_position **positions, type16 *count) {
 #ifndef NO_ANIMATION
 	type8 got_anim = 0;
 	type16 i, j, ttable;
@@ -1054,7 +972,7 @@ type8 ms_animate(struct ms_position **positions, type16 *count) {
 #endif
 }
 
-type8 *ms_get_anim_frame(type16s number, type16 *width, type16 *height, type8 **mask) {
+type8 *Magnetic::ms_get_anim_frame(type16s number, type16 *width, type16 *height, type8 **mask) {
 #ifndef NO_ANIMATION
 	if (number >= 0) {
 		extract_frame(anim_frame_table + number);
@@ -1067,7 +985,7 @@ type8 *ms_get_anim_frame(type16s number, type16 *width, type16 *height, type8 **
 	return 0;
 }
 
-type8 ms_anim_is_repeating(void) {
+type8 Magnetic::ms_anim_is_repeating() const {
 #ifndef NO_ANIMATION
 	return anim_repeat;
 #else
@@ -1075,7 +993,7 @@ type8 ms_anim_is_repeating(void) {
 #endif
 }
 
-type16s find_name_in_sndheader(const char *name) {
+type16s Magnetic::find_name_in_sndheader(const char *name) {
 	type16s header_pos = 0;
 
 	while (header_pos < snd_hsize) {
@@ -1088,7 +1006,7 @@ type16s find_name_in_sndheader(const char *name) {
 	return -1;
 }
 
-type8 *sound_extract(const char *name, type32 *length, type16 *tempo) {
+type8 *Magnetic::sound_extract(const char *name, type32 *length, type16 *tempo) {
 	type32 offset = 0;
 	type16s header_pos = -1;
 
@@ -1113,7 +1031,7 @@ type8 *sound_extract(const char *name, type32 *length, type16 *tempo) {
 	return nullptr;
 }
 
-void save_undo(void) {
+void Magnetic::save_undo() {
 	type8 *tmp, i;
 	type32 tmp32;
 
@@ -1139,7 +1057,7 @@ void save_undo(void) {
 	undo_stat[1] = 1;
 }
 
-type8 ms_undo(void) {
+type8 Magnetic::ms_undo() {
 	type8 i;
 
 	ms_flush();
@@ -1158,7 +1076,7 @@ type8 ms_undo(void) {
 }
 
 #ifdef LOGEMU
-void log_status(void) {
+void Magnetic::log_status() {
 	int j;
 
 	fprintf(dbg_log, "\nD0:");
@@ -1172,7 +1090,7 @@ void log_status(void) {
 }
 #endif
 
-void ms_status(void) {
+void Magnetic::ms_status() {
 	int j;
 
 	Common::String s = "D0:";
@@ -1187,13 +1105,7 @@ void ms_status(void) {
 	warning("%s", s.c_str());
 }
 
-type32 ms_count(void) {
-	return i_count;
-}
-
-/* align register pointer for word/byte accesses */
-
-type8 *reg_align(type8 *ptr, type8 size) {
+type8 *Magnetic::reg_align(type8 *ptr, type8 size) {
 	if (size == 1)
 		ptr += 2;
 	if (size == 0)
@@ -1201,7 +1113,7 @@ type8 *reg_align(type8 *ptr, type8 size) {
 	return ptr;
 }
 
-type32 read_reg(int i, int s) {
+type32 Magnetic::read_reg(int i, int s) {
 	type8 *ptr;
 
 	if (i > 15) {
@@ -1223,7 +1135,7 @@ type32 read_reg(int i, int s) {
 	}
 }
 
-void write_reg(int i, int s, type32 val) {
+void Magnetic::write_reg(int i, int s, type32 val) {
 	type8 *ptr;
 
 	if (i > 15) {
@@ -1248,9 +1160,7 @@ void write_reg(int i, int s, type32 val) {
 	}
 }
 
-/* [35c4] */
-
-void char_out(type8 c) {
+void Magnetic::char_out(type8 c) {
 	static type8 big = 0, period = 0, pipe = 0;
 
 	if (c == 0xff) {
@@ -1327,18 +1237,13 @@ void char_out(type8 c) {
 	ms_putchar(c);
 }
 
-
-/* extract addressing mode information [1c6f] */
-
-void set_info(type8 b) {
+void Magnetic::set_info(type8 b) {
 	regnr = (type8)(b & 0x07);
 	admode = (type8)((b >> 3) & 0x07);
 	opsize = (type8)(b >> 6);
 }
 
-/* read a word and increase pc */
-
-void read_word(void) {
+void Magnetic::read_word() {
 	type8 *epc;
 
 	epc = effective(pc);
@@ -1347,9 +1252,7 @@ void read_word(void) {
 	pc += 2;
 }
 
-/* get addressing mode and set arg1 [1c84] */
-
-void set_arg1(void) {
+void Magnetic::set_arg1() {
 	type8 tmp[2], l1c;
 
 	is_reversible = 1;
@@ -1479,9 +1382,7 @@ void set_arg1(void) {
 		arg1 = effective(arg1i);
 }
 
-/* get addressing mode and set arg2 [1bc5] */
-
-void set_arg2_nosize(int use_dx, type8 b) {
+void Magnetic::set_arg2_nosize(int use_dx, type8 b) {
 	if (use_dx)
 		arg2 = (type8 *) dreg;
 	else
@@ -1489,14 +1390,12 @@ void set_arg2_nosize(int use_dx, type8 b) {
 	arg2 += (b & 0x0e) << 1;
 }
 
-void set_arg2(int use_dx, type8 b) {
+void Magnetic::set_arg2(int use_dx, type8 b) {
 	set_arg2_nosize(use_dx, b);
 	arg2 = reg_align(arg2, opsize);
 }
 
-/* [1b9e] */
-
-void swap_args(void) {
+void Magnetic::swap_args() {
 	type8 *tmp;
 
 	tmp = arg1;
@@ -1504,16 +1403,12 @@ void swap_args(void) {
 	arg2 = tmp;
 }
 
-/* [1cdc] */
-
-void push(type32 c) {
+void Magnetic::push(type32 c) {
 	write_reg(15, 2, read_reg(15, 2) - 4);
 	write_l(effective(read_reg(15, 2)), c);
 }
 
-/* [1cd1] */
-
-type32 pop(void) {
+type32 Magnetic::pop() {
 	type32 c;
 
 	c = read_l(effective(read_reg(15, 2)));
@@ -1521,9 +1416,7 @@ type32 pop(void) {
 	return c;
 }
 
-/* check addressing mode and get argument [2e85] */
-
-void get_arg(void) {
+void Magnetic::get_arg() {
 #ifdef LOGEMU
 	out(" %.4X", pc);
 #endif
@@ -1537,7 +1430,7 @@ void get_arg(void) {
 	set_arg1();
 }
 
-void set_flags(void) {
+void Magnetic::set_flags() {
 	type16 i;
 	type32 j;
 
@@ -1566,9 +1459,7 @@ void set_flags(void) {
 	}
 }
 
-/* [263a] */
-
-int condition(type8 b) {
+int Magnetic::condition(type8 b) {
 	switch (b & 0x0f) {
 	case 0:
 		return 0xff;
@@ -1604,9 +1495,7 @@ int condition(type8 b) {
 	return 0x00;
 }
 
-/* [26dc] */
-
-void branch(type8 b) {
+void Magnetic::branch(type8 b) {
 	if (b == 0)
 		pc += (type16s) read_w(effective(pc));
 	else
@@ -1616,9 +1505,7 @@ void branch(type8 b) {
 #endif
 }
 
-/* [2869] */
-
-void do_add(type8 adda) {
+void Magnetic::do_add(type8 adda) {
 	if (adda) {
 		if (opsize == 0)
 			write_l(arg1, read_l(arg1) + (type8s) arg2[0]);
@@ -1651,9 +1538,7 @@ void do_add(type8 adda) {
 	}
 }
 
-/* [2923] */
-
-void do_sub(type8 suba) {
+void Magnetic::do_sub(type8 suba) {
 	if (suba) {
 		if (opsize == 0)
 			write_l(arg1, read_l(arg1) - (type8s) arg2[0]);
@@ -1686,9 +1571,7 @@ void do_sub(type8 suba) {
 	}
 }
 
-/* [283b] */
-
-void do_eor(void) {
+void Magnetic::do_eor() {
 	if (opsize == 0)
 		arg1[0] ^= arg2[0];
 	if (opsize == 1)
@@ -1699,9 +1582,7 @@ void do_eor(void) {
 	set_flags();
 }
 
-/* [280d] */
-
-void do_and(void) {
+void Magnetic::do_and() {
 	if (opsize == 0)
 		arg1[0] &= arg2[0];
 	if (opsize == 1)
@@ -1712,9 +1593,7 @@ void do_and(void) {
 	set_flags();
 }
 
-/* [27df] */
-
-void do_or(void) {
+void Magnetic::do_or() {
 	if (opsize == 0)
 		arg1[0] |= arg2[0];
 	if (opsize == 1)
@@ -1725,9 +1604,7 @@ void do_or(void) {
 	set_flags();    /* [1c2b] */
 }
 
-/* [289f] */
-
-void do_cmp(void) {
+void Magnetic::do_cmp() {
 	type8 *tmp;
 
 	tmp = arg1;
@@ -1741,9 +1618,7 @@ void do_cmp(void) {
 	arg1 = tmp;
 }
 
-/* [2973] */
-
-void do_move(void) {
+void Magnetic::do_move() {
 
 	if (opsize == 0)
 		arg1[0] = arg2[0];
@@ -1758,7 +1633,7 @@ void do_move(void) {
 	}
 }
 
-type8 do_btst(type8 a) {
+type8 Magnetic::do_btst(type8 a) {
 	a &= admode ? 0x7 : 0x1f;
 	while (admode == 0 && a >= 8) {
 		a -= 8;
@@ -1770,9 +1645,7 @@ type8 do_btst(type8 a) {
 	return a;
 }
 
-/* bit operation entry point [307c] */
-
-void do_bop(type8 b, type8 a) {
+void Magnetic::do_bop(type8 b, type8 a) {
 #ifdef LOGEMU
 	out("bop (%.2x,%.2x) ", (int) b, (int) a);
 #endif
@@ -1802,7 +1675,7 @@ void do_bop(type8 b, type8 a) {
 	}
 }
 
-void check_btst(void) {
+void Magnetic::check_btst() {
 #ifdef LOGEMU
 	out("btst");
 #endif
@@ -1812,7 +1685,7 @@ void check_btst(void) {
 	do_bop(byte2, arg2[0]);
 }
 
-void check_lea(void) {
+void Magnetic::check_lea() {
 #ifdef LOGEMU
 	out("lea");
 #endif
@@ -1831,9 +1704,7 @@ void check_lea(void) {
 	}
 }
 
-/* [33cc] */
-
-void check_movem(void) {
+void Magnetic::check_movem() {
 	type8 l1c;
 
 #ifdef LOGEMU
@@ -1861,9 +1732,7 @@ void check_movem(void) {
 	}
 }
 
-/* [3357] */
-
-void check_movem2(void) {
+void Magnetic::check_movem2() {
 	type8 l1c;
 
 #ifdef LOGEMU
@@ -1891,10 +1760,7 @@ void check_movem2(void) {
 	}
 }
 
-/* [30e4] in Jinxter, ~540 lines of 6510 spaghetti-code */
-/* The mother of all bugs, but hey - no gotos used :-) */
-
-void dict_lookup(void) {
+void Magnetic::dict_lookup() {
 	type16 dtab, doff, output, output_bak, bank, word, output2;
 	type16 tmp16, i, obj_adj, adjlist, adjlist_bak;
 	type8 c, c2, c3, flag, matchlen, longest, flag2;
@@ -2066,9 +1932,7 @@ void dict_lookup(void) {
 	write_reg(8 + 6, 1, read_reg(8 + 5, 1) + 1);
 }
 
-/* A0=findproperties(D0) [2b86], properties_ptr=[2b78] A0FE */
-
-void do_findprop(void) {
+void Magnetic::do_findprop() {
 	type16 tmp;
 
 	if ((version > 2) && ((read_reg(0, 1) & 0x3fff) > fp_size)) {
@@ -2086,7 +1950,7 @@ void do_findprop(void) {
 	write_reg(8 + 0, 2, tmp * 14 + properties);
 }
 
-void write_string(void) {
+void Magnetic::write_string() {
 	static type32 offset_bak;
 	static type8 mask_bak;
 	type8 c, b, mask;
@@ -2142,7 +2006,7 @@ void write_string(void) {
 	}
 }
 
-void output_number(type16 number) {
+void Magnetic::output_number(type16 number) {
 	type16 tens = number / 10;
 
 	if (tens > 0)
@@ -2151,7 +2015,7 @@ void output_number(type16 number) {
 	ms_putchar('0' + number);
 }
 
-type16 output_text(const char *text) {
+type16 Magnetic::output_text(const char *text) {
 	type16 i;
 
 	for (i = 0; text[i] != 0; i++)
@@ -2159,7 +2023,7 @@ type16 output_text(const char *text) {
 	return i;
 }
 
-type16s hint_input(void) {
+type16s Magnetic::hint_input() {
 	type8 c1, c2, c3;
 
 	output_text(">>");
@@ -2208,7 +2072,7 @@ type16s hint_input(void) {
 	return 0;
 }
 
-type16 show_hints_text(ms_hint *hintsData, type16 index) {
+type16 Magnetic::show_hints_text(ms_hint *hintsData, type16 index) {
 	type16 i = 0, j = 0;
 	type16s input;
 	ms_hint *hint = hintsData + index;
@@ -2282,7 +2146,7 @@ type16 show_hints_text(ms_hint *hintsData, type16 index) {
 	return 0;
 }
 
-void do_line_a(void) {
+void Magnetic::do_line_a() {
 	type8 l1c;
 	char *str;
 	type16 ptr, ptr2, tmp16, dtype;
@@ -2656,9 +2520,7 @@ void do_line_a(void) {
 		}
 }
 
-/* emulate an instruction [1b7e] */
-
-type8 ms_rungame(void) {
+type8 Magnetic::ms_rungame() {
 	type8 l1c;
 	type16 ptr;
 	type32 tmp32;
diff --git a/engines/glk/magnetic/glk.cpp b/engines/glk/magnetic/glk.cpp
index 1801ef9..fec8691 100644
--- a/engines/glk/magnetic/glk.cpp
+++ b/engines/glk/magnetic/glk.cpp
@@ -20,157 +20,253 @@
  *
  */
 
-#include "glk/magnetic/defs.h"
+#include "glk/magnetic/magnetic_defs.h"
 #include "glk/magnetic/magnetic.h"
 
 namespace Glk {
 namespace Magnetic {
 
-/*
- * True and false definitions -- usually defined in glkstart.h, but we need
- * them early, so we'll define them here too.  We also need NULL, but that's
- * normally from stdio.h or one of it's cousins.
- */
-#ifndef FALSE
-# define FALSE false
-#endif
-#ifndef TRUE
-# define TRUE true
-#endif
+const gms_command_t Magnetic::GMS_COMMAND_TABLE[14] = {
+	{ &Magnetic::gms_command_summary,        "summary",        false, false },
+	{ &Magnetic::gms_command_undo,           "undo",           false, true },
+	{ &Magnetic::gms_command_script,         "script",         true,  false },
+	{ &Magnetic::gms_command_inputlog,       "inputlog",       true,  false },
+	{ &Magnetic::gms_command_readlog,        "readlog",        true,  false },
+	{ &Magnetic::gms_command_abbreviations,  "abbreviations",  true,  false },
+	{ &Magnetic::gms_command_graphics,       "graphics",       true,  false },
+	{ &Magnetic::gms_command_gamma,          "gamma",          true,  false },
+	{ &Magnetic::gms_command_animations,     "animations",     true,  false },
+	{ &Magnetic::gms_command_prompts,        "prompts",        true,  false },
+	{ &Magnetic::gms_command_version,        "version",        false, false },
+	{ &Magnetic::gms_command_commands,       "commands",       true,  false },
+	{ &Magnetic::gms_command_help,           "help",           true,  false },
+
+	{ nullptr, nullptr, false, false}
+};
+
+
 
-#define BYTE_MAX 255
-#define CHAR_BIT 8
-#define UINT16_MAX 0xffff
-#define INT32_MAX 0x7fffffff
+static gms_gamma_t GMS_GAMMA_TABLE[] = {
+	{ "0.90", { 0,  29,  63,  99, 137, 175, 215, 255 }, true },
+	{ "0.95", { 0,  33,  68, 105, 141, 179, 217, 255 }, true },
+	{ "1.00", { 0,  36,  73, 109, 146, 182, 219, 255 }, false },
+	{ "1.05", { 0,  40,  77, 114, 150, 185, 220, 255 }, true },
+	{ "1.10", { 0,  43,  82, 118, 153, 188, 222, 255 }, true },
+	{ "1.15", { 0,  47,  86, 122, 157, 190, 223, 255 }, true },
+	{ "1.20", { 0,  50,  90, 126, 160, 193, 224, 255 }, true },
+	{ "1.25", { 0,  54,  94, 129, 163, 195, 225, 255 }, true },
+	{ "1.30", { 0,  57,  97, 133, 166, 197, 226, 255 }, true },
+	{ "1.35", { 0,  60, 101, 136, 168, 199, 227, 255 }, true },
+	{ "1.40", { 0,  64, 104, 139, 171, 201, 228, 255 }, true },
+	{ "1.45", { 0,  67, 107, 142, 173, 202, 229, 255 }, true },
+	{ "1.50", { 0,  70, 111, 145, 176, 204, 230, 255 }, true },
+	{ "1.55", { 0,  73, 114, 148, 178, 205, 231, 255 }, true },
+	{ "1.60", { 0,  76, 117, 150, 180, 207, 232, 255 }, true },
+	{ "1.65", { 0,  78, 119, 153, 182, 208, 232, 255 }, true },
+	{ "1.70", { 0,  81, 122, 155, 183, 209, 233, 255 }, true },
+	{ "1.75", { 0,  84, 125, 157, 185, 210, 233, 255 }, true },
+	{ "1.80", { 0,  87, 127, 159, 187, 212, 234, 255 }, true },
+	{ "1.85", { 0,  89, 130, 161, 188, 213, 235, 255 }, true },
+	{ "1.90", { 0,  92, 132, 163, 190, 214, 235, 255 }, true },
+	{ "1.95", { 0,  94, 134, 165, 191, 215, 236, 255 }, true },
+	{ "2.00", { 0,  96, 136, 167, 193, 216, 236, 255 }, true },
+	{ "2.05", { 0,  99, 138, 169, 194, 216, 237, 255 }, true },
+	{ "2.10", { 0, 101, 140, 170, 195, 217, 237, 255 }, true },
+	{ "2.15", { 0, 103, 142, 172, 197, 218, 237, 255 }, true },
+	{ "2.20", { 0, 105, 144, 173, 198, 219, 238, 255 }, true },
+	{ "2.25", { 0, 107, 146, 175, 199, 220, 238, 255 }, true },
+	{ "2.30", { 0, 109, 148, 176, 200, 220, 238, 255 }, true },
+	{ "2.35", { 0, 111, 150, 178, 201, 221, 239, 255 }, true },
+	{ "2.40", { 0, 113, 151, 179, 202, 222, 239, 255 }, true },
+	{ "2.45", { 0, 115, 153, 180, 203, 222, 239, 255 }, true },
+	{ "2.50", { 0, 117, 154, 182, 204, 223, 240, 255 }, true },
+	{ "2.55", { 0, 119, 156, 183, 205, 223, 240, 255 }, true },
+	{ "2.60", { 0, 121, 158, 184, 206, 224, 240, 255 }, true },
+	{ "2.65", { 0, 122, 159, 185, 206, 225, 241, 255 }, true },
+	{ "2.70", { 0, 124, 160, 186, 207, 225, 241, 255 }, true },
+	{ NULL,   { 0,   0,   0,   0,   0,   0,   0,   0 }, false }
+};
+
+static gms_abbreviation_t GMS_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}
+};
 
 /*---------------------------------------------------------------------*/
-/*  Module variables, miscellaneous other stuff                        */
+/*  Module constants                                                   */
 /*---------------------------------------------------------------------*/
 
+/* CRC table initialization polynomial. */
+static const glui32 GMS_CRC_POLYNOMIAL = 0xedb88320;
+
 /* Glk Magnetic Scrolls port version number. */
 static const glui32 GMS_PORT_VERSION = 0x00010601;
 
+/* Magnetic Scrolls standard input prompt string. */
+static const char *const GMS_INPUT_PROMPT = ">";
+
 /*
- * We use a maximum of five Glk windows, one for status, one for pictures,
- * two for hints, and one for everything else.  The status and pictures
- * windows may be NULL, depending on user selections and the capabilities
- * of the Glk library.  The hints windows will normally be NULL, except
- * when in the hints subsystem.
+ * Weighting values for calculating the luminance of a color.  There are
+ * two commonly used sets of values for these -- 299,587,114, taken from
+ * NTSC (Never The Same Color) 1953 standards, and 212,716,72, which is the
+ * set that modern CRTs tend to match.  The NTSC ones seem to give the best
+ * subjective results.
  */
-static winid_t gms_main_window = NULL,
-               gms_status_window = NULL,
-               gms_graphics_window = NULL,
-               gms_hint_menu_window = NULL,
-               gms_hint_text_window = NULL;
+static const gms_rgb_t GMS_LUMINANCE_WEIGHTS = { 299, 587, 114 };
 
 /*
- * Transcript stream and input log.  These are NULL if there is no current
- * collection of these strings.
+ * 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 strid_t gms_transcript_stream = NULL,
-               gms_inputlog_stream = NULL;
+static const int GMS_REPAINT_LIMIT = 256;
 
-/* Input read log stream, for reading back an input log. */
-static strid_t gms_readlog_stream = NULL;
+/*
+ * 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 GMS_GRAPHICS_TIMEOUT = 50;
 
-/* Note about whether graphics is possible, or not. */
-static int gms_graphics_possible = TRUE;
+/*
+ * Count of timeouts to wait in between animation paints, and to wait on
+ * repaint request.  Waiting for 2 timeouts of around 50ms, gets us to the
+ * 100ms recommended animation frame rate.  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 GMS_GRAPHICS_ANIMATION_WAIT = 2,
+GMS_GRAPHICS_REPAINT_WAIT = 10;
 
-/* Magnetic Scrolls standard input prompt string. */
-static const char *const GMS_INPUT_PROMPT = ">";
+/* Pixel size multiplier for image size scaling. */
+static const int GMS_GRAPHICS_PIXEL = 2;
 
-/* Forward declaration of event wait function. */
-static void gms_event_wait(glui32 wait_type, event_t *event);
+/* Proportion of the display to use for graphics. */
+static const glui32 GMS_GRAPHICS_PROPORTION = 60;
 
+/*
+ * 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 GMS_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
+	GMS_GRAPHICS_BORDER_COLOR = 0x00000000;
+static const int GMS_GRAPHICS_BORDER = 1,
+	GMS_GRAPHICS_SHADING = 2,
+	GMS_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 GMS_GRAPHICS_UNUSED_PIXEL = 0xff;
+
+/* Default width used for non-windowing Glk status lines. */
+static const int GMS_DEFAULT_STATUS_WIDTH = 74;
+
+/* Success and fail return codes from hint functions. */
+static const type8 GMS_HINT_SUCCESS = 1,
+GMS_HINT_ERROR = 0;
+
+/* Default window sizes for non-windowing Glk libraries. */
+static const glui32 GMS_HINT_DEFAULT_WIDTH = 72,
+GMS_HINT_DEFAULT_HEIGHT = 25;
+
+/*
+ * Special hint nodes indicating the root hint node, and a value to signal
+ * quit from hints subsystem.
+ */
+static const type16 GMS_HINT_ROOT_NODE = 0,
+GMS_HINTS_DONE = UINT16_MAX;
+
+/* Generic hint topic for the root hints node. */
+static const char *const GMS_GENERIC_TOPIC = "Hints Menu";
 
 /*---------------------------------------------------------------------*/
 /*  Glk port utility functions                                         */
 /*---------------------------------------------------------------------*/
 
-/*
- * gms_fatal()
- *
- * Fatal error handler.  The function returns, expecting the caller to
- * abort() or otherwise handle the error.
- */
-static void gms_fatal(const char *string) {
+void Magnetic::gms_fatal(const char *str) {
 	/*
 	 * If the failure happens too early for us to have a window, print
 	 * the message to stderr.
 	 */
 	if (!gms_main_window)
-		error("\n\nINTERNAL ERROR: %s", string);
+		error("\n\nINTERNAL ERROR: %s", str);
 
 	/* Cancel all possible pending window input events. */
-	g_vm->glk_cancel_line_event(gms_main_window, NULL);
-	g_vm->glk_cancel_char_event(gms_main_window);
+	glk_cancel_line_event(gms_main_window, NULL);
+	glk_cancel_char_event(gms_main_window);
 	if (gms_hint_menu_window) {
-		g_vm->glk_cancel_char_event(gms_hint_menu_window);
-		g_vm->glk_window_close(gms_hint_menu_window, NULL);
+		glk_cancel_char_event(gms_hint_menu_window);
+		glk_window_close(gms_hint_menu_window, NULL);
 	}
 	if (gms_hint_text_window) {
-		g_vm->glk_cancel_char_event(gms_hint_text_window);
-		g_vm->glk_window_close(gms_hint_text_window, NULL);
+		glk_cancel_char_event(gms_hint_text_window);
+		glk_window_close(gms_hint_text_window, NULL);
 	}
 
 	/* Print a message indicating the error. */
-	g_vm->glk_set_window(gms_main_window);
-	g_vm->glk_set_style(style_Normal);
-	g_vm->glk_put_string("\n\nINTERNAL ERROR: ");
-	g_vm->glk_put_string(string);
+	glk_set_window(gms_main_window);
+	glk_set_style(style_Normal);
+	glk_put_string("\n\nINTERNAL ERROR: ");
+	glk_put_string(str);
 
-	g_vm->glk_put_string("\n\nPlease record the details of this error, try to"
+	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");
 }
 
-
-/*
- * gms_malloc()
- * gms_realloc()
- *
- * Non-failing malloc and realloc; call gms_fatal and exit if memory
- * allocation fails.
- */
-static void *gms_malloc(size_t size) {
+void *Magnetic::gms_malloc(size_t size) {
 	void *pointer;
 
 	pointer = malloc(size);
 	if (!pointer) {
 		gms_fatal("GLK: Out of system memory");
-		g_vm->glk_exit();
+		glk_exit();
 	}
 
 	return pointer;
 }
 
-static void *gms_realloc(void *ptr, size_t size) {
+void *Magnetic::gms_realloc(void *ptr, size_t size) {
 	void *pointer;
 
 	pointer = realloc(ptr, size);
 	if (!pointer) {
 		gms_fatal("GLK: Out of system memory");
-		g_vm->glk_exit();
+		glk_exit();
 	}
 
 	return pointer;
 }
 
-
-/*
- * gms_strncasecmp()
- * gms_strcasecmp()
- *
- * Strncasecmp and strcasecmp are not ANSI functions, so here are local
- * definitions to do the same jobs.
- */
-static int gms_strncasecmp(const char *s1, const char *s2, size_t n) {
+int Magnetic::gms_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]);
+		diff = glk_char_to_lower(s1[index]) - glk_char_to_lower(s2[index]);
 		if (diff < 0 || diff > 0)
 			return diff < 0 ? -1 : 1;
 	}
@@ -178,7 +274,7 @@ static int gms_strncasecmp(const char *s1, const char *s2, size_t n) {
 	return 0;
 }
 
-static int gms_strcasecmp(const char *s1, const char *s2) {
+int Magnetic::gms_strcasecmp(const char *s1, const char *s2) {
 	size_t s1len, s2len;
 	int result;
 
@@ -196,22 +292,11 @@ static int gms_strcasecmp(const char *s1, const char *s2) {
 /*  Glk port CRC functions                                             */
 /*---------------------------------------------------------------------*/
 
-/* CRC table initialization polynomial. */
-static const glui32 GMS_CRC_POLYNOMIAL = 0xedb88320;
-
-
-/*
- * gms_get_buffer_crc()
- *
- * Return the CRC of the bytes in buffer[0..length-1].
- *
- * This algorithm is taken from the PNG specification, version 1.0.
- */
-static glui32 gms_get_buffer_crc(const void *void_buffer, size_t length) {
-	static int is_initialized = FALSE;
+glui32 Magnetic::gms_get_buffer_crc(const void *void_buffer, size_t length) {
+	static int is_initialized = false;
 	static glui32 crc_table[BYTE_MAX + 1];
 
-	const char *buffer = (const char *) void_buffer;
+	const char *buf = (const char *) void_buffer;
 	glui32 crc;
 	size_t index;
 
@@ -227,7 +312,7 @@ static glui32 gms_get_buffer_crc(const void *void_buffer, size_t length) {
 			crc_table[index] = crc;
 		}
 
-		is_initialized = TRUE;
+		is_initialized = true;
 
 		/* CRC lookup table self-test, after is_initialized set -- recursion. */
 		assert(gms_get_buffer_crc("123456789", 9) == 0xcbf43926);
@@ -239,363 +324,45 @@ static glui32 gms_get_buffer_crc(const void *void_buffer, size_t length) {
 	 */
 	crc = 0xffffffff;
 	for (index = 0; index < length; index++)
-		crc = crc_table[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> CHAR_BIT);
+		crc = crc_table[(crc ^ buf[index]) & BYTE_MAX] ^ (crc >> CHAR_BIT);
 	return crc ^ 0xffffffff;
 }
 
-
 /*---------------------------------------------------------------------*/
 /*  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.  Set on game startup, by
- * identifying the game from its text file header.
- */
-static const char *gms_gameid_game_name = NULL;
-
-
-/*
- * The following game database is built from Generic/games.txt, and is used
- * to identify the game being run.  Magnetic Scrolls games don't generally
- * supply a status line, so this data can be used instead.
- */
-struct gms_game_table_t {
-	const type32 undo_size;   /* Header word at offset 0x22 */
-	const type32 undo_pc;     /* Header word at offset 0x26 */
-	const char *const name;   /* Game title and platform */
-};
-typedef const gms_game_table_t *gms_game_tableref_t;
-
-static const gms_game_table_t GMS_GAME_TABLE[] = {
-	{0x2100, 0x427e, "Corruption v1.11 (Amstrad CPC)"},
-	{0x2100, 0x43a0, "Corruption v1.11 (Archimedes)"},
-	{0x2100, 0x43a0, "Corruption v1.11 (DOS)"},
-	{0x2100, 0x4336, "Corruption v1.11 (Commodore 64)"},
-	{0x2100, 0x4222, "Corruption v1.11 (Spectrum +3)"},
-	{0x2100, 0x4350, "Corruption v1.12 (Archimedes)"},
-	{0x2500, 0x6624, "Corruption v1.12 (DOS, Magnetic Windows)"},
-
-	{0x2300, 0x3fa0, "Fish v1.02 (DOS)"},
-	{0x2400, 0x4364, "Fish v1.03 (Spectrum +3)"},
-	{0x2300, 0x3f72, "Fish v1.07 (Commodore 64)"},
-	{0x2200, 0x3f9c, "Fish v1.08 (Archimedes)"},
-	{0x2a00, 0x583a, "Fish v1.10 (DOS, Magnetic Windows)"},
-
-	{0x5000, 0x6c30, "Guild v1.0 (Amstrad CPC)"},
-	{0x5000, 0x6cac, "Guild v1.0 (Commodore 64)"},
-	{0x5000, 0x6d5c, "Guild v1.1 (DOS)"},
-	{0x3300, 0x698a, "Guild v1.3 (Archimedes)"},
-	{0x3200, 0x6772, "Guild v1.3 (Spectrum +3)"},
-	{0x3400, 0x6528, "Guild v1.3 (DOS, Magnetic Windows)"},
-
-	{0x2b00, 0x488c, "Jinxter v1.05 (Commodore 64)"},
-	{0x2c00, 0x4a08, "Jinxter v1.05 (DOS)"},
-	{0x2c00, 0x487a, "Jinxter v1.05 (Spectrum +3)"},
-	{0x2c00, 0x4a56, "Jinxter v1.10 (DOS)"},
-	{0x2b00, 0x4924, "Jinxter v1.22 (Amstrad CPC)"},
-	{0x2c00, 0x4960, "Jinxter v1.30 (Archimedes)"},
-
-	{0x1600, 0x3940, "Myth v1.0 (Commodore 64)"},
-	{0x1500, 0x3a0a, "Myth v1.0 (DOS)"},
-
-	{0x3600, 0x42cc, "Pawn v2.3 (Amstrad CPC)"},
-	{0x3600, 0x4420, "Pawn v2.3 (Archimedes)"},
-	{0x3600, 0x3fb0, "Pawn v2.3 (Commodore 64)"},
-	{0x3600, 0x4420, "Pawn v2.3 (DOS)"},
-	{0x3900, 0x42e4, "Pawn v2.3 (Spectrum 128)"},
-	{0x3900, 0x42f4, "Pawn v2.4 (Spectrum +3)"},
-
-	{0x3900, 0x75f2, "Wonderland v1.21 (DOS, Magnetic Windows)"},
-	{0x3900, 0x75f8, "Wonderland v1.27 (Archimedes)"},
-	{0, 0, NULL}
-};
-
-
-/*
- * gms_gameid_lookup_game()
- *
- * Look up and return the game table entry given a game's undo size and
- * undo pc values.  Returns the entry, or NULL if not found.
- */
-static gms_game_tableref_t gms_gameid_lookup_game(type32 undo_size, type32 undo_pc) {
-	gms_game_tableref_t game;
-
-	for (game = GMS_GAME_TABLE; game->name; game++) {
-		if (game->undo_size == undo_size && game->undo_pc == undo_pc)
-			break;
-	}
-
-	return game->name ? game : NULL;
-}
-
-
-/*
- * gms_gameid_read_uint32()
- *
- * Endian-safe unsigned 32 bit integer read from game text file.  Returns
- * 0 on error, a known unused table value.
- */
-static type32 gms_gameid_read_uint32(int offset, Common::SeekableReadStream *stream) {
+type32 Magnetic::gms_gameid_read_uint32(int offset, Common::SeekableReadStream *stream) {
 	if (!stream->seek(offset))
 		return 0;
 	return stream->readUint32BE();
 }
 
-
-/*
- * gms_gameid_identify_game()
- *
- * Identify a game from its text file header, and cache the game's name for
- * later queries.  Sets the cache to NULL if not found.
- */
-static void gms_gameid_identify_game(const Common::String &text_file) {
+void Magnetic::gms_gameid_identify_game(const Common::String &text_file) {
 	Common::File stream;
 
 	if (!stream.open(text_file))
 		error("Error opening game file");
 
-	type32 undo_size, undo_pc;
+	type32 game_size, game_pc;
 	gms_game_tableref_t game;
 
 	/* Read the game's signature undo size and undo pc values. */
-	undo_size = gms_gameid_read_uint32(0x22, &stream);
-	undo_pc = gms_gameid_read_uint32(0x26, &stream);
+	game_size = gms_gameid_read_uint32(0x22, &stream);
+	game_pc = gms_gameid_read_uint32(0x26, &stream);
 
 	/* Search for these values in the table, and set game name if found. */
-	game = gms_gameid_lookup_game(undo_size, undo_pc);
+	game = gms_gameid_lookup_game(game_size, game_pc);
 	gms_gameid_game_name = game ? game->name : NULL;
 }
 
-
-/*
- * gms_gameid_get_game_name()
- *
- * Return the name of the game, or NULL if not identifiable.
- */
-static const char *gms_gameid_get_game_name(void) {
-	return gms_gameid_game_name;
-}
-
-
 /*---------------------------------------------------------------------*/
 /*  Glk port picture functions                                         */
 /*---------------------------------------------------------------------*/
 
-/*
- * Color conversions lookup tables, and a word about gamma corrections.
- *
- * When uncorrected, some game pictures can look dark (Corruption, Won-
- * derland), whereas others look just fine (Guild Of Thieves, Jinxter).
- *
- * The standard general-purpose gamma correction is around 2.1, with
- * specific values, normally, of 2.5-2.7 for IBM PC systems, and 1.8 for
- * Macintosh.  However, applying even the low end of this range can make
- * some pictures look washed out, yet improve others nicely.
- *
- * To try to solve this, here we'll set up a precalculated table with
- * discrete gamma values.  On displaying a picture, we'll try to find a
- * gamma correction that seems to offer a reasonable level of contrast
- * for the picture.
- *
- * Here's an AWK script to create the gamma table:
- *
- * BEGIN { max=255.0; step=max/7.0
- *         for (gamma=0.9; gamma<=2.7; gamma+=0.05) {
- *             printf "  {\"%2.2f\", {0, ", gamma
- *             for (i=1; i<8; i++) {
- *                 printf "%3.0f", (((step*i / max) ^ (1.0/gamma)) * max)
- *                 printf "%s", (i<7) ? ", " : ""
- *             }
- *             printf "}, "
- *             printf "%s },\n", (gamma>0.99 && gamma<1.01) ? "FALSE" : "TRUE "
- *         } }
- *
- */
-typedef const struct {
-	const char *const level;       /* Gamma correction level. */
-	const unsigned char table[8];  /* Color lookup table. */
-	const int is_corrected;        /* Flag if non-linear. */
-} gms_gamma_t;
-typedef gms_gamma_t *gms_gammaref_t;
-
-static gms_gamma_t GMS_GAMMA_TABLE[] = {
-	{"0.90", {0,  29,  63,  99, 137, 175, 215, 255}, TRUE},
-	{"0.95", {0,  33,  68, 105, 141, 179, 217, 255}, TRUE},
-	{"1.00", {0,  36,  73, 109, 146, 182, 219, 255}, FALSE},
-	{"1.05", {0,  40,  77, 114, 150, 185, 220, 255}, TRUE},
-	{"1.10", {0,  43,  82, 118, 153, 188, 222, 255}, TRUE},
-	{"1.15", {0,  47,  86, 122, 157, 190, 223, 255}, TRUE},
-	{"1.20", {0,  50,  90, 126, 160, 193, 224, 255}, TRUE},
-	{"1.25", {0,  54,  94, 129, 163, 195, 225, 255}, TRUE},
-	{"1.30", {0,  57,  97, 133, 166, 197, 226, 255}, TRUE},
-	{"1.35", {0,  60, 101, 136, 168, 199, 227, 255}, TRUE},
-	{"1.40", {0,  64, 104, 139, 171, 201, 228, 255}, TRUE},
-	{"1.45", {0,  67, 107, 142, 173, 202, 229, 255}, TRUE},
-	{"1.50", {0,  70, 111, 145, 176, 204, 230, 255}, TRUE},
-	{"1.55", {0,  73, 114, 148, 178, 205, 231, 255}, TRUE},
-	{"1.60", {0,  76, 117, 150, 180, 207, 232, 255}, TRUE},
-	{"1.65", {0,  78, 119, 153, 182, 208, 232, 255}, TRUE},
-	{"1.70", {0,  81, 122, 155, 183, 209, 233, 255}, TRUE},
-	{"1.75", {0,  84, 125, 157, 185, 210, 233, 255}, TRUE},
-	{"1.80", {0,  87, 127, 159, 187, 212, 234, 255}, TRUE},
-	{"1.85", {0,  89, 130, 161, 188, 213, 235, 255}, TRUE},
-	{"1.90", {0,  92, 132, 163, 190, 214, 235, 255}, TRUE},
-	{"1.95", {0,  94, 134, 165, 191, 215, 236, 255}, TRUE},
-	{"2.00", {0,  96, 136, 167, 193, 216, 236, 255}, TRUE},
-	{"2.05", {0,  99, 138, 169, 194, 216, 237, 255}, TRUE},
-	{"2.10", {0, 101, 140, 170, 195, 217, 237, 255}, TRUE},
-	{"2.15", {0, 103, 142, 172, 197, 218, 237, 255}, TRUE},
-	{"2.20", {0, 105, 144, 173, 198, 219, 238, 255}, TRUE},
-	{"2.25", {0, 107, 146, 175, 199, 220, 238, 255}, TRUE},
-	{"2.30", {0, 109, 148, 176, 200, 220, 238, 255}, TRUE},
-	{"2.35", {0, 111, 150, 178, 201, 221, 239, 255}, TRUE},
-	{"2.40", {0, 113, 151, 179, 202, 222, 239, 255}, TRUE},
-	{"2.45", {0, 115, 153, 180, 203, 222, 239, 255}, TRUE},
-	{"2.50", {0, 117, 154, 182, 204, 223, 240, 255}, TRUE},
-	{"2.55", {0, 119, 156, 183, 205, 223, 240, 255}, TRUE},
-	{"2.60", {0, 121, 158, 184, 206, 224, 240, 255}, TRUE},
-	{"2.65", {0, 122, 159, 185, 206, 225, 241, 255}, TRUE},
-	{"2.70", {0, 124, 160, 186, 207, 225, 241, 255}, TRUE},
-	{NULL,   {0,   0,   0,   0,   0,   0,   0,   0}, FALSE}
-};
-
-/* R,G,B color triple definition. */
-typedef struct {
-	int red, green, blue;
-} gms_rgb_t;
-typedef gms_rgb_t *gms_rgbref_t;
-
-/*
- * Weighting values for calculating the luminance of a color.  There are
- * two commonly used sets of values for these -- 299,587,114, taken from
- * NTSC (Never The Same Color) 1953 standards, and 212,716,72, which is the
- * set that modern CRTs tend to match.  The NTSC ones seem to give the best
- * subjective results.
- */
-static const gms_rgb_t GMS_LUMINANCE_WEIGHTS = { 299, 587, 114 };
-
-/*
- * 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 GMS_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 GMS_GRAPHICS_TIMEOUT = 50;
-
-/*
- * Count of timeouts to wait in between animation paints, and to wait on
- * repaint request.  Waiting for 2 timeouts of around 50ms, gets us to the
- * 100ms recommended animation frame rate.  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 GMS_GRAPHICS_ANIMATION_WAIT = 2,
-                 GMS_GRAPHICS_REPAINT_WAIT = 10;
-
-/* Pixel size multiplier for image size scaling. */
-static const int GMS_GRAPHICS_PIXEL = 2;
-
-/* Proportion of the display to use for graphics. */
-static const glui32 GMS_GRAPHICS_PROPORTION = 60;
-
-/*
- * 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 GMS_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
-                    GMS_GRAPHICS_BORDER_COLOR = 0x00000000;
-static const int GMS_GRAPHICS_BORDER = 1,
-                 GMS_GRAPHICS_SHADING = 2,
-                 GMS_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 GMS_GRAPHICS_UNUSED_PIXEL = 0xff;
-
-/*
- * The current picture bitmap being displayed, its width, height, palette,
- * animation flag, and picture id.
- */
-enum { GMS_PALETTE_SIZE = 16 };
-static type8 *gms_graphics_bitmap = NULL;
-static type16 gms_graphics_width = 0,
-              gms_graphics_height = 0,
-              gms_graphics_palette[GMS_PALETTE_SIZE]; /* = { 0, ... }; */
-static bool gms_graphics_animated = FALSE;
-static type32 gms_graphics_picture = 0;
-
-/*
- * Flags set on new picture, and on resize or arrange events, and a flag
- * to indicate whether background repaint is stopped or active.
- */
-static bool gms_graphics_new_picture = FALSE,
-            gms_graphics_repaint = FALSE,
-            gms_graphics_active = FALSE;
-
-/* Flag to try to monitor the state of interpreter graphics. */
-static bool gms_graphics_interpreter = FALSE;
-
-/*
- * 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 type8 *gms_graphics_off_screen = NULL,
-              *gms_graphics_on_screen = NULL;
-
-/*
- * Pointer to the current active gamma table entry.  Because of the way
- * it's queried, this may not be NULL, otherwise we risk a race, with
- * admittedly a very low probability, with the updater.  So, it's init-
- * ialized instead to the gamma table.  The real value in use is inserted
- * on the first picture update timeout call for a new picture.
- */
-static gms_gammaref_t gms_graphics_current_gamma = GMS_GAMMA_TABLE;
-
-/*
- * The number of colors used in the palette by the current picture.  This
- * value is also at risk of a race with the updater, so it too has a mild
- * lie for a default value.
- */
-static int gms_graphics_color_count = GMS_PALETTE_SIZE;
-
-
-/*
- * gms_graphics_open()
- *
- * If it's not open, open the graphics window.  Returns TRUE if graphics
- * was successfully started, or already on.
- */
-static int gms_graphics_open(void) {
+int Magnetic::gms_graphics_open() {
 	if (!gms_graphics_window) {
-		gms_graphics_window = g_vm->glk_window_open(gms_main_window,
+		gms_graphics_window = glk_window_open(gms_main_window,
 		                      winmethod_Above
 		                      | winmethod_Proportional,
 		                      GMS_GRAPHICS_PROPORTION,
@@ -605,86 +372,41 @@ static int gms_graphics_open(void) {
 	return gms_graphics_window != NULL;
 }
 
-
-/*
- * gms_graphics_close()
- *
- * If open, close the graphics window and set back to NULL.
- */
-static void gms_graphics_close(void) {
+void Magnetic::gms_graphics_close() {
 	if (gms_graphics_window) {
-		g_vm->glk_window_close(gms_graphics_window, NULL);
+		glk_window_close(gms_graphics_window, NULL);
 		gms_graphics_window = NULL;
 	}
 }
 
-
-/*
- * gms_graphics_start()
- *
- * If graphics enabled, start any background picture update processing.
- */
-static void gms_graphics_start(void) {
-	if (g_vm->gms_graphics_enabled) {
+void Magnetic::gms_graphics_start() {
+	if (gms_graphics_enabled) {
 		/* If not running, start the updating "thread". */
 		if (!gms_graphics_active) {
-			g_vm->glk_request_timer_events(GMS_GRAPHICS_TIMEOUT);
-			gms_graphics_active = TRUE;
+			glk_request_timer_events(GMS_GRAPHICS_TIMEOUT);
+			gms_graphics_active = true;
 		}
 	}
 }
 
-
-/*
- * gms_graphics_stop()
- *
- * Stop any background picture update processing.
- */
-static void gms_graphics_stop(void) {
+void Magnetic::gms_graphics_stop() {
 	/* If running, stop the updating "thread". */
 	if (gms_graphics_active) {
-		g_vm->glk_request_timer_events(0);
-		gms_graphics_active = FALSE;
+		glk_request_timer_events(0);
+		gms_graphics_active = false;
 	}
 }
 
-
-/*
- * gms_graphics_are_displayed()
- *
- * Return TRUE if graphics are currently being displayed, FALSE otherwise.
- */
-static int gms_graphics_are_displayed(void) {
-	return gms_graphics_window != NULL;
-}
-
-
-/*
- * gms_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 gms_graphics_paint(void) {
-	if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
+void Magnetic::gms_graphics_paint() {
+	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
 		/* Set the repaint flag, and start graphics. */
-		gms_graphics_repaint = TRUE;
+		gms_graphics_repaint = true;
 		gms_graphics_start();
 	}
 }
 
-
-/*
- * gms_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, on change of gamma color correction policy, and on change
- * of animation policy.
- */
-static void gms_graphics_restart(void) {
-	if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
+void Magnetic::gms_graphics_restart() {
+	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
 		/*
 		 * If the picture is animated, we'll need to be able to re-get the
 		 * first animation frame so that the picture can be treated as if
@@ -715,21 +437,13 @@ static void gms_graphics_restart(void) {
 		}
 
 		/* Set the new picture flag, and start graphics. */
-		gms_graphics_new_picture = TRUE;
+		gms_graphics_new_picture = true;
 		gms_graphics_start();
 	}
 }
 
-
-/*
- * gms_graphics_count_colors()
- *
- * Analyze an image, and return the usage count of each palette color, and
- * an overall count of how many colors out of the palette are used.  NULL
- * arguments indicate no interest in the return value.
- */
-static void gms_graphics_count_colors(type8 bitmap[], type16 width, type16 height,
-                                      int *color_count, long color_usage[]) {
+void Magnetic::gms_graphics_count_colors(type8 bitmap[], type16 width, type16 height,
+		int *color_count, long color_usage[]) {
 	int x, y, count;
 	long usage[GMS_PALETTE_SIZE], index_row;
 	assert(bitmap);
@@ -762,20 +476,8 @@ static void gms_graphics_count_colors(type8 bitmap[], type16 width, type16 heigh
 		memcpy(color_usage, usage, sizeof(usage));
 }
 
-
-/*
- * gms_graphics_game_to_rgb_color()
- * gms_graphics_split_color()
- * gms_graphics_combine_color()
- * gms_graphics_color_luminance()
- *
- * General graphics helper functions, to convert between Magnetic Scrolls
- * and RGB color representations, and between RGB and Glk glui32 color
- * representations, and to calculate color luminance.
- */
-static void
-gms_graphics_game_to_rgb_color(type16 color, gms_gammaref_t gamma,
-                               gms_rgbref_t rgb_color) {
+void Magnetic::gms_graphics_game_to_rgb_color(type16 color, gms_gammaref_t gamma,
+		gms_rgbref_t rgb_color) {
 	assert(gamma && rgb_color);
 
 	/*
@@ -789,7 +491,7 @@ gms_graphics_game_to_rgb_color(type16 color, gms_gammaref_t gamma,
 	rgb_color->blue  = gamma->table[(color & 0x007)];
 }
 
-static void gms_graphics_split_color(glui32 color, gms_rgbref_t rgb_color) {
+void Magnetic::gms_graphics_split_color(glui32 color, gms_rgbref_t rgb_color) {
 	assert(rgb_color);
 
 	rgb_color->red   = (color >> 16) & 0xff;
@@ -797,7 +499,7 @@ static void gms_graphics_split_color(glui32 color, gms_rgbref_t rgb_color) {
 	rgb_color->blue  = color & 0xff;
 }
 
-static glui32 gms_graphics_combine_color(gms_rgbref_t rgb_color) {
+glui32 Magnetic::gms_graphics_combine_color(gms_rgbref_t rgb_color) {
 	glui32 color;
 	assert(rgb_color);
 
@@ -805,8 +507,8 @@ static glui32 gms_graphics_combine_color(gms_rgbref_t rgb_color) {
 	return color;
 }
 
-static int gms_graphics_color_luminance(gms_rgbref_t rgb_color) {
-	static int is_initialized = FALSE;
+int Magnetic::gms_graphics_color_luminance(gms_rgbref_t rgb_color) {
+	static int is_initialized = false;
 	static int weighting = 0;
 
 	long luminance;
@@ -816,7 +518,7 @@ static int gms_graphics_color_luminance(gms_rgbref_t rgb_color) {
 		weighting = GMS_LUMINANCE_WEIGHTS.red + GMS_LUMINANCE_WEIGHTS.green
 		            + GMS_LUMINANCE_WEIGHTS.blue;
 
-		is_initialized = TRUE;
+		is_initialized = true;
 	}
 
 	/* Calculate the luminance and scale back by 1000 to 0-255 before return. */
@@ -828,16 +530,7 @@ static int gms_graphics_color_luminance(gms_rgbref_t rgb_color) {
 	return (int)(luminance / weighting);
 }
 
-
-/*
- * gms_graphics_compare_luminance()
- * gms_graphics_constrast_variance()
- *
- * Calculate the contrast variance of the given palette and color usage, at
- * the given gamma correction level.  Helper functions for automatic gamma
- * correction.
- */
-static int gms_graphics_compare_luminance(const void *void_first,
+int Magnetic::gms_graphics_compare_luminance(const void *void_first,
         const void *void_second) {
 	long first = *(const long *)void_first;
 	long second = *(const long *)void_second;
@@ -845,7 +538,7 @@ static int gms_graphics_compare_luminance(const void *void_first,
 	return first > second ? 1 : second > first ? -1 : 0;
 }
 
-static long gms_graphics_contrast_variance(type16 palette[],
+long Magnetic::gms_graphics_contrast_variance(type16 palette[],
         long color_usage[], gms_gammaref_t gamma) {
 	int index, count, has_black, mean;
 	long sum;
@@ -854,7 +547,7 @@ static long gms_graphics_contrast_variance(type16 palette[],
                                            plus one extra for black */
 
 	/* Calculate the luminance energy of each palette color at this gamma. */
-	has_black = FALSE;
+	has_black = false;
 	for (index = 0, count = 0; index < GMS_PALETTE_SIZE; index++) {
 		if (color_usage[index] > 0) {
 			gms_rgb_t rgb_color;
@@ -903,27 +596,7 @@ static long gms_graphics_contrast_variance(type16 palette[],
 	return sum / (count - 1);
 }
 
-
-/*
- * gms_graphics_equal_contrast_gamma()
- *
- * Try to find a gamma correction for the given palette and color usage that
- * gives relatively equal contrast among the displayed colors.
- *
- * To do this, we search the gamma tables, computing color luminance for each
- * color in the palette given this gamma.  From luminances, we then compute
- * the contrasts between the colors, and settle on the gamma correction that
- * gives the most even and well-distributed picture contrast.  We ignore
- * colors not used in the palette.
- *
- * Note that we don't consider how often a palette color is used, only whether
- * it's represented, or not.  Some weighting might improve things, but the
- * simple method seems to work adequately.  In practice, as there are only 16
- * colors in a palette, most pictures use most colors in a relatively well
- * distributed manner.  This algorithm probably wouldn't work well on real
- * photographs, though.
- */
-static gms_gammaref_t gms_graphics_equal_contrast_gamma(type16 palette[], long color_usage[]) {
+gms_gammaref_t Magnetic::gms_graphics_equal_contrast_gamma(type16 palette[], long color_usage[]) {
 	gms_gammaref_t gamma, result;
 	long lowest_variance;
 	assert(palette && color_usage);
@@ -952,25 +625,9 @@ static gms_gammaref_t gms_graphics_equal_contrast_gamma(type16 palette[], long c
 	return result;
 }
 
-
-/*
- * gms_graphics_select_gamma()
- *
- * Select a suitable gamma for the picture, based on the current gamma mode.
- *
- * The function returns either the linear gamma, a gamma value half way
- * between linear and the gamma that gives the most even contrast, or just
- * the gamma that gives the most even contrast.
- *
- * In the normal case, a value half way to the extreme case of making color
- * contrast equal for all colors is, subjectively, a reasonable value to use.
- * The problem cases are the darkest pictures, and selecting this value
- * brightens them while at the same time not making them look overbright or
- * too "sunny".
- */
-static gms_gammaref_t gms_graphics_select_gamma(type8 bitmap[],
+gms_gammaref_t Magnetic::gms_graphics_select_gamma(type8 bitmap[],
         type16 width, type16 height, type16 palette[]) {
-	static int is_initialized = FALSE;
+	static int is_initialized = false;
 	static gms_gammaref_t linear_gamma = NULL;
 
 	long color_usage[GMS_PALETTE_SIZE];
@@ -988,7 +645,7 @@ static gms_gammaref_t gms_graphics_select_gamma(type8 bitmap[],
 			}
 		}
 
-		is_initialized = TRUE;
+		is_initialized = true;
 	}
 	assert(linear_gamma);
 
@@ -996,7 +653,7 @@ static gms_gammaref_t gms_graphics_select_gamma(type8 bitmap[],
 	 * Check to see if automated correction is turned off; if it is, return
 	 * the linear gamma.
 	 */
-	if (g_vm->gms_gamma_mode == GAMMA_OFF)
+	if (gms_gamma_mode == GAMMA_OFF)
 		return linear_gamma;
 
 	/*
@@ -1017,23 +674,15 @@ static gms_gammaref_t gms_graphics_select_gamma(type8 bitmap[],
 	 * For normal automated correction, return a gamma value half way between
 	 * the linear gamma and the equal contrast gamma.
 	 */
-	if (g_vm->gms_gamma_mode == GAMMA_NORMAL)
+	if (gms_gamma_mode == GAMMA_NORMAL)
 		return linear_gamma + (contrast_gamma - linear_gamma) / 2;
 
 	/* Correction must be high; return the equal contrast gamma. */
-	assert(g_vm->gms_gamma_mode == GAMMA_HIGH);
+	assert(gms_gamma_mode == GAMMA_HIGH);
 	return contrast_gamma;
 }
 
-
-/*
- * gms_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 gms_graphics_clear_and_border(winid_t glk_window,
+void Magnetic::gms_graphics_clear_and_border(winid_t glk_window,
         int x_offset, int y_offset, int pixel_size, type16 width, type16 height) {
 	glui32 background, fade_color, shading_color;
 	gms_rgb_t rgb_background, rgb_border, rgb_fade;
@@ -1046,7 +695,7 @@ static void gms_graphics_clear_and_border(winid_t glk_window,
 	 * 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(gms_main_window,
+	if (!glk_style_measure(gms_main_window,
 	                             style_Normal, stylehint_BackColor, &background)) {
 		/*
 		 * Unable to get the main window background, so assume, and default
@@ -1059,8 +708,8 @@ static void gms_graphics_clear_and_border(winid_t glk_window,
 	 * 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);
+	glk_window_set_background_color(glk_window, background);
+	glk_window_clear(glk_window);
 
 	/*
 	 * For very small pictures, just border them, but don't try and do any
@@ -1069,7 +718,7 @@ static void gms_graphics_clear_and_border(winid_t glk_window,
 	if (width < 2 * GMS_GRAPHICS_SHADE_STEPS
 	        || height < 2 * GMS_GRAPHICS_SHADE_STEPS) {
 		/* Paint a rectangle bigger than the picture by border pixels. */
-		g_vm->glk_window_fill_rect(glk_window,
+		glk_window_fill_rect(glk_window,
 		                           GMS_GRAPHICS_BORDER_COLOR,
 		                           x_offset - GMS_GRAPHICS_BORDER,
 		                           y_offset - GMS_GRAPHICS_BORDER,
@@ -1084,7 +733,7 @@ static void gms_graphics_clear_and_border(winid_t glk_window,
 	 * 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,
+	glk_window_fill_rect(glk_window,
 	                           GMS_GRAPHICS_BORDER_COLOR,
 	                           x_offset - GMS_GRAPHICS_BORDER,
 	                           y_offset - GMS_GRAPHICS_BORDER,
@@ -1119,12 +768,12 @@ static void gms_graphics_clear_and_border(winid_t glk_window,
 	shading_color = background;
 	for (index = 0; index < GMS_GRAPHICS_SHADE_STEPS; index++) {
 		/* Shade the two border areas with this color. */
-		g_vm->glk_window_fill_rect(glk_window, shading_color,
+		glk_window_fill_rect(glk_window, shading_color,
 		                           x_offset + width * pixel_size
 		                           + GMS_GRAPHICS_BORDER,
 		                           y_offset + index - GMS_GRAPHICS_BORDER,
 		                           GMS_GRAPHICS_SHADING, 1);
-		g_vm->glk_window_fill_rect(glk_window, shading_color,
+		glk_window_fill_rect(glk_window, shading_color,
 		                           x_offset + index - GMS_GRAPHICS_BORDER,
 		                           y_offset + height * pixel_size
 		                           + GMS_GRAPHICS_BORDER,
@@ -1135,14 +784,7 @@ static void gms_graphics_clear_and_border(winid_t glk_window,
 	}
 }
 
-
-/*
- * gms_graphics_convert_palette()
- *
- * Convert a Magnetic Scrolls color palette to a Glk one, using the given
- * gamma corrections.
- */
-static void gms_graphics_convert_palette(type16 ms_palette[], gms_gammaref_t gamma,
+void Magnetic::gms_graphics_convert_palette(type16 ms_palette[], gms_gammaref_t gamma,
         glui32 glk_palette[]) {
 	int index;
 	assert(ms_palette && gamma && glk_palette);
@@ -1159,21 +801,13 @@ static void gms_graphics_convert_palette(type16 ms_palette[], gms_gammaref_t gam
 	}
 }
 
-
-/*
- * gms_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 gms_graphics_position_picture(winid_t glk_window,
-        int pixel_size, type16 width, type16 height,
-        int *x_offset, int *y_offset) {
+void Magnetic::gms_graphics_position_picture(winid_t glk_window,
+        int pixel_size, type16 width, type16 height, int *x_offset, int *y_offset) {
 	glui32 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);
+	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
@@ -1183,21 +817,9 @@ static void gms_graphics_position_picture(winid_t glk_window,
 	*y_offset = ((int) window_height - height * pixel_size) / 2;
 }
 
-
-/*
- * gms_graphics_apply_animation_frame()
- *
- * Apply a single animation frame to the given off-screen image buffer, using
- * the frame bitmap, width, height and mask, the off-screen buffer, and the
- * width and height of the main picture.
- *
- * Note that 'mask' may be NULL, implying that no frame pixel is transparent.
- */
-static void gms_graphics_apply_animation_frame(type8 bitmap[],
-        type16 frame_width, type16 frame_height,
-        type8 mask[], int frame_x, int frame_y,
-        type8 off_screen[], type16 width,
-        type16 height) {
+void Magnetic::gms_graphics_apply_animation_frame(type8 bitmap[],
+        type16 frame_width, type16 frame_height, type8 mask[], int frame_x, int frame_y,
+        type8 off_screen[], type16 width, type16 height) {
 	int mask_width, x, y;
 	type8 mask_hibit;
 	long frame_row, buffer_row, mask_row;
@@ -1273,18 +895,7 @@ static void gms_graphics_apply_animation_frame(type8 bitmap[],
 	}
 }
 
-
-/*
- * gms_graphics_animate()
- *
- * This function finds and applies the next set of animation frames to the
- * given off-screen image buffer.  It's handed the width and height of the
- * main picture, and the off-screen buffer.
- *
- * It returns FALSE if at the end of animations, TRUE if more animations
- * remain.
- */
-static int gms_graphics_animate(type8 off_screen[], type16 width, type16 height) {
+int Magnetic::gms_graphics_animate(type8 off_screen[], type16 width, type16 height) {
 	struct ms_position *positions;
 	type16 count;
 	type8 status;
@@ -1294,7 +905,7 @@ static int gms_graphics_animate(type8 off_screen[], type16 width, type16 height)
 	/* Search for more animation frames, and return zero if none. */
 	status = ms_animate(&positions, &count);
 	if (status == 0)
-		return FALSE;
+		return false;
 
 	/* Apply each animation frame to the off-screen buffer. */
 	for (frame = 0; frame < count; frame++) {
@@ -1316,26 +927,13 @@ static int gms_graphics_animate(type8 off_screen[], type16 width, type16 height)
 		}
 	}
 
-	/* Return TRUE since more animation frames remain. */
-	return TRUE;
+	/* Return true since more animation frames remain. */
+	return true;
 }
 
 #ifndef GARGLK
-/*
- * gms_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 gms_graphics_is_vertex(type8 off_screen[], type16 width, type16 height,
-                                  int x, int y) {
+int Magnetic::gms_graphics_is_vertex(type8 off_screen[], type16 width, type16 height,
+		int x, int y) {
 	type8 pixel;
 	int above, below, left, right;
 	long index_row;
@@ -1359,47 +957,14 @@ static int gms_graphics_is_vertex(type8 off_screen[], type16 width, type16 heigh
 	right = (x == width - 1 || off_screen[index_row + x + 1] != pixel);
 
 	/*
-	 * Return TRUE if this pixel lies at the vertex of a rectangular, fillable,
+	 * 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()
- * gms_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, as it could be
- * in some animation frames) 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.
- */
-typedef struct {
-	long complexity;  /* Count of vertices for this color. */
-	long usage;       /* Color usage count. */
-	int color;        /* Color index into palette. */
-} gms_layering_t;
-
-static int gms_graphics_compare_layering_inverted(const void *void_first,
+int Magnetic::gms_graphics_compare_layering_inverted(const void *void_first,
         const void *void_second) {
 	gms_layering_t *first = (gms_layering_t *) void_first;
 	gms_layering_t *second = (gms_layering_t *) void_second;
@@ -1418,7 +983,7 @@ static int gms_graphics_compare_layering_inverted(const void *void_first,
 	       first->usage > second->usage ? -1 : 0;
 }
 
-static void gms_graphics_assign_layers(type8 off_screen[], type8 on_screen[],
+void Magnetic::gms_graphics_assign_layers(type8 off_screen[], type8 on_screen[],
                                        type16 width, type16 height,
                                        int layers[], long layer_usage[]) {
 	int index, x, y;
@@ -1473,36 +1038,7 @@ static void gms_graphics_assign_layers(type8 off_screen[], type8 on_screen[],
 	}
 }
 
-/*
- * gms_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 gms_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
+void Magnetic::gms_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
                                       type8 off_screen[], type8 on_screen[],
                                       int x, int y, int x_offset, int y_offset,
                                       int pixel_size, type16 width, type16 height) {
@@ -1571,7 +1107,7 @@ break_y_min:
 break_y_max:
 
 	/* Fill the region using Glk's rectangle fill. */
-	g_vm->glk_window_fill_rect(glk_window, palette[pixel],
+	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,
@@ -1608,10 +1144,8 @@ break_y_max:
 }
 #endif
 
-static void gms_graphics_paint_everything(winid_t glk_window,
-        glui32 palette[],
-        type8 off_screen[],
-        int x_offset, int y_offset,
+void Magnetic::gms_graphics_paint_everything(winid_t glk_window,
+        glui32 palette[], type8 off_screen[], int x_offset, int y_offset,
         type16 width, type16 height) {
 	type8       pixel;          /* Reference pixel color */
 	int     x, y;
@@ -1619,7 +1153,7 @@ static void gms_graphics_paint_everything(winid_t glk_window,
 	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,
+			glk_window_fill_rect(glk_window,
 			                           palette[ pixel ],
 			                           x * GMS_GRAPHICS_PIXEL + x_offset,
 			                           y * GMS_GRAPHICS_PIXEL + y_offset,
@@ -1628,31 +1162,14 @@ static void gms_graphics_paint_everything(winid_t glk_window,
 	}
 }
 
-/*
- * gms_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 gms_graphics_timeout() {
+void Magnetic::gms_graphics_timeout() {
 	static glui32 palette[GMS_PALETTE_SIZE];   /* Precomputed Glk palette */
 #ifndef GARGLK
 	static int layers[GMS_PALETTE_SIZE];       /* Assigned image layers */
 	static long layer_usage[GMS_PALETTE_SIZE]; /* Image layer occupancies */
 #endif
 
-	static int deferred_repaint = FALSE;       /* Local delayed repaint flag */
+	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 */
@@ -1683,8 +1200,8 @@ static void gms_graphics_timeout() {
 	 * we are receiving consecutive Glk arrange or redraw events.
 	 */
 	if (gms_graphics_repaint) {
-		deferred_repaint = TRUE;
-		gms_graphics_repaint = FALSE;
+		deferred_repaint = true;
+		gms_graphics_repaint = false;
 		ignore_counter = GMS_GRAPHICS_REPAINT_WAIT - 1;
 		return;
 	}
@@ -1816,8 +1333,8 @@ static void gms_graphics_timeout() {
 		yield_counter = 0;
 
 		/* Clear the new picture and deferred repaint flags. */
-		gms_graphics_new_picture = FALSE;
-		deferred_repaint = FALSE;
+		gms_graphics_new_picture = false;
+		deferred_repaint = false;
 	}
 
 #ifndef GARGLK
@@ -1924,7 +1441,7 @@ static void gms_graphics_timeout() {
 	 * If animated, and if animations are enabled, handle further animation
 	 * frames, if any.
 	 */
-	if (g_vm->gms_animation_enabled && gms_graphics_animated) {
+	if (gms_animation_enabled && gms_graphics_animated) {
 		int more_animation;
 
 		/*
@@ -1999,31 +1516,7 @@ static void gms_graphics_timeout() {
 	}
 }
 
-
-/*
- * ms_showpic()
- *
- * 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.
- *
- * The graphics window is opened if required, or closed if mode is zero.
- *
- * The function checks for changes of actual picture by calculating the
- * CRC for picture data; this helps to prevent unnecessary repaints in
- * cases where the interpreter passes us the same picture as we're already
- * displaying.  There is a less than 1 in 4,294,967,296 chance that a new
- * picture will be missed.  We'll live with that.
- *
- * Why use CRCs, rather than simply storing the values of picture passed in
- * a static variable?  Because some games, typically Magnetic Windows, use
- * the picture argument as a form of string pointer, and can pass in the
- * same value for several, perhaps all, game pictures.  If we just checked
- * for a change in the picture argument, we'd never see one.  So we must
- * instead look for changes in the real picture data.
- */
-void ms_showpic(type32 picture, type8 mode) {
+void Magnetic::ms_showpic(type32 picture, type8 mode) {
 	static glui32 current_crc = 0;  /* CRC of the current picture */
 
 	type8 *bitmap, animated;
@@ -2034,13 +1527,13 @@ void ms_showpic(type32 picture, type8 mode) {
 	/* See if the mode indicates no graphics. */
 	if (mode == 0) {
 		/* Note that the interpreter turned graphics off. */
-		gms_graphics_interpreter = FALSE;
+		gms_graphics_interpreter = false;
 
 		/*
 		 * If we are currently displaying the graphics window, stop any update
 		 * "thread" and turn off graphics.
 		 */
-		if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
+		if (gms_graphics_enabled && gms_graphics_are_displayed()) {
 			gms_graphics_stop();
 			gms_graphics_close();
 		}
@@ -2050,7 +1543,7 @@ void ms_showpic(type32 picture, type8 mode) {
 	}
 
 	/* Note that the interpreter turned graphics on. */
-	gms_graphics_interpreter = TRUE;
+	gms_graphics_interpreter = true;
 
 	/*
 	 * Obtain the image details for the requested picture.  The call returns
@@ -2075,7 +1568,7 @@ void ms_showpic(type32 picture, type8 mode) {
 	if (width == gms_graphics_width
 	        && height == gms_graphics_height
 	        && crc == current_crc
-	        && g_vm->gms_graphics_enabled && gms_graphics_are_displayed())
+	        && gms_graphics_enabled && gms_graphics_are_displayed())
 		return;
 
 	/*
@@ -2106,39 +1599,19 @@ void ms_showpic(type32 picture, type8 mode) {
 	 * the picture details will simply stick around in module variables until
 	 * they are required.
 	 */
-	if (g_vm->gms_graphics_enabled) {
+	if (gms_graphics_enabled) {
 		/*
 		 * Ensure graphics on, then set the new picture flag and start the
 		 * updating "thread".
 		 */
 		if (gms_graphics_open()) {
-			gms_graphics_new_picture = TRUE;
+			gms_graphics_new_picture = true;
 			gms_graphics_start();
 		}
 	}
 }
 
-
-/*
- * gms_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
-gms_graphics_picture_is_available(void) {
-	return gms_graphics_bitmap != NULL;
-}
-
-
-/*
- * gms_graphics_get_picture_details()
- *
- * Return the width, height, and animation flag 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 gms_graphics_get_picture_details(int *width, int *height, int *is_animated) {
+int Magnetic::gms_graphics_get_picture_details(int *width, int *height, int *is_animated) {
 	if (gms_graphics_picture_is_available()) {
 		if (width)
 			*width = gms_graphics_width;
@@ -2147,30 +1620,15 @@ static int gms_graphics_get_picture_details(int *width, int *height, int *is_ani
 		if (is_animated)
 			*is_animated = gms_graphics_animated;
 
-		return TRUE;
+		return true;
 	}
 
-	return FALSE;
+	return false;
 }
 
-
-/*
- * gms_graphics_get_rendering_details()
- *
- * Returns the current level of applied gamma correction, 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 gamma, 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
- * gamma and color count values are returned.
- */
-static int gms_graphics_get_rendering_details(const char **gamma, int *color_count,
-        int *is_active) {
-	if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
+int Magnetic::gms_graphics_get_rendering_details(const char **gamma,
+		int *color_count, int *is_active) {
+	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
 		/*
 		 * Return the string representing the gamma correction.  If racing
 		 * with timeouts, we might return the gamma for the last picture.
@@ -2192,31 +1650,17 @@ static int gms_graphics_get_rendering_details(const char **gamma, int *color_cou
 		if (is_active)
 			*is_active = gms_graphics_active;
 
-		return TRUE;
+		return true;
 	}
 
-	return FALSE;
+	return false;
 }
 
-
-/*
- * gms_graphics_interpreter_enabled()
- *
- * Return TRUE if it looks like interpreter graphics are turned on, FALSE
- * otherwise.
- */
-static int gms_graphics_interpreter_enabled(void) {
+int Magnetic::gms_graphics_interpreter_enabled() {
 	return gms_graphics_interpreter;
 }
 
-
-/*
- * gms_graphics_cleanup()
- *
- * Free memory resources allocated by graphics functions.  Called on game
- * end.
- */
-static void gms_graphics_cleanup(void) {
+void Magnetic::gms_graphics_cleanup() {
 	free(gms_graphics_bitmap);
 	gms_graphics_bitmap = NULL;
 	free(gms_graphics_off_screen);
@@ -2224,40 +1668,16 @@ static void gms_graphics_cleanup(void) {
 	free(gms_graphics_on_screen);
 	gms_graphics_on_screen = NULL;
 
-	gms_graphics_animated = FALSE;
+	gms_graphics_animated = false;
 	gms_graphics_picture = 0;
 }
 
-
 /*---------------------------------------------------------------------*/
 /*  Glk port status line functions                                     */
 /*---------------------------------------------------------------------*/
 
-/*
- * The interpreter feeds us status line characters one at a time, with Tab
- * indicating right justify, and CR indicating the line is complete.  To get
- * this to fit with the Glk event and redraw model, here we'll buffer each
- * completed status line, so we have a stable string to output when needed.
- * It's also handy to have this buffer for Glk libraries that don't support
- * separate windows.
- */
-enum { GMS_STATBUFFER_LENGTH = 1024 };
-static char gms_status_buffer[GMS_STATBUFFER_LENGTH];
-static int gms_status_length = 0;
-
-/* Default width used for non-windowing Glk status lines. */
-static const int GMS_DEFAULT_STATUS_WIDTH = 74;
-
-
-/*
- * ms_statuschar()
- *
- * Receive one status character from the interpreter.  Characters are
- * buffered internally, and on CR, the buffer is copied to the main static
- * status buffer for use by the status line printing function.
- */
-void ms_statuschar(type8 c) {
-	static char buffer[GMS_STATBUFFER_LENGTH];
+void Magnetic::ms_statuschar(type8 c) {
+	static char buffer_[GMS_STATBUFFER_LENGTH];
 	static int length = 0;
 
 	/*
@@ -2266,39 +1686,31 @@ void ms_statuschar(type8 c) {
 	 * buffer the character.
 	 */
 	if (c == '\n') {
-		memcpy(gms_status_buffer, buffer, length);
+		memcpy(gms_status_buffer, buffer_, length);
 		gms_status_length = length;
 
 		length = 0;
 	} else {
-		if (length < (int)sizeof(buffer))
-			buffer[length++] = c;
+		if (length < (int)sizeof(buffer_))
+			buffer_[length++] = c;
 	}
 }
 
-
-/*
- * gms_status_update()
- *
- * Update the information in the status window with the current contents of
- * the completed status line buffer, or a default string if no completed
- * status line.
- */
-static void gms_status_update(void) {
+void Magnetic::gms_status_update() {
 	glui32 width, height;
 	int index;
 	assert(gms_status_window);
 
-	g_vm->glk_window_get_size(gms_status_window, &width, &height);
+	glk_window_get_size(gms_status_window, &width, &height);
 	if (height > 0) {
-		g_vm->glk_window_clear(gms_status_window);
-		g_vm->glk_window_move_cursor(gms_status_window, 0, 0);
-		g_vm->glk_set_window(gms_status_window);
+		glk_window_clear(gms_status_window);
+		glk_window_move_cursor(gms_status_window, 0, 0);
+		glk_set_window(gms_status_window);
 
-		g_vm->glk_set_style(style_User1);
+		glk_set_style(style_User1);
 		for (index = 0; index < (int)width; index++)
-			g_vm->glk_put_char(' ');
-		g_vm->glk_window_move_cursor(gms_status_window, 1, 0);
+			glk_put_char(' ');
+		glk_window_move_cursor(gms_status_window, 1, 0);
 
 		if (gms_status_length > 0) {
 			/*
@@ -2308,9 +1720,9 @@ static void gms_status_update(void) {
 			 */
 			for (index = 0; index < gms_status_length; index++) {
 				if (gms_status_buffer[index] == '\t')
-					g_vm->glk_window_move_cursor(gms_status_window, width - 11, 0);
+					glk_window_move_cursor(gms_status_window, width - 11, 0);
 				else
-					g_vm->glk_put_char(gms_status_buffer[index]);
+					glk_put_char(gms_status_buffer[index]);
 			}
 		} else {
 			const char *game_name;
@@ -2322,23 +1734,15 @@ static void gms_status_update(void) {
 			 * in general, seem to use one.
 			 */
 			game_name = gms_gameid_get_game_name();
-			g_vm->glk_put_string(game_name ? game_name : "ScummVM Magnetic version 2.3");
+			glk_put_string(game_name ? game_name : "ScummVM Magnetic version 2.3");
 		}
 
-		g_vm->glk_set_window(gms_main_window);
+		glk_set_window(gms_main_window);
 	}
 }
 
-
-/*
- * gms_status_print()
- *
- * Print the current contents of the completed status line buffer out in the
- * main window, if it has changed since the last call.  This is for non-
- * windowing Glk libraries.
- */
-static void gms_status_print(void) {
-	static char buffer[GMS_STATBUFFER_LENGTH];
+void Magnetic::gms_status_print() {
+	static char buffer_[GMS_STATBUFFER_LENGTH];
 	static int length = 0;
 
 	int index, column;
@@ -2349,14 +1753,14 @@ static void gms_status_print(void) {
 	 */
 	if (gms_status_length == 0
 	        || (gms_status_length == length
-	            && strncmp(buffer, gms_status_buffer, length)) == 0)
+	            && strncmp(buffer_, gms_status_buffer, length)) == 0)
 		return;
 
 	/* Set fixed width font to try to preserve status line formatting. */
-	g_vm->glk_set_style(style_Preformatted);
+	glk_set_style(style_Preformatted);
 
-	/* Bracket, and output the status line buffer. */
-	g_vm->glk_put_string("[ ");
+	/* Bracket, and output the status line buffer_. */
+	glk_put_string("[ ");
 	column = 1;
 	for (index = 0; index < gms_status_length; index++) {
 		/*
@@ -2367,49 +1771,34 @@ static void gms_status_print(void) {
 		 */
 		if (gms_status_buffer[index] == '\t') {
 			while (column <= GMS_DEFAULT_STATUS_WIDTH - 11) {
-				g_vm->glk_put_char(' ');
+				glk_put_char(' ');
 				column++;
 			}
 		} else {
-			g_vm->glk_put_char(gms_status_buffer[index]);
+			glk_put_char(gms_status_buffer[index]);
 			column++;
 		}
 	}
 
 	while (column <= GMS_DEFAULT_STATUS_WIDTH) {
-		g_vm->glk_put_char(' ');
+		glk_put_char(' ');
 		column++;
 	}
-	g_vm->glk_put_string(" ]\n");
+	glk_put_string(" ]\n");
 
-	/* Save the details of the printed status buffer. */
-	memcpy(buffer, gms_status_buffer, gms_status_length);
+	/* Save the details of the printed status buffer_. */
+	memcpy(buffer_, gms_status_buffer, gms_status_length);
 	length = gms_status_length;
 }
 
-
-/*
- * gms_status_notify()
- *
- * Front end function for updating status.  Either updates the status window
- * or prints the status line to the main window.
- */
-static void gms_status_notify(void) {
+void Magnetic::gms_status_notify() {
 	if (gms_status_window)
 		gms_status_update();
 	else
 		gms_status_print();
 }
 
-
-/*
- * gms_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 gms_status_redraw(void) {
+void Magnetic::gms_status_redraw() {
 	if (gms_status_window) {
 		winid_t parent;
 
@@ -2423,98 +1812,50 @@ static void gms_status_redraw(void) {
 		 * libraries other than Xglk, moreover, we're careful to activate it
 		 * only on resize and arrange events.
 		 */
-		parent = g_vm->glk_window_get_parent(gms_status_window);
-		g_vm->glk_window_set_arrangement(parent,
+		parent = glk_window_get_parent(gms_status_window);
+		glk_window_set_arrangement(parent,
 		                                 winmethod_Above | winmethod_Fixed, 1, NULL);
 
 		gms_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 gms_help_requested = FALSE,
-           gms_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 *gms_output_buffer = NULL;
-static int gms_output_allocation = 0,
-           gms_output_length = 0;
-
-/*
- * Flag to indicate if the last buffer flushed looked like it ended in a
- * ">" prompt.
- */
-static int gms_output_prompt = FALSE;
-
-
-/*
- * gms_output_register_help_request()
- * gms_output_silence_help_hints()
- * gms_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 gms_output_register_help_request(void) {
-	gms_help_requested = TRUE;
+void Magnetic::gms_output_register_help_request() {
+	gms_help_requested = true;
 }
 
-static void gms_output_silence_help_hints(void) {
-	gms_help_hints_silenced = TRUE;
+void Magnetic::gms_output_silence_help_hints() {
+	gms_help_hints_silenced = true;
 }
 
-static void gms_output_provide_help_hint(void) {
+void Magnetic::gms_output_provide_help_hint() {
 	if (gms_help_requested && !gms_help_hints_silenced) {
-		g_vm->glk_set_style(style_Emphasized);
-		g_vm->glk_put_string("[Try 'glk help' for help on special interpreter"
+		glk_set_style(style_Emphasized);
+		glk_put_string("[Try 'glk help' for help on special interpreter"
 		                     " commands]\n");
 
-		gms_help_requested = FALSE;
-		g_vm->glk_set_style(style_Normal);
+		gms_help_requested = false;
+		glk_set_style(style_Normal);
 	}
 }
 
-
-/*
- * gms_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 gms_game_prompted(void) {
+int Magnetic::gms_game_prompted() {
 	int result;
 
 	result = gms_output_prompt;
-	gms_output_prompt = FALSE;
+	gms_output_prompt = false;
 
 	return result;
 }
 
-
-/*
- * gms_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 gms_detect_game_prompt(void) {
+void Magnetic::gms_detect_game_prompt() {
 	int index;
 
-	gms_output_prompt = FALSE;
+	gms_output_prompt = false;
 
 	/*
 	 * Search for a prompt across any last unterminated buffered line; a prompt
@@ -2523,34 +1864,20 @@ static void gms_detect_game_prompt(void) {
 	for (index = gms_output_length - 1;
 	        index >= 0 && gms_output_buffer[index] != '\n'; index--) {
 		if (gms_output_buffer[index] != ' ') {
-			gms_output_prompt = TRUE;
+			gms_output_prompt = true;
 			break;
 		}
 	}
 }
 
-
-/*
- * gms_output_delete()
- *
- * Delete all buffered output text.  Free all malloc'ed buffer memory, and
- * return the buffer variables to their initial values.
- */
-static void gms_output_delete(void) {
+void Magnetic::gms_output_delete() {
 	free(gms_output_buffer);
 	gms_output_buffer = NULL;
 	gms_output_allocation = gms_output_length = 0;
 }
 
-
-/*
- * gms_output_flush()
- *
- * Flush any buffered output text to the Glk main window, and clear the
- * buffer.
- */
-static void gms_output_flush(void) {
-	assert(g_vm->glk_stream_get_current());
+void Magnetic::gms_output_flush() {
+	assert(glk_stream_get_current());
 
 	if (gms_output_length > 0) {
 		/*
@@ -2559,7 +1886,7 @@ static void gms_output_flush(void) {
 		 * the game's prompt (if any).
 		 */
 		gms_detect_game_prompt();
-		g_vm->glk_set_style(style_Normal);
+		glk_set_style(style_Normal);
 
 		if (gms_output_prompt) {
 			int index;
@@ -2568,12 +1895,12 @@ static void gms_output_flush(void) {
 			        index >= 0 && gms_output_buffer[index] != '\n';)
 				index--;
 
-			g_vm->glk_put_buffer(gms_output_buffer, index + 1);
+			glk_put_buffer(gms_output_buffer, index + 1);
 			gms_output_provide_help_hint();
-			g_vm->glk_put_buffer(gms_output_buffer + index + 1,
+			glk_put_buffer(gms_output_buffer + index + 1,
 			                     gms_output_length - index - 1);
 		} else {
-			g_vm->glk_put_buffer(gms_output_buffer, gms_output_length);
+			glk_put_buffer(gms_output_buffer, gms_output_length);
 			gms_output_provide_help_hint();
 		}
 
@@ -2581,13 +1908,7 @@ static void gms_output_flush(void) {
 	}
 }
 
-
-/*
- * ms_putchar()
- *
- * Buffer a character for eventual printing to the main window.
- */
-void ms_putchar(type8 c) {
+void Magnetic::ms_putchar(type8 c) {
 	int bytes;
 	assert(gms_output_length <= gms_output_allocation);
 
@@ -2615,139 +1936,56 @@ void ms_putchar(type8 c) {
 	gms_output_buffer[gms_output_length++] = c;
 }
 
-
-/*
- * gms_styled_string()
- * gms_styled_char()
- * gms_standout_string()
- * gms_standout_char()
- * gms_normal_string()
- * gms_normal_char()
- * gms_header_string()
- * gms_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 gms_styled_string(glui32 style, const char *message) {
+void Magnetic::gms_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);
+	glk_set_style(style);
+	glk_put_string(message);
+	glk_set_style(style_Normal);
 }
 
-static void gms_styled_char(glui32 style, char c) {
-	char buffer[2];
+void Magnetic::gms_styled_char(glui32 style, char c) {
+	char str[2];
 
-	buffer[0] = c;
-	buffer[1] = '\0';
-	gms_styled_string(style, buffer);
+	str[0] = c;
+	str[1] = '\0';
+	gms_styled_string(style, str);
 }
 
-static void gms_standout_string(const char *message) {
+void Magnetic::gms_standout_string(const char *message) {
 	gms_styled_string(style_Emphasized, message);
 }
 
-#if 0
-static void gms_standout_char(char c) {
-	gms_styled_char(style_Emphasized, c);
-}
-#endif
-
-static void gms_normal_string(const char *message) {
+void Magnetic::gms_normal_string(const char *message) {
 	gms_styled_string(style_Normal, message);
 }
 
-static void gms_normal_char(char c) {
+void Magnetic::gms_normal_char(char c) {
 	gms_styled_char(style_Normal, c);
 }
 
-static void gms_header_string(const char *message) {
+void Magnetic::gms_header_string(const char *message) {
 	gms_styled_string(style_Header, message);
 }
 
-static void gms_banner_string(const char *message) {
+void Magnetic::gms_banner_string(const char *message) {
 	gms_styled_string(style_Subheader, message);
 }
 
-/*
- * ms_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 gms_output_flush() when reading line input.
- *
- * Taking ms_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 ms_flush(void) {
+void Magnetic::ms_flush() {
 }
 
-
 /*---------------------------------------------------------------------*/
 /*  Glk port hint functions                                            */
 /*---------------------------------------------------------------------*/
 
-/* Hint type definitions. */
-enum {
-	GMS_HINT_TYPE_FOLDER = 1,
-	GMS_HINT_TYPE_TEXT = 2
-};
-
-/* Success and fail return codes from hint functions. */
-static const type8 GMS_HINT_SUCCESS = 1,
-                   GMS_HINT_ERROR = 0;
-
-/* Default window sizes for non-windowing Glk libraries. */
-static const glui32 GMS_HINT_DEFAULT_WIDTH = 72,
-                    GMS_HINT_DEFAULT_HEIGHT = 25;
-
-/*
- * Special hint nodes indicating the root hint node, and a value to signal
- * quit from hints subsystem.
- */
-static const type16 GMS_HINT_ROOT_NODE = 0,
-                    GMS_HINTS_DONE = UINT16_MAX;
-
-/* Generic hint topic for the root hints node. */
-static const char *const GMS_GENERIC_TOPIC = "Hints Menu";
-
-/*
- * Note of the interpreter's hints array.  Note that keeping its address
- * like this assumes that it's either static or heap in the interpreter.
- */
-static struct ms_hint *gms_hints = NULL;
-
-/* Details of the current hint node on display from the hints array. */
-static type16 gms_current_hint_node = 0;
-
-/*
- * Array of cursors for each hint.  The cursor indicates the current hint
- * position in a folder, and the last hint shown in text hints.  Space
- * is allocated as needed for a given set of hints, and needs to be freed
- * on interpreter exit.
- */
-static int *gms_hint_cursor = NULL;
-
-
-/*
- * gms_get_hint_max_node()
- *
- * Return the maximum hint node referred to by the tree under the given node.
- * The result is the largest index found, or node, if greater.  Because the
- * interpreter doesn't supply it, we need to uncover it the hard way.  The
- * function is recursive, and since it is a tree search, assumes that hints
- * is a tree, not a graph.
- */
-static type16 gms_get_hint_max_node(const struct ms_hint hints[], type16 node) {
+type16 Magnetic::gms_get_hint_max_node(const struct ms_hint hints_[], type16 node) {
 	const struct ms_hint *hint;
 	int index;
 	type16 max_node;
-	assert(hints);
+	assert(hints_);
 
-	hint = hints + node;
+	hint = hints_ + node;
 	max_node = node;
 
 	switch (hint->nodetype) {
@@ -2762,38 +2000,30 @@ static type16 gms_get_hint_max_node(const struct ms_hint hints[], type16 node) {
 		for (index = 0; index < hint->elcount; index++) {
 			type16 link_max;
 
-			link_max = gms_get_hint_max_node(hints, hint->links[index]);
+			link_max = gms_get_hint_max_node(hints_, hint->links[index]);
 			if (link_max > max_node)
 				max_node = link_max;
 		}
 		break;
 
 	default:
-		gms_fatal("GLK: Invalid hints node type encountered");
-		g_vm->glk_exit();
+		gms_fatal("GLK: Invalid hints_ node type encountered");
+		glk_exit();
 	}
 
 	/*
 	 * Return the largest node reference found, capped to avoid overlapping the
-	 * special end-hints value.
+	 * special end-hints_ value.
 	 */
 	return max_node < GMS_HINTS_DONE ? max_node : GMS_HINTS_DONE - 1;
 }
 
-
-/*
- * gms_get_hint_content()
- *
- * Return the content string for a given hint number within a given node.
- * This counts over 'number' ASCII NULs in the node's content, returning
- * the address of the string located this way.
- */
-static const char *gms_get_hint_content(const struct ms_hint hints[], type16 node, int number) {
+const char *Magnetic::gms_get_hint_content(const struct ms_hint hints_[], type16 node, int number) {
 	const struct ms_hint *hint;
 	int offset, index;
-	assert(hints);
+	assert(hints_);
 
-	hint = hints + node;
+	hint = hints_ + node;
 
 	/* Run through content until 'number' strings found. */
 	offset = 0;
@@ -2804,16 +2034,8 @@ static const char *gms_get_hint_content(const struct ms_hint hints[], type16 nod
 	return hint->content + offset;
 }
 
-
-/*
- * gms_get_hint_topic()
- *
- * Return the topic string for a given hint node.  This is found by searching
- * the parent node for a link to the node handed in.  For the root node, the
- * string is defaulted, since the root node has no parent.
- */
-static const char *gms_get_hint_topic(const struct ms_hint hints[], type16 node) {
-	assert(hints);
+const char *Magnetic::gms_get_hint_topic(const ms_hint hints_[], type16 node) {
+	assert(hints_);
 
 	if (node == GMS_HINT_ROOT_NODE) {
 		/* If the node is the root node, return a generic string. */
@@ -2827,12 +2049,12 @@ static const char *gms_get_hint_topic(const struct ms_hint hints[], type16 node)
 		 * Search the parent for a link to node, and use that as the hint topic;
 		 * NULL if none found.
 		 */
-		parent = hints[node].parent;
+		parent = hints_[node].parent;
 
 		topic = NULL;
-		for (index = 0; index < hints[parent].elcount; index++) {
-			if (hints[parent].links[index] == node) {
-				topic = gms_get_hint_content(hints, parent, index);
+		for (index = 0; index < hints_[parent].elcount; index++) {
+			if (hints_[parent].links[index] == node) {
+				topic = gms_get_hint_content(hints_, parent, index);
 				break;
 			}
 		}
@@ -2841,17 +2063,7 @@ static const char *gms_get_hint_topic(const struct ms_hint hints[], type16 node)
 	}
 }
 
-
-/*
- * gms_hint_open()
- *
- * If not already open, open the hints windows.  Returns TRUE if the windows
- * opened, or were already open.
- *
- * The function creates two hints windows -- a text grid on top, for menus,
- * and a text buffer below for hints.
- */
-static int gms_hint_open(void) {
+int Magnetic::gms_hint_open() {
 	if (!gms_hint_menu_window) {
 		assert(!gms_hint_text_window);
 
@@ -2859,91 +2071,64 @@ static int gms_hint_open(void) {
 		 * Open the hint menu window.  The initial size is two lines, but we'll
 		 * change this later to suit the hint.
 		 */
-		gms_hint_menu_window = g_vm->glk_window_open(gms_main_window,
+		gms_hint_menu_window = glk_window_open(gms_main_window,
 		                       winmethod_Above | winmethod_Fixed,
 		                       2, wintype_TextGrid, 0);
 		if (!gms_hint_menu_window)
-			return FALSE;
+			return false;
 
 		/*
 		 * Now open the hints text window.  This is set to be 100% of the size
 		 * of the main window, so should cover what remains of it completely.
 		 */
-		gms_hint_text_window = g_vm->glk_window_open(gms_main_window,
+		gms_hint_text_window = glk_window_open(gms_main_window,
 		                       winmethod_Above
 		                       | winmethod_Proportional,
 		                       100, wintype_TextBuffer, 0);
 		if (!gms_hint_text_window) {
-			g_vm->glk_window_close(gms_hint_menu_window, NULL);
+			glk_window_close(gms_hint_menu_window, NULL);
 			gms_hint_menu_window = NULL;
-			return FALSE;
+			return false;
 		}
 	}
 
-	return TRUE;
+	return true;
 }
 
-
-/*
- * gms_hint_close()
- *
- * If open, close the hints windows.
- */
-static void gms_hint_close(void) {
+void Magnetic::Magnetic::gms_hint_close() {
 	if (gms_hint_menu_window) {
 		assert(gms_hint_text_window);
 
-		g_vm->glk_window_close(gms_hint_menu_window, NULL);
+		glk_window_close(gms_hint_menu_window, NULL);
 		gms_hint_menu_window = NULL;
-		g_vm->glk_window_close(gms_hint_text_window, NULL);
+		glk_window_close(gms_hint_text_window, NULL);
 		gms_hint_text_window = NULL;
 	}
 }
 
-
-/*
- * gms_hint_windows_available()
- *
- * Return TRUE if hints windows are available.  If they're not, the hints
- * system will need to use alternative output methods.
- */
-static int gms_hint_windows_available(void) {
+int Magnetic::gms_hint_windows_available() {
 	return (gms_hint_menu_window && gms_hint_text_window);
 }
 
-
-/*
- * gms_hint_menu_print()
- * gms_hint_menu_header()
- * gms_hint_menu_justify()
- * gms_hint_text_print()
- * gms_hint_menutext_done()
- * gms_hint_menutext_start()
- *
- * Output functions for writing hints.  These functions will write to hints
- * windows where available, and to the main window where not.  When writing
- * to hints windows, they also take care not to line wrap in the menu window.
- * Limited formatting is available.
- */
-static void gms_hint_menu_print(int line, int column, const char *string,
-                                glui32 width, glui32 height) {
-	assert(string);
+void Magnetic::gms_hint_menu_print(int line, int column, const char *string_,
+		glui32 width, glui32 height) {
+	assert(string_);
 
 	/* Ignore the call if the text position is outside the window. */
 	if (!(line > (int)height || column > (int)width)) {
 		if (gms_hint_windows_available()) {
 			int posn, index;
 
-			g_vm->glk_window_move_cursor(gms_hint_menu_window, column, line);
-			g_vm->glk_set_window(gms_hint_menu_window);
+			glk_window_move_cursor(gms_hint_menu_window, column, line);
+			glk_set_window(gms_hint_menu_window);
 
-			/* Write until the end of the string, or the end of the window. */
+			/* Write until the end of the string_, or the end of the window. */
 			for (posn = column, index = 0;
-			        posn < (int)width && index < (int)strlen(string); posn++, index++) {
-				g_vm->glk_put_char(string[index]);
+			        posn < (int)width && index < (int)strlen(string_); posn++, index++) {
+				glk_put_char(string_[index]);
 			}
 
-			g_vm->glk_set_window(gms_main_window);
+			glk_set_window(gms_main_window);
 		} else {
 			static int current_line = 0;    /* Retained line number */
 			static int current_column = 0;  /* Retained col number */
@@ -2974,32 +2159,31 @@ static void gms_hint_menu_print(int line, int column, const char *string,
 				gms_normal_char(' ');
 
 			/*
-			 * Write characters until the end of the string, or the end of the
+			 * Write characters until the end of the string_, or the end of the
 			 * (self-imposed not-really-there) window.
 			 */
 			for (index = 0;
-			        current_column < (int)width && index < (int)strlen(string);
+			        current_column < (int)width && index < (int)strlen(string_);
 			        current_column++, index++) {
-				gms_normal_char(string[index]);
+				gms_normal_char(string_[index]);
 			}
 		}
 	}
 }
 
-static void gms_hint_menu_header(int line, const char *string,
-                                 glui32 width, glui32 height) {
+void Magnetic::gms_hint_menu_header(int line, const char *string_,
+		glui32 width, glui32 height) {
 	int posn, length;
-	assert(string);
+	assert(string_);
 
 	/* Output the text in the approximate line center. */
-	length = strlen(string);
+	length = strlen(string_);
 	posn = length < (int)width ? (width - length) / 2 : 0;
-	gms_hint_menu_print(line, posn, string, width, height);
+	gms_hint_menu_print(line, posn, string_, width, height);
 }
 
-static void gms_hint_menu_justify(int line,
-                                  const char *left_string, const char *right_string,
-                                  glui32 width, glui32 height) {
+void Magnetic::gms_hint_menu_justify(int line, const char *left_string,
+		const char *right_string, glui32 width, glui32 height) {
 	int posn, length;
 	assert(left_string && right_string);
 
@@ -3012,18 +2196,18 @@ static void gms_hint_menu_justify(int line,
 	gms_hint_menu_print(line, posn, right_string, width, height);
 }
 
-static void gms_hint_text_print(const char *string) {
-	assert(string);
+void Magnetic::gms_hint_text_print(const char *string_) {
+	assert(string_);
 
 	if (gms_hint_windows_available()) {
-		g_vm->glk_set_window(gms_hint_text_window);
-		g_vm->glk_put_string(string);
-		g_vm->glk_set_window(gms_main_window);
+		glk_set_window(gms_hint_text_window);
+		glk_put_string(string_);
+		glk_set_window(gms_main_window);
 	} else
-		gms_normal_string(string);
+		gms_normal_string(string_);
 }
 
-static void gms_hint_menutext_start(void) {
+void Magnetic::gms_hint_menutext_start() {
 	/*
 	 * Twiddle for non-windowing libraries; 'clear' the main window by writing
 	 * a null string at line 1, then a null string at line 0.  This works
@@ -3039,7 +2223,7 @@ static void gms_hint_menutext_start(void) {
 	}
 }
 
-static void gms_hint_menutext_done(void) {
+void Magnetic::gms_hint_menutext_done() {
 	/*
 	 * Twiddle for non-windowing libraries; 'clear' the main window by writing
 	 * an empty string to line zero.  For windowing Glk libraries, this function
@@ -3051,57 +2235,41 @@ static void gms_hint_menutext_done(void) {
 	}
 }
 
-
-/*
- * gms_hint_menutext_char_event()
- *
- * Request and return a character event from the hints windows.  In practice,
- * this means either of the hints windows if available, or the main window
- * if not.
- */
-static void gms_hint_menutext_char_event(event_t *event) {
+void Magnetic::gms_hint_menutext_char_event(event_t *event) {
 	assert(event);
 
 	if (gms_hint_windows_available()) {
-		g_vm->glk_request_char_event(gms_hint_menu_window);
-		g_vm->glk_request_char_event(gms_hint_text_window);
+		glk_request_char_event(gms_hint_menu_window);
+		glk_request_char_event(gms_hint_text_window);
 
 		gms_event_wait(evtype_CharInput, event);
 		assert(event->window == gms_hint_menu_window
 		       || event->window == gms_hint_text_window);
 
-		g_vm->glk_cancel_char_event(gms_hint_menu_window);
-		g_vm->glk_cancel_char_event(gms_hint_text_window);
+		glk_cancel_char_event(gms_hint_menu_window);
+		glk_cancel_char_event(gms_hint_text_window);
 	} else {
-		g_vm->glk_request_char_event(gms_main_window);
+		glk_request_char_event(gms_main_window);
 		gms_event_wait(evtype_CharInput, event);
 	}
 }
 
-
-/*
- * gms_hint_arrange_windows()
- *
- * Arrange the hints windows so that the hint menu window has the requested
- * number of lines.  Returns the actual hint menu window width and height,
- * or defaults if no hints windows are available.
- */
-static void gms_hint_arrange_windows(int requested_lines, glui32 *width, glui32 *height) {
+void Magnetic::gms_hint_arrange_windows(int requested_lines, glui32 *width, glui32 *height) {
 	if (gms_hint_windows_available()) {
 		winid_t parent;
 
 		/* Resize the hint menu window to fit the current hint. */
-		parent = g_vm->glk_window_get_parent(gms_hint_menu_window);
-		g_vm->glk_window_set_arrangement(parent,
+		parent = glk_window_get_parent(gms_hint_menu_window);
+		glk_window_set_arrangement(parent,
 		                                 winmethod_Above | winmethod_Fixed,
 		                                 requested_lines, NULL);
 
 		/* Measure, and return the size of the hint menu window. */
-		g_vm->glk_window_get_size(gms_hint_menu_window, width, height);
+		glk_window_get_size(gms_hint_menu_window, width, height);
 
 		/* Clear both the hint menu and the hint text window. */
-		g_vm->glk_window_clear(gms_hint_menu_window);
-		g_vm->glk_window_clear(gms_hint_text_window);
+		glk_window_clear(gms_hint_menu_window);
+		glk_window_clear(gms_hint_text_window);
 	} else {
 		/*
 		 * No hints windows, so default width and height.  The hints output
@@ -3114,17 +2282,11 @@ static void gms_hint_arrange_windows(int requested_lines, glui32 *width, glui32
 	}
 }
 
-
-/*
- * gms_hint_display_folder()
- *
- * Update the hints windows for the given folder hint node.
- */
-static void gms_hint_display_folder(const struct ms_hint hints[],
-                                    const int cursor[], type16 node) {
+void Magnetic::gms_hint_display_folder(const ms_hint hints_[],
+		const int cursor[], type16 node) {
 	glui32 width, height;
 	int line, index;
-	assert(hints && cursor);
+	assert(hints_ && cursor);
 
 	/*
 	 * Arrange windows to suit the hint folder.  For a folder menu window we
@@ -3132,12 +2294,12 @@ static void gms_hint_display_folder(const struct ms_hint hints[],
 	 * making a total of five additional lines.  Width and height receive the
 	 * actual menu window dimensions.
 	 */
-	gms_hint_arrange_windows(hints[node].elcount + 5, &width, &height);
+	gms_hint_arrange_windows(hints_[node].elcount + 5, &width, &height);
 
 	/* Paint in the menu header. */
 	line = 0;
 	gms_hint_menu_header(line++,
-	                     gms_get_hint_topic(hints, node),
+	                     gms_get_hint_topic(hints_, node),
 	                     width, height);
 	gms_hint_menu_justify(line++,
 	                      " N = next subject  ", "  P = previous ",
@@ -3153,12 +2315,12 @@ static void gms_hint_display_folder(const struct ms_hint hints[],
 	 * text for the selected hint is preceded by a '>' pointer.
 	 */
 	line++;
-	for (index = 0; index < hints[node].elcount; index++) {
+	for (index = 0; index < hints_[node].elcount; index++) {
 		gms_hint_menu_print(line, 3,
 		                    index == cursor[node] ? ">" : " ",
 		                    width, height);
 		gms_hint_menu_print(line++, 5,
-		                    gms_get_hint_content(hints, node, index),
+		                    gms_get_hint_content(hints_, node, index),
 		                    width, height);
 	}
 
@@ -3170,21 +2332,15 @@ static void gms_hint_display_folder(const struct ms_hint hints[],
 	gms_hint_menu_print(line, 0, " ", width, height);
 }
 
-
-/*
- * gms_hint_display_text()
- *
- * Update the hints windows for the given text hint node.
- */
-static void gms_hint_display_text(const struct ms_hint hints[],
-                                  const int cursor[], type16 node) {
+void Magnetic::gms_hint_display_text(const ms_hint hints_[],
+		const int cursor[], type16 node) {
 	glui32 width, height;
 	int line, index;
-	assert(hints && cursor);
+	assert(hints_ && cursor);
 
 	/*
 	 * Arrange windows to suit the hint text.  For a hint menu, we use a simple
-	 * two-line set of controls; everything else is in the hints text window.
+	 * two-line set of controls; everything else is in the hints_ text window.
 	 * Width and height receive the actual menu window dimensions.
 	 */
 	gms_hint_arrange_windows(2, &width, &height);
@@ -3192,66 +2348,52 @@ static void gms_hint_display_text(const struct ms_hint hints[],
 	/* Paint in a short menu header. */
 	line = 0;
 	gms_hint_menu_header(line++,
-	                     gms_get_hint_topic(hints, node),
+	                     gms_get_hint_topic(hints_, node),
 	                     width, height);
 	gms_hint_menu_justify(line++,
 	                      " RETURN = read hint  ", "  Q = previous menu ",
 	                      width, height);
 
 	/*
-	 * Output hints to the hints text window.  Hints not yet exposed are
+	 * Output hints_ to the hints_ text window.  hints_ not yet exposed are
 	 * indicated by the cursor for the hint, and are displayed as a dash.
 	 */
 	gms_hint_text_print("\n");
-	for (index = 0; index < hints[node].elcount; index++) {
-		char buffer[16];
+	for (index = 0; index < hints_[node].elcount; index++) {
+		char buf[16];
 
-		sprintf(buffer, "%3d.  ", index + 1);
-		gms_hint_text_print(buffer);
+		sprintf(buf, "%3d.  ", index + 1);
+		gms_hint_text_print(buf);
 
 		gms_hint_text_print(index < cursor[node]
-		                    ? gms_get_hint_content(hints, node, index) : "-");
+		                    ? gms_get_hint_content(hints_, node, index) : "-");
 		gms_hint_text_print("\n");
 	}
 }
 
+void Magnetic::gms_hint_display(const ms_hint hints_[], const int cursor[], type16 node) {
+	assert(hints_ && cursor);
 
-/*
- * gms_hint_display()
- *
- * Display the given hint using the appropriate display function.
- */
-static void gms_hint_display(const struct ms_hint hints[],
-                             const int cursor[], type16 node) {
-	assert(hints && cursor);
-
-	switch (hints[node].nodetype) {
+	switch (hints_[node].nodetype) {
 	case GMS_HINT_TYPE_TEXT:
-		gms_hint_display_text(hints, cursor, node);
+		gms_hint_display_text(hints_, cursor, node);
 		break;
 
 	case GMS_HINT_TYPE_FOLDER:
-		gms_hint_display_folder(hints, cursor, node);
+		gms_hint_display_folder(hints_, cursor, node);
 		break;
 
 	default:
-		gms_fatal("GLK: Invalid hints node type encountered");
-		g_vm->glk_exit();
+		gms_fatal("GLK: Invalid hints_ node type encountered");
+		glk_exit();
 	}
 }
 
-
-/*
- * gms_hint_handle_folder()
- *
- * Handle a Glk keycode for the given folder hint.  Return the next node to
- * handle, or the special end-hints on Quit at the root node.
- */
-static type16 gms_hint_handle_folder(const struct ms_hint hints[],
-                                     int cursor[], type16 node, glui32 keycode) {
+type16 Magnetic::gms_hint_handle_folder(const ms_hint hints_[],
+		int cursor[], type16 node, glui32 keycode) {
 	unsigned char response;
 	type16 next_node;
-	assert(hints && cursor);
+	assert(hints_ && cursor);
 
 	/* Convert key code into a single response character. */
 	switch (keycode) {
@@ -3270,7 +2412,7 @@ static type16 gms_hint_handle_folder(const struct ms_hint hints[],
 		response = 'Q';
 		break;
 	default:
-		response = keycode <= BYTE_MAX ? g_vm->glk_char_to_upper(keycode) : 0;
+		response = keycode <= BYTE_MAX ? glk_char_to_upper(keycode) : 0;
 		break;
 	}
 
@@ -3282,7 +2424,7 @@ static type16 gms_hint_handle_folder(const struct ms_hint hints[],
 	switch (response) {
 	case 'N':
 		/* Advance the hint cursor, wrapping at the folder end. */
-		if (cursor[node] < hints[node].elcount - 1)
+		if (cursor[node] < hints_[node].elcount - 1)
 			cursor[node]++;
 		else
 			cursor[node] = 0;
@@ -3293,18 +2435,18 @@ static type16 gms_hint_handle_folder(const struct ms_hint hints[],
 		if (cursor[node] > 0)
 			cursor[node]--;
 		else
-			cursor[node] = hints[node].elcount - 1;
+			cursor[node] = hints_[node].elcount - 1;
 		break;
 
 	case '\n':
 		/* The next node is the hint node at the cursor position. */
-		next_node = hints[node].links[cursor[node]];
+		next_node = hints_[node].links[cursor[node]];
 		break;
 
 	case 'Q':
 		/* If root, we're done; if not, next node is node's parent. */
 		next_node = node == GMS_HINT_ROOT_NODE
-		            ? GMS_HINTS_DONE : hints[node].parent;
+		            ? GMS_HINTS_DONE : hints_[node].parent;
 		break;
 
 	default:
@@ -3314,18 +2456,11 @@ static type16 gms_hint_handle_folder(const struct ms_hint hints[],
 	return next_node;
 }
 
-
-/*
- * gms_hint_handle_text()
- *
- * Handle a Glk keycode for the given text hint.  Return the next node to
- * handle.
- */
-static type16 gms_hint_handle_text(const struct ms_hint hints[],
-                                   int cursor[], type16 node, glui32 keycode) {
+type16 Magnetic::gms_hint_handle_text(const ms_hint hints_[],
+		int cursor[], type16 node, glui32 keycode) {
 	unsigned char response;
 	type16 next_node;
-	assert(hints && cursor);
+	assert(hints_ && cursor);
 
 	/* Convert key code into a single response character. */
 	switch (keycode) {
@@ -3338,7 +2473,7 @@ static type16 gms_hint_handle_text(const struct ms_hint hints[],
 		response = 'Q';
 		break;
 	default:
-		response = keycode <= BYTE_MAX ? g_vm->glk_char_to_upper(keycode) : 0;
+		response = keycode <= BYTE_MAX ? glk_char_to_upper(keycode) : 0;
 		break;
 	}
 
@@ -3350,13 +2485,13 @@ static type16 gms_hint_handle_text(const struct ms_hint hints[],
 	switch (response) {
 	case '\n':
 		/* If not at end of the hint, advance the hint cursor. */
-		if (cursor[node] < hints[node].elcount)
+		if (cursor[node] < hints_[node].elcount)
 			cursor[node]++;
 		break;
 
 	case 'Q':
 		/* Done with this hint node, so next node is its parent. */
-		next_node = hints[node].parent;
+		next_node = hints_[node].parent;
 		break;
 
 	default:
@@ -3366,71 +2501,50 @@ static type16 gms_hint_handle_text(const struct ms_hint hints[],
 	return next_node;
 }
 
-
-/*
- * gms_hint_handle()
- *
- * Handle a Glk keycode for the given hint using the appropriate handler
- * function.  Return the next node to handle.
- */
-static type16 gms_hint_handle(const struct ms_hint hints[],
-                              int cursor[], type16 node, glui32 keycode) {
+type16 Magnetic::gms_hint_handle(const ms_hint hints_[],
+		int cursor[], type16 node, glui32 keycode) {
 	type16 next_node;
-	assert(hints && cursor);
+	assert(hints_ && cursor);
 
 	next_node = GMS_HINT_ROOT_NODE;
-	switch (hints[node].nodetype) {
+	switch (hints_[node].nodetype) {
 	case GMS_HINT_TYPE_TEXT:
-		next_node = gms_hint_handle_text(hints, cursor, node, keycode);
+		next_node = gms_hint_handle_text(hints_, cursor, node, keycode);
 		break;
 
 	case GMS_HINT_TYPE_FOLDER:
-		next_node = gms_hint_handle_folder(hints, cursor, node, keycode);
+		next_node = gms_hint_handle_folder(hints_, cursor, node, keycode);
 		break;
 
 	default:
-		gms_fatal("GLK: Invalid hints node type encountered");
-		g_vm->glk_exit();
+		gms_fatal("GLK: Invalid hints_ node type encountered");
+		glk_exit();
 	}
 
 	return next_node;
 }
 
-
-/*
- * ms_showhints()
- *
- * Start game hints.  These are modal, though there's no overriding Glk
- * reason why.  It's just that this matches the way they're implemented by
- * most Inform games.  This may not be the best way of doing help, but at
- * least it's likely to be familiar, and anything more ambitious may be
- * beyond the current Glk capabilities.
- *
- * This function uses CRCs to detect any change of hints data.  Normally,
- * we'd expect none, at least within a given game run, but we can probably
- * handle it okay if it happens.
- */
-type8 ms_showhints(struct ms_hint *hints) {
-	static int is_initialized = FALSE;
+type8 Magnetic::ms_showhints(ms_hint *hints_) {
+	static int is_initialized = false;
 	static glui32 current_crc = 0;
 
 	type16 hint_count;
 	glui32 crc;
-	assert(hints);
+	assert(hints_);
 
 	/*
-	 * Find the number of hints in the array.  To do this, we'll visit every
+	 * Find the number of hints_ in the array.  To do this, we'll visit every
 	 * node in a tree search, starting at the root, to locate the maximum node
 	 * number found, then add one to that.  It's a pity that the interpreter
 	 * doesn't hand us this information directly.
 	 */
-	hint_count = gms_get_hint_max_node(hints, GMS_HINT_ROOT_NODE) + 1;
+	hint_count = gms_get_hint_max_node(hints_, GMS_HINT_ROOT_NODE) + 1;
 
 	/*
-	 * Calculate a CRC for the hints array data.  If the CRC has changed, or
+	 * Calculate a CRC for the hints_ array data.  If the CRC has changed, or
 	 * this is the first call, assign a new cursor array.
 	 */
-	crc = gms_get_buffer_crc(hints, hint_count * sizeof(*hints));
+	crc = gms_get_buffer_crc(hints_, hint_count * sizeof(*hints_));
 	if (crc != current_crc || !is_initialized) {
 		int bytes;
 
@@ -3441,22 +2555,22 @@ type8 ms_showhints(struct ms_hint *hints) {
 		memset(gms_hint_cursor, 0, bytes);
 
 		/*
-		 * Retain the hints CRC, for later comparisons, and set is_initialized
+		 * Retain the hints_ CRC, for later comparisons, and set is_initialized
 		 * flag.
 		 */
 		current_crc = crc;
-		is_initialized = TRUE;
+		is_initialized = true;
 	}
 
 	/*
-	 * Save the hints array passed in.  This is done here since even if the data
+	 * Save the hints_ array passed in.  This is done here since even if the data
 	 * remains the same (found by the CRC check above), the pointer to it might
 	 * have changed.
 	 */
-	gms_hints = hints;
+	gms_hints = hints_;
 
 	/*
-	 * Try to create the hints windows.  If they can't be created, perhaps
+	 * Try to create the hints_ windows.  If they can't be created, perhaps
 	 * because the Glk library doesn't support it, the output functions will
 	 * work around this.
 	 */
@@ -3464,8 +2578,8 @@ type8 ms_showhints(struct ms_hint *hints) {
 	gms_hint_menutext_start();
 
 	/*
-	 * Begin hints display at the root node, and navigate until the user exits
-	 * hints.
+	 * Begin hints_ display at the root node, and navigate until the user exits
+	 * hints_.
 	 */
 	gms_current_hint_node = GMS_HINT_ROOT_NODE;
 	while (gms_current_hint_node != GMS_HINTS_DONE) {
@@ -3490,32 +2604,14 @@ type8 ms_showhints(struct ms_hint *hints) {
 	return GMS_HINT_SUCCESS;
 }
 
-
-/*
- * gms_hint_redraw()
- *
- * Update the hints windows for the current hint.  This function should be
- * called from the event handler on resize events, to repaint the hints
- * display.  It does nothing if no hints windows have been opened, since
- * in this case, there's no resize action required -- either we're not in
- * the hints subsystem, or hints are being displayed in the main game
- * window, for whatever reason.
- */
-static void gms_hint_redraw(void) {
+void Magnetic::gms_hint_redraw() {
 	if (gms_hint_windows_available()) {
 		assert(gms_hints && gms_hint_cursor);
 		gms_hint_display(gms_hints, gms_hint_cursor, gms_current_hint_node);
 	}
 }
 
-
-/*
- * gms_hints_cleanup()
- *
- * Free memory resources allocated by hints functions.  Called on game
- * end.
- */
-static void gms_hints_cleanup(void) {
+void Magnetic::gms_hints_cleanup() {
 	free(gms_hint_cursor);
 	gms_hint_cursor = NULL;
 
@@ -3523,32 +2619,18 @@ static void gms_hints_cleanup(void) {
 	gms_current_hint_node = 0;
 }
 
-
-void ms_playmusic(type8 *midi_data, type32 length, type16 tempo) {
+void Magnetic::ms_playmusic(type8 *midi_data, type32 length, type16 tempo) {
 }
 
-
 /*---------------------------------------------------------------------*/
 /*  Glk command escape functions                                       */
 /*---------------------------------------------------------------------*/
 
-/*
- * gms_command_undo()
- *
- * Stub function for the undo command.  The real work is to return the
- * undo code to the input functions.
- */
-static void gms_command_undo(const char *argument) {
+void Magnetic::gms_command_undo(const char *argument) {
 	assert(argument);
 }
 
-
-/*
- * gms_command_script()
- *
- * Turn game output scripting (logging) on and off.
- */
-static void gms_command_script(const char *argument) {
+void Magnetic::gms_command_script(const char *argument) {
 	assert(argument);
 
 	if (gms_strcasecmp(argument, "on") == 0) {
@@ -3559,7 +2641,7 @@ static void gms_command_script(const char *argument) {
 			return;
 		}
 
-		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_Transcript
+		fileref = glk_fileref_create_by_prompt(fileusage_Transcript
 		          | fileusage_TextMode,
 		          filemode_WriteAppend, 0);
 		if (!fileref) {
@@ -3567,15 +2649,15 @@ static void gms_command_script(const char *argument) {
 			return;
 		}
 
-		gms_transcript_stream = g_vm->glk_stream_open_file(fileref,
+		gms_transcript_stream = glk_stream_open_file(fileref,
 		                        filemode_WriteAppend, 0);
-		g_vm->glk_fileref_destroy(fileref);
+		glk_fileref_destroy(fileref);
 		if (!gms_transcript_stream) {
 			gms_standout_string("Glk transcript failed.\n");
 			return;
 		}
 
-		g_vm->glk_window_set_echo_stream(gms_main_window, gms_transcript_stream);
+		glk_window_set_echo_stream(gms_main_window, gms_transcript_stream);
 
 		gms_normal_string("Glk transcript is now on.\n");
 	}
@@ -3586,10 +2668,10 @@ static void gms_command_script(const char *argument) {
 			return;
 		}
 
-		g_vm->glk_stream_close(gms_transcript_stream, NULL);
+		glk_stream_close(gms_transcript_stream, NULL);
 		gms_transcript_stream = NULL;
 
-		g_vm->glk_window_set_echo_stream(gms_main_window, NULL);
+		glk_window_set_echo_stream(gms_main_window, NULL);
 
 		gms_normal_string("Glk transcript is now off.\n");
 	}
@@ -3609,13 +2691,7 @@ static void gms_command_script(const char *argument) {
 	}
 }
 
-
-/*
- * gms_command_inputlog()
- *
- * Turn game input logging on and off.
- */
-static void gms_command_inputlog(const char *argument) {
+void Magnetic::gms_command_inputlog(const char *argument) {
 	assert(argument);
 
 	if (gms_strcasecmp(argument, "on") == 0) {
@@ -3626,7 +2702,7 @@ static void gms_command_inputlog(const char *argument) {
 			return;
 		}
 
-		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
+		fileref = glk_fileref_create_by_prompt(fileusage_InputRecord
 		          | fileusage_BinaryMode,
 		          filemode_WriteAppend, 0);
 		if (!fileref) {
@@ -3634,9 +2710,9 @@ static void gms_command_inputlog(const char *argument) {
 			return;
 		}
 
-		gms_inputlog_stream = g_vm->glk_stream_open_file(fileref,
+		gms_inputlog_stream = glk_stream_open_file(fileref,
 		                      filemode_WriteAppend, 0);
-		g_vm->glk_fileref_destroy(fileref);
+		glk_fileref_destroy(fileref);
 		if (!gms_inputlog_stream) {
 			gms_standout_string("Glk input logging failed.\n");
 			return;
@@ -3651,7 +2727,7 @@ static void gms_command_inputlog(const char *argument) {
 			return;
 		}
 
-		g_vm->glk_stream_close(gms_inputlog_stream, NULL);
+		glk_stream_close(gms_inputlog_stream, NULL);
 		gms_inputlog_stream = NULL;
 
 		gms_normal_string("Glk input log is now off.\n");
@@ -3672,13 +2748,7 @@ static void gms_command_inputlog(const char *argument) {
 	}
 }
 
-
-/*
- * gms_command_readlog()
- *
- * Set the game input log, to read input from a file.
- */
-static void gms_command_readlog(const char *argument) {
+void Magnetic::gms_command_readlog(const char *argument) {
 	assert(argument);
 
 	if (gms_strcasecmp(argument, "on") == 0) {
@@ -3689,7 +2759,7 @@ static void gms_command_readlog(const char *argument) {
 			return;
 		}
 
-		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
+		fileref = glk_fileref_create_by_prompt(fileusage_InputRecord
 		          | fileusage_BinaryMode,
 		          filemode_Read, 0);
 		if (!fileref) {
@@ -3697,14 +2767,14 @@ static void gms_command_readlog(const char *argument) {
 			return;
 		}
 
-		if (!g_vm->glk_fileref_does_file_exist(fileref)) {
-			g_vm->glk_fileref_destroy(fileref);
+		if (!glk_fileref_does_file_exist(fileref)) {
+			glk_fileref_destroy(fileref);
 			gms_standout_string("Glk read log failed.\n");
 			return;
 		}
 
-		gms_readlog_stream = g_vm->glk_stream_open_file(fileref, filemode_Read, 0);
-		g_vm->glk_fileref_destroy(fileref);
+		gms_readlog_stream = glk_stream_open_file(fileref, filemode_Read, 0);
+		glk_fileref_destroy(fileref);
 		if (!gms_readlog_stream) {
 			gms_standout_string("Glk read log failed.\n");
 			return;
@@ -3719,7 +2789,7 @@ static void gms_command_readlog(const char *argument) {
 			return;
 		}
 
-		g_vm->glk_stream_close(gms_readlog_stream, NULL);
+		glk_stream_close(gms_readlog_stream, NULL);
 		gms_readlog_stream = NULL;
 
 		gms_normal_string("Glk read log is now off.\n");
@@ -3740,38 +2810,32 @@ static void gms_command_readlog(const char *argument) {
 	}
 }
 
-
-/*
- * gms_command_abbreviations()
- *
- * Turn abbreviation expansions on and off.
- */
-static void gms_command_abbreviations(const char *argument) {
+void Magnetic::gms_command_abbreviations(const char *argument) {
 	assert(argument);
 
 	if (gms_strcasecmp(argument, "on") == 0) {
-		if (g_vm->gms_abbreviations_enabled) {
+		if (gms_abbreviations_enabled) {
 			gms_normal_string("Glk abbreviation expansions are already on.\n");
 			return;
 		}
 
-		g_vm->gms_abbreviations_enabled = TRUE;
+		gms_abbreviations_enabled = true;
 		gms_normal_string("Glk abbreviation expansions are now on.\n");
 	}
 
 	else if (gms_strcasecmp(argument, "off") == 0) {
-		if (!g_vm->gms_abbreviations_enabled) {
+		if (!gms_abbreviations_enabled) {
 			gms_normal_string("Glk abbreviation expansions are already off.\n");
 			return;
 		}
 
-		g_vm->gms_abbreviations_enabled = FALSE;
+		gms_abbreviations_enabled = false;
 		gms_normal_string("Glk abbreviation expansions are now off.\n");
 	}
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk abbreviation expansions are ");
-		gms_normal_string(g_vm->gms_abbreviations_enabled ? "on" : "off");
+		gms_normal_string(gms_abbreviations_enabled ? "on" : "off");
 		gms_normal_string(".\n");
 	}
 
@@ -3784,15 +2848,7 @@ static void gms_command_abbreviations(const char *argument) {
 	}
 }
 
-
-/*
- * gms_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 gms_command_graphics(const char *argument) {
+void Magnetic::gms_command_graphics(const char *argument) {
 	assert(argument);
 
 	if (!gms_graphics_possible) {
@@ -3801,12 +2857,12 @@ static void gms_command_graphics(const char *argument) {
 	}
 
 	if (gms_strcasecmp(argument, "on") == 0) {
-		if (g_vm->gms_graphics_enabled) {
+		if (gms_graphics_enabled) {
 			gms_normal_string("Glk graphics are already on.\n");
 			return;
 		}
 
-		g_vm->gms_graphics_enabled = TRUE;
+		gms_graphics_enabled = true;
 
 		/* If a picture is loaded, call the restart function to repaint it. */
 		if (gms_graphics_picture_is_available()) {
@@ -3821,7 +2877,7 @@ static void gms_command_graphics(const char *argument) {
 	}
 
 	else if (gms_strcasecmp(argument, "off") == 0) {
-		if (!g_vm->gms_graphics_enabled) {
+		if (!gms_graphics_enabled) {
 			gms_normal_string("Glk graphics are already off.\n");
 			return;
 		}
@@ -3830,7 +2886,7 @@ static void gms_command_graphics(const char *argument) {
 		 * Set graphics to disabled, and stop any graphics processing.  Close
 		 * the graphics window.
 		 */
-		g_vm->gms_graphics_enabled = FALSE;
+		gms_graphics_enabled = false;
 		gms_graphics_stop();
 		gms_graphics_close();
 
@@ -3839,25 +2895,25 @@ static void gms_command_graphics(const char *argument) {
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk graphics are available,");
-		gms_normal_string(g_vm->gms_graphics_enabled
+		gms_normal_string(gms_graphics_enabled
 		                  ? " and enabled.\n" : " but disabled.\n");
 
 		if (gms_graphics_picture_is_available()) {
 			int width, height, is_animated;
 
 			if (gms_graphics_get_picture_details(&width, &height, &is_animated)) {
-				char buffer[16];
+				char buf[16];
 
 				gms_normal_string("There is ");
 				gms_normal_string(is_animated ? "an animated" : "a");
 				gms_normal_string(" picture loaded, ");
 
-				sprintf(buffer, "%d", width);
-				gms_normal_string(buffer);
+				sprintf(buf, "%d", width);
+				gms_normal_string(buf);
 				gms_normal_string(" by ");
 
-				sprintf(buffer, "%d", height);
-				gms_normal_string(buffer);
+				sprintf(buf, "%d", height);
+				gms_normal_string(buf);
 
 				gms_normal_string(" pixels.\n");
 			}
@@ -3866,22 +2922,22 @@ static void gms_command_graphics(const char *argument) {
 		if (!gms_graphics_interpreter_enabled())
 			gms_normal_string("Interpreter graphics are disabled.\n");
 
-		if (g_vm->gms_graphics_enabled && gms_graphics_are_displayed()) {
+		if (gms_graphics_enabled && gms_graphics_are_displayed()) {
 			int color_count, is_active;
 			const char *gamma;
 
 			if (gms_graphics_get_rendering_details(&gamma, &color_count,
 			                                       &is_active)) {
-				char buffer[16];
+				char buf[16];
 
 				gms_normal_string("Graphics are ");
 				gms_normal_string(is_active ? "active, " : "displayed, ");
 
-				sprintf(buffer, "%d", color_count);
-				gms_normal_string(buffer);
+				sprintf(buf, "%d", color_count);
+				gms_normal_string(buf);
 				gms_normal_string(" colours");
 
-				if (g_vm->gms_gamma_mode == GAMMA_OFF)
+				if (gms_gamma_mode == GAMMA_OFF)
 					gms_normal_string(", without gamma correction");
 				else {
 					gms_normal_string(", with gamma ");
@@ -3893,7 +2949,7 @@ static void gms_command_graphics(const char *argument) {
 				gms_normal_string("Graphics are being displayed.\n");
 		}
 
-		if (g_vm->gms_graphics_enabled && !gms_graphics_are_displayed())
+		if (gms_graphics_enabled && !gms_graphics_are_displayed())
 			gms_normal_string("Graphics are not being displayed.\n");
 	}
 
@@ -3906,13 +2962,7 @@ static void gms_command_graphics(const char *argument) {
 	}
 }
 
-
-/*
- * gms_command_gamma()
- *
- * Enable or disable picture gamma corrections.
- */
-static void gms_command_gamma(const char *argument) {
+void Magnetic::gms_command_gamma(const char *argument) {
 	assert(argument);
 
 	if (!gms_graphics_possible) {
@@ -3921,13 +2971,13 @@ static void gms_command_gamma(const char *argument) {
 	}
 
 	if (gms_strcasecmp(argument, "high") == 0) {
-		if (g_vm->gms_gamma_mode == GAMMA_HIGH) {
+		if (gms_gamma_mode == GAMMA_HIGH) {
 			gms_normal_string("Glk automatic gamma correction mode is"
 			                  " already 'high'.\n");
 			return;
 		}
 
-		g_vm->gms_gamma_mode = GAMMA_HIGH;
+		gms_gamma_mode = GAMMA_HIGH;
 		gms_graphics_restart();
 
 		gms_normal_string("Glk automatic gamma correction mode is"
@@ -3936,13 +2986,13 @@ static void gms_command_gamma(const char *argument) {
 
 	else if (gms_strcasecmp(argument, "normal") == 0
 	         || gms_strcasecmp(argument, "on") == 0) {
-		if (g_vm->gms_gamma_mode == GAMMA_NORMAL) {
+		if (gms_gamma_mode == GAMMA_NORMAL) {
 			gms_normal_string("Glk automatic gamma correction mode is"
 			                  " already 'normal'.\n");
 			return;
 		}
 
-		g_vm->gms_gamma_mode = GAMMA_NORMAL;
+		gms_gamma_mode = GAMMA_NORMAL;
 		gms_graphics_restart();
 
 		gms_normal_string("Glk automatic gamma correction mode is"
@@ -3951,13 +3001,13 @@ static void gms_command_gamma(const char *argument) {
 
 	else if (gms_strcasecmp(argument, "none") == 0
 	         || gms_strcasecmp(argument, "off") == 0) {
-		if (g_vm->gms_gamma_mode == GAMMA_OFF) {
+		if (gms_gamma_mode == GAMMA_OFF) {
 			gms_normal_string("Glk automatic gamma correction mode is"
 			                  " already 'off'.\n");
 			return;
 		}
 
-		g_vm->gms_gamma_mode = GAMMA_OFF;
+		gms_gamma_mode = GAMMA_OFF;
 		gms_graphics_restart();
 
 		gms_normal_string("Glk automatic gamma correction mode is"
@@ -3966,7 +3016,7 @@ static void gms_command_gamma(const char *argument) {
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk automatic gamma correction mode is '");
-		switch (g_vm->gms_gamma_mode) {
+		switch (gms_gamma_mode) {
 		case GAMMA_OFF:
 			gms_normal_string("off");
 			break;
@@ -3991,13 +3041,7 @@ static void gms_command_gamma(const char *argument) {
 	}
 }
 
-
-/*
- * gms_command_animations()
- *
- * Enable or disable picture animations.
- */
-static void gms_command_animations(const char *argument) {
+void Magnetic::gms_command_animations(const char *argument) {
 	assert(argument);
 
 	if (!gms_graphics_possible) {
@@ -4008,7 +3052,7 @@ static void gms_command_animations(const char *argument) {
 	if (gms_strcasecmp(argument, "on") == 0) {
 		int is_animated;
 
-		if (g_vm->gms_animation_enabled) {
+		if (gms_animation_enabled) {
 			gms_normal_string("Glk graphics animations are already on.\n");
 			return;
 		}
@@ -4018,7 +3062,7 @@ static void gms_command_animations(const char *argument) {
 		 * is animated; if it isn't, we can leave it displayed as is, since
 		 * changing animation mode doesn't affect this picture.
 		 */
-		g_vm->gms_animation_enabled = TRUE;
+		gms_animation_enabled = true;
 		if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
 			if (is_animated)
 				gms_graphics_restart();
@@ -4030,12 +3074,12 @@ static void gms_command_animations(const char *argument) {
 	else if (gms_strcasecmp(argument, "off") == 0) {
 		int is_animated;
 
-		if (!g_vm->gms_animation_enabled) {
+		if (!gms_animation_enabled) {
 			gms_normal_string("Glk graphics animations are already off.\n");
 			return;
 		}
 
-		g_vm->gms_animation_enabled = FALSE;
+		gms_animation_enabled = false;
 		if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
 			if (is_animated)
 				gms_graphics_restart();
@@ -4046,7 +3090,7 @@ static void gms_command_animations(const char *argument) {
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk graphics animations are ");
-		gms_normal_string(g_vm->gms_animation_enabled ? "on" : "off");
+		gms_normal_string(gms_animation_enabled ? "on" : "off");
 		gms_normal_string(".\n");
 	}
 
@@ -4059,22 +3103,16 @@ static void gms_command_animations(const char *argument) {
 	}
 }
 
-
-/*
- * gms_command_prompts()
- *
- * Turn the extra "> " prompt output on and off.
- */
-static void gms_command_prompts(const char *argument) {
+void Magnetic::gms_command_prompts(const char *argument) {
 	assert(argument);
 
 	if (gms_strcasecmp(argument, "on") == 0) {
-		if (g_vm->gms_prompt_enabled) {
+		if (gms_prompt_enabled) {
 			gms_normal_string("Glk extra prompts are already on.\n");
 			return;
 		}
 
-		g_vm->gms_prompt_enabled = TRUE;
+		gms_prompt_enabled = true;
 		gms_normal_string("Glk extra prompts are now on.\n");
 
 		/* Check for a game prompt to clear the flag. */
@@ -4082,18 +3120,18 @@ static void gms_command_prompts(const char *argument) {
 	}
 
 	else if (gms_strcasecmp(argument, "off") == 0) {
-		if (!g_vm->gms_prompt_enabled) {
+		if (!gms_prompt_enabled) {
 			gms_normal_string("Glk extra prompts are already off.\n");
 			return;
 		}
 
-		g_vm->gms_prompt_enabled = FALSE;
+		gms_prompt_enabled = false;
 		gms_normal_string("Glk extra prompts are now off.\n");
 	}
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk extra prompts are ");
-		gms_normal_string(g_vm->gms_prompt_enabled ? "on" : "off");
+		gms_normal_string(gms_prompt_enabled ? "on" : "off");
 		gms_normal_string(".\n");
 	}
 
@@ -4106,46 +3144,29 @@ static void gms_command_prompts(const char *argument) {
 	}
 }
 
-
-/*
- * gms_command_print_version_number()
- * gms_command_version()
- *
- * Print out the Glk library version number.
- */
-static void gms_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);
-	gms_normal_string(buffer);
+void Magnetic::gms_command_print_version_number(glui32 version_) {
+	Common::String str = Common::String::format("%lu.%lu.%lu",
+	        (unsigned long)version_ >> 16,
+	        (unsigned long)(version_ >> 8) & 0xff,
+	        (unsigned long)version_ & 0xff);
+	gms_normal_string(str.c_str());
 }
 
-static void
-gms_command_version(const char *argument) {
-	glui32 version;
+void Magnetic::gms_command_version(const char *argument) {
+	glui32 version_;
 	assert(argument);
 
-	gms_normal_string("This is version ");
+	gms_normal_string("This is version_ ");
 	gms_command_print_version_number(GMS_PORT_VERSION);
 	gms_normal_string(" of the Glk Magnetic port.\n");
 
-	version = g_vm->glk_gestalt(gestalt_Version, 0);
-	gms_normal_string("The Glk library version is ");
-	gms_command_print_version_number(version);
+	version_ = glk_gestalt(gestalt_Version, 0);
+	gms_normal_string("The Glk library version_ is ");
+	gms_command_print_version_number(version_);
 	gms_normal_string(".\n");
 }
 
-
-/*
- * gms_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 gms_command_commands(const char *argument) {
+void Magnetic::gms_command_commands(const char *argument) {
 	assert(argument);
 
 	if (gms_strcasecmp(argument, "on") == 0) {
@@ -4153,13 +3174,13 @@ static void gms_command_commands(const char *argument) {
 	}
 
 	else if (gms_strcasecmp(argument, "off") == 0) {
-		g_vm->gms_commands_enabled = FALSE;
+		gms_commands_enabled = false;
 		gms_normal_string("Glk commands are now off.\n");
 	}
 
 	else if (strlen(argument) == 0) {
 		gms_normal_string("Glk commands are ");
-		gms_normal_string(g_vm->gms_commands_enabled ? "on" : "off");
+		gms_normal_string(gms_commands_enabled ? "on" : "off");
 		gms_normal_string(".\n");
 	}
 
@@ -4172,43 +3193,8 @@ static void gms_command_commands(const char *argument) {
 	}
 }
 
-/* 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. */
-	const int undo_return;                          /* "Undo" return value. */
-} gms_command_t;
-typedef gms_command_t *gms_commandref_t;
-
-static void gms_command_summary(const char *argument);
-static void gms_command_help(const char *argument);
-
-static gms_command_t GMS_COMMAND_TABLE[] = {
-	{"summary",        gms_command_summary,        FALSE, FALSE},
-	{"undo",           gms_command_undo,           FALSE, TRUE},
-	{"script",         gms_command_script,         TRUE,  FALSE},
-	{"inputlog",       gms_command_inputlog,       TRUE,  FALSE},
-	{"readlog",        gms_command_readlog,        TRUE,  FALSE},
-	{"abbreviations",  gms_command_abbreviations,  TRUE,  FALSE},
-	{"graphics",       gms_command_graphics,       TRUE,  FALSE},
-	{"gamma",          gms_command_gamma,          TRUE,  FALSE},
-	{"animations",     gms_command_animations,     TRUE,  FALSE},
-	{"prompts",        gms_command_prompts,        TRUE,  FALSE},
-	{"version",        gms_command_version,        FALSE, FALSE},
-	{"commands",       gms_command_commands,       TRUE,  FALSE},
-	{"help",           gms_command_help,           TRUE,  FALSE},
-	{NULL, NULL, FALSE, FALSE}
-};
-
-
-/*
- * gms_command_summary()
- *
- * Report all current Glk settings.
- */
-static void gms_command_summary(const char *argument) {
-	gms_commandref_t entry;
+void Magnetic::gms_command_summary(const char *argument) {
+	const gms_command_t *entry;
 	assert(argument);
 
 	/*
@@ -4216,29 +3202,23 @@ static void gms_command_summary(const char *argument) {
 	 * prompting each to print its current setting.
 	 */
 	for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
-		if (entry->handler == gms_command_summary
-		        || entry->handler == gms_command_undo
-		        || entry->handler == gms_command_help)
+		if (entry->handler == &Magnetic::gms_command_summary
+		        || entry->handler == &Magnetic::gms_command_undo
+		        || entry->handler == &Magnetic::gms_command_help)
 			continue;
 
-		entry->handler("");
+		(this->*entry->handler)("");
 	}
 }
 
-
-/*
- * gms_command_help()
- *
- * Document the available Glk commands.
- */
-static void gms_command_help(const char *command) {
-	gms_commandref_t entry, matched;
+void Magnetic::gms_command_help(const char *command) {
+	const gms_command_t *entry, *matched;
 	assert(command);
 
 	if (strlen(command) == 0) {
 		gms_normal_string("Glk commands are");
 		for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
-			gms_commandref_t next;
+			const gms_command_t *next;
 
 			next = entry + 1;
 			gms_normal_string(next->command ? " " : " and ");
@@ -4277,17 +3257,17 @@ static void gms_command_help(const char *command) {
 		return;
 	}
 
-	if (matched->handler == gms_command_summary) {
+	if (matched->handler == &Magnetic::gms_command_summary) {
 		gms_normal_string("Prints a summary of all the current Glk Magnetic"
 		                  " settings.\n");
 	}
 
-	else if (matched->handler == gms_command_undo) {
+	else if (matched->handler == &Magnetic::gms_command_undo) {
 		gms_normal_string("Undoes a single game turn.\n\nEquivalent to the"
 		                  " standalone game 'undo' command.\n");
 	}
 
-	else if (matched->handler == gms_command_script) {
+	else if (matched->handler == &Magnetic::gms_command_script) {
 		gms_normal_string("Logs the game's output to a file.\n\nUse ");
 		gms_standout_string("glk script on");
 		gms_normal_string(" to begin logging game output, and ");
@@ -4296,7 +3276,7 @@ static void gms_command_help(const char *command) {
 		                  " when you turn scripts on.\n");
 	}
 
-	else if (matched->handler == gms_command_inputlog) {
+	else if (matched->handler == &Magnetic::gms_command_inputlog) {
 		gms_normal_string("Records the commands you type into a game.\n\nUse ");
 		gms_standout_string("glk inputlog on");
 		gms_normal_string(", to begin recording your commands, and ");
@@ -4307,7 +3287,7 @@ static void gms_command_help(const char *command) {
 		gms_normal_string(" command.\n");
 	}
 
-	else if (matched->handler == gms_command_readlog) {
+	else if (matched->handler == &Magnetic::gms_command_readlog) {
 		gms_normal_string("Plays back commands recorded with ");
 		gms_standout_string("glk inputlog on");
 		gms_normal_string(".\n\nUse ");
@@ -4317,7 +3297,7 @@ static void gms_command_help(const char *command) {
 		                  " text file created using any standard editor.\n");
 	}
 
-	else if (matched->handler == gms_command_abbreviations) {
+	else if (matched->handler == &Magnetic::gms_command_abbreviations) {
 		gms_normal_string("Controls abbreviation expansion.\n\nGlk Magnetic"
 		                  " automatically expands several standard single"
 		                  " letter abbreviations for you; for example, \"x\""
@@ -4331,7 +3311,7 @@ static void gms_command_help(const char *command) {
 		                  " single quote.\n");
 	}
 
-	else if (matched->handler == gms_command_graphics) {
+	else if (matched->handler == &Magnetic::gms_command_graphics) {
 		gms_normal_string("Turns interpreter graphics on and off.\n\nUse ");
 		gms_standout_string("glk graphics on");
 		gms_normal_string(" to enable interpreter graphics, and ");
@@ -4346,7 +3326,7 @@ static void gms_command_help(const char *command) {
 		                  " interpreter.\n");
 	}
 
-	else if (matched->handler == gms_command_gamma) {
+	else if (matched->handler == &Magnetic::gms_command_gamma) {
 		gms_normal_string("Sets the level of automatic gamma correction applied"
 		                  " to game graphics.\n\nUse ");
 		gms_standout_string("glk gamma normal");
@@ -4359,7 +3339,7 @@ static void gms_command_help(const char *command) {
 		gms_normal_string(" to turn off all automatic gamma correction.\n");
 	}
 
-	else if (matched->handler == gms_command_animations) {
+	else if (matched->handler == &Magnetic::gms_command_animations) {
 		gms_normal_string("Turns graphic animations on and off.\n\nUse ");
 		gms_standout_string("glk animation on");
 		gms_normal_string(" to enable animations, or ");
@@ -4371,7 +3351,7 @@ static void gms_command_help(const char *command) {
 		                  " game's pictures.\n");
 	}
 
-	else if (matched->handler == gms_command_prompts) {
+	else if (matched->handler == &Magnetic::gms_command_prompts) {
 		gms_normal_string("Controls extra input prompting.\n\n"
 		                  "Glk Magnetic can issue a replacement '>' input"
 		                  " prompt if it detects that the game hasn't prompted"
@@ -4382,12 +3362,12 @@ static void gms_command_help(const char *command) {
 		gms_normal_string(" to turn it off.\n");
 	}
 
-	else if (matched->handler == gms_command_version) {
+	else if (matched->handler == &Magnetic::gms_command_version) {
 		gms_normal_string("Prints the version numbers of the Glk library"
 		                  " and the Glk Magnetic port.\n");
 	}
 
-	else if (matched->handler == gms_command_commands) {
+	else if (matched->handler == &Magnetic::gms_command_commands) {
 		gms_normal_string("Turn off Glk commands.\n\nUse ");
 		gms_standout_string("glk commands off");
 		gms_normal_string(" to disable all Glk commands, including this one."
@@ -4395,7 +3375,7 @@ static void gms_command_help(const char *command) {
 		                  " commands back on while inside the game.\n");
 	}
 
-	else if (matched->handler == gms_command_help)
+	else if (matched->handler == &Magnetic::gms_command_help)
 		gms_command_help("");
 
 	else
@@ -4403,32 +3383,22 @@ static void gms_command_help(const char *command) {
 		                  "  Sorry.\n");
 }
 
-
-/*
- * gms_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.
- *
- * On unambiguous returns, it will also set the value for undo_command to the
- * table undo return value.
- */
-static int gms_command_escape(const char *string, int *undo_command) {
+int Magnetic::gms_command_escape(const char *string_, int *undo_command) {
 	int posn;
 	char *string_copy, *command, *argument;
-	assert(string && undo_command);
+	assert(string_ && undo_command);
 
 	/*
-	 * Return FALSE if the string doesn't begin with the Glk command escape
+	 * Return false if the string doesn't begin with the Glk command escape
 	 * introducer.
 	 */
-	posn = strspn(string, "\t ");
-	if (gms_strncasecmp(string + posn, "glk", strlen("glk")) != 0)
-		return FALSE;
+	posn = strspn(string_, "\t ");
+	if (gms_strncasecmp(string_ + posn, "glk", strlen("glk")) != 0)
+		return false;
 
-	/* Take a copy of the string, without any leading space or introducer. */
-	string_copy = (char *)gms_malloc(strlen(string + posn) + 1 - strlen("glk"));
-	strcpy(string_copy, string + posn + strlen("glk"));
+	/* Take a copy of the string_, without any leading space or introducer. */
+	string_copy = (char *)gms_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,
@@ -4455,11 +3425,11 @@ static int gms_command_escape(const char *string, int *undo_command) {
 	 * as "help".
 	 */
 	if (strlen(command) > 0) {
-		gms_commandref_t entry, matched;
+		const gms_command_t *entry, *matched;
 		int matches;
 
 		/*
-		 * Search for the first unambiguous table command string matching
+		 * Search for the first unambiguous table command string_ matching
 		 * the command passed in.
 		 */
 		matches = 0;
@@ -4475,7 +3445,7 @@ static int gms_command_escape(const char *string, int *undo_command) {
 		if (matches == 1) {
 			if (!matched->undo_return)
 				gms_normal_char('\n');
-			matched->handler(argument);
+			(this->*(matched->handler))(argument);
 
 			if (!matched->takes_argument && strlen(argument) > 0) {
 				gms_normal_string("[The ");
@@ -4501,91 +3471,48 @@ static int gms_command_escape(const char *string, int *undo_command) {
 		gms_command_help("");
 	}
 
-	/* The string contained a Glk command; return TRUE. */
+	/* The string_ contained a Glk command; return true. */
 	free(string_copy);
-	return TRUE;
+	return true;
 }
 
-
-/*
- * gms_command_undo_special()
- *
- * This function makes a special case of the input line containing the single
- * word "undo", treating it as if it is "glk undo".  This makes life a bit
- * more convenient for the player, since it's the same behavior that most
- * other IF systems have.  It returns TRUE if "undo" found, FALSE otherwise.
- */
-static int gms_command_undo_special(const char *string) {
+int Magnetic::gms_command_undo_special(const char *string_) {
 	int posn, end;
-	assert(string);
+	assert(string_);
 
-	/* Find the start and end of the first string word. */
-	posn = strspn(string, "\t ");
-	end = posn + strcspn(string + posn, "\t ");
+	/* Find the start and end of the first string_ word. */
+	posn = strspn(string_, "\t ");
+	end = posn + strcspn(string_ + posn, "\t ");
 
-	/* See if string contains an "undo" request, with nothing following. */
+	/* See if string_ contains an "undo" request, with nothing following. */
 	if (end - posn == (int)strlen("undo")
-	        && gms_strncasecmp(string + posn, "undo", end - posn) == 0) {
-		posn = end + strspn(string + end, "\t ");
-		if (string[posn] == '\0')
-			return TRUE;
+	        && gms_strncasecmp(string_ + posn, "undo", end - posn) == 0) {
+		posn = end + strspn(string_ + end, "\t ");
+		if (string_[posn] == '\0')
+			return true;
 	}
 
-	return FALSE;
+	return false;
 }
 
-
 /*---------------------------------------------------------------------*/
 /*  Glk port input functions                                           */
 /*---------------------------------------------------------------------*/
 
-/*
- * Input buffer allocated for reading input lines.  The buffer is filled
- * from either an input log, if one is currently being read, or from Glk
- * line input.  We also need an "undo" notification flag.
- */
-enum { GMS_INPUTBUFFER_LENGTH = 256 };
-static char gms_input_buffer[GMS_INPUTBUFFER_LENGTH];
-static int gms_input_length = 0,
-           gms_input_cursor = 0,
-           gms_undo_notification = FALSE;
-
-/* Table of single-character command abbreviations. */
-typedef const struct {
-	const char abbreviation;       /* Abbreviation character. */
-	const char *const expansion;   /* Expansion string. */
-} gms_abbreviation_t;
-typedef gms_abbreviation_t *gms_abbreviationref_t;
-
-static gms_abbreviation_t GMS_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}
-};
-
-
-/*
- * gms_expand_abbreviations()
- *
- * Expand a few common one-character abbreviations commonly found in other
- * game systems, but not always normal in Magnetic Scrolls games.
- */
-static void gms_expand_abbreviations(char *buffer, int size) {
+void Magnetic::gms_expand_abbreviations(char *buffer_, int size) {
 	char *command, abbreviation;
 	const char *expansion;
 	gms_abbreviationref_t entry;
-	assert(buffer);
+	assert(buffer_);
 
 	/* Ignore anything that isn't a single letter command. */
-	command = buffer + strspn(buffer, "\t ");
+	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]);
+	abbreviation = glk_char_to_lower((unsigned char) command[0]);
 	expansion = NULL;
 	for (entry = GMS_ABBREVIATIONS; entry->expansion; entry++) {
 		if (entry->abbreviation == abbreviation) {
@@ -4599,7 +3526,7 @@ static void gms_expand_abbreviations(char *buffer, int size) {
 	 * expansion string.
 	 */
 	if (expansion) {
-		if ((int)(strlen(buffer) + strlen(expansion)) - 1 >= size)
+		if ((int)(strlen(buffer_) + strlen(expansion)) - 1 >= size)
 			return;
 
 		memmove(command + strlen(expansion) - 1, command, strlen(command) + 1);
@@ -4615,20 +3542,7 @@ static void gms_expand_abbreviations(char *buffer, int size) {
 	}
 }
 
-
-/*
- * gms_buffer_input
- *
- * Read and buffer a line of input.  If there is an input log active, then
- * data is taken by reading this first.  Otherwise, the function gets a
- * line from Glk.
- *
- * It also makes special cases of some lines read from the user, either
- * handling commands inside them directly, or expanding abbreviations as
- * appropriate.  This is not reflected in the buffer, which is adjusted as
- * required before returning.
- */
-static void gms_buffer_input(void) {
+void Magnetic::gms_buffer_input() {
 	event_t event;
 
 	/*
@@ -4645,7 +3559,7 @@ static void gms_buffer_input(void) {
 	 * To slightly improve things, if it looks like we didn't get a prompt from
 	 * the game, do our own.
 	 */
-	if (g_vm->gms_prompt_enabled && !gms_game_prompted()) {
+	if (gms_prompt_enabled && !gms_game_prompted()) {
 		gms_normal_char('\n');
 		gms_normal_string(GMS_INPUT_PROMPT);
 	}
@@ -4658,13 +3572,13 @@ static void gms_buffer_input(void) {
 		glui32 chars;
 
 		/* Get the next line from the log stream. */
-		chars = g_vm->glk_get_line_stream(gms_readlog_stream,
+		chars = glk_get_line_stream(gms_readlog_stream,
 		                                  gms_input_buffer, sizeof(gms_input_buffer));
 		if (chars > 0) {
 			/* Echo the line just read in input style. */
-			g_vm->glk_set_style(style_Input);
-			g_vm->glk_put_buffer(gms_input_buffer, chars);
-			g_vm->glk_set_style(style_Normal);
+			glk_set_style(style_Input);
+			glk_put_buffer(gms_input_buffer, chars);
+			glk_set_style(style_Normal);
 
 			/* Note how many characters buffered, and return. */
 			gms_input_length = chars;
@@ -4675,7 +3589,7 @@ static void gms_buffer_input(void) {
 		 * 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(gms_readlog_stream, NULL);
+		glk_stream_close(gms_readlog_stream, NULL);
 		gms_readlog_stream = NULL;
 	}
 
@@ -4683,11 +3597,11 @@ static void gms_buffer_input(void) {
 	 * No input log being read, or we just hit the end of file on one.  Revert
 	 * to normal line input; start by getting a new line from Glk.
 	 */
-	g_vm->glk_request_line_event(gms_main_window,
+	glk_request_line_event(gms_main_window,
 	                             gms_input_buffer, sizeof(gms_input_buffer) - 1, 0);
 	gms_event_wait(evtype_LineInput, &event);
-	if (g_vm->shouldQuit()) {
-		g_vm->glk_cancel_line_event(gms_main_window, &event);
+	if (shouldQuit()) {
+		glk_cancel_line_event(gms_main_window, &event);
 		return;
 	}
 
@@ -4699,15 +3613,15 @@ static void gms_buffer_input(void) {
 	if (gms_command_undo_special(gms_input_buffer)) {
 		/* Write the "undo" to any input log. */
 		if (gms_inputlog_stream) {
-			g_vm->glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
-			g_vm->glk_put_char_stream(gms_inputlog_stream, '\n');
+			glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
+			glk_put_char_stream(gms_inputlog_stream, '\n');
 		}
 
 		/* Overwrite buffer with an empty line if we saw "undo". */
 		gms_input_buffer[0] = '\n';
 		gms_input_length = 1;
 
-		gms_undo_notification = TRUE;
+		gms_undo_notification = true;
 		return;
 	}
 
@@ -4715,7 +3629,7 @@ static void gms_buffer_input(void) {
 	 * If neither abbreviations nor local commands are enabled, use the data
 	 * read above without further massaging.
 	 */
-	if (g_vm->gms_abbreviations_enabled || g_vm->gms_commands_enabled) {
+	if (gms_abbreviations_enabled || gms_commands_enabled) {
 		char *command;
 
 		/*
@@ -4729,7 +3643,7 @@ static void gms_buffer_input(void) {
 			memmove(command, command + 1, strlen(command));
 		} else {
 			/* Check for, and expand, any abbreviated commands. */
-			if (g_vm->gms_abbreviations_enabled) {
+			if (gms_abbreviations_enabled) {
 				gms_expand_abbreviations(gms_input_buffer,
 				                         sizeof(gms_input_buffer));
 			}
@@ -4739,7 +3653,7 @@ static void gms_buffer_input(void) {
 			 * suppress the interpreter's use of this input for Glk commands
 			 * by overwriting the line with a single newline character.
 			 */
-			if (g_vm->gms_commands_enabled) {
+			if (gms_commands_enabled) {
 				int posn;
 
 				posn = strspn(gms_input_buffer, "\t ");
@@ -4768,8 +3682,8 @@ static void gms_buffer_input(void) {
 	 * special commands, nor any input read from a current open input log.
 	 */
 	if (gms_inputlog_stream) {
-		g_vm->glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
-		g_vm->glk_put_char_stream(gms_inputlog_stream, '\n');
+		glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
+		glk_put_char_stream(gms_inputlog_stream, '\n');
 	}
 
 	/*
@@ -4784,15 +3698,7 @@ static void gms_buffer_input(void) {
 	gms_input_length = strlen(gms_input_buffer);
 }
 
-
-/*
- * ms_getchar()
- *
- * Return the single next character to the interpreter.  This function
- * extracts characters from the input buffer until empty, when it then
- * tries to buffer more data.
- */
-type8 ms_getchar(type8 trans) {
+type8 Magnetic::ms_getchar(type8 trans) {
 	/* See if we are at the end of the input buffer. */
 	if (gms_input_cursor == gms_input_length) {
 		/*
@@ -4802,7 +3708,7 @@ type8 ms_getchar(type8 trans) {
 		gms_buffer_input();
 		gms_input_cursor = 0;
 
-		if (g_vm->shouldQuit())
+		if (shouldQuit())
 			return '\0';
 		
 		if (gms_undo_notification) {
@@ -4810,7 +3716,7 @@ type8 ms_getchar(type8 trans) {
 			 * Clear the undo notification, and discard buffered input (usually
 			 * just the '\n' placed there when the undo command was recognized).
 			 */
-			gms_undo_notification = FALSE;
+			gms_undo_notification = false;
 			gms_input_length = 0;
 
 			/*
@@ -4826,59 +3732,15 @@ type8 ms_getchar(type8 trans) {
 	return gms_input_buffer[gms_input_cursor++];
 }
 
-#if 0
-/*
- * gms_confirm()
- *
- * Print a confirmation prompt, and read a single input character, taking
- * only [YyNn] input.  If the character is 'Y' or 'y', return TRUE.
- */
-static int gms_confirm(const char *prompt) {
-	event_t event;
-	unsigned char response;
-	assert(prompt);
-
-	/*
-	 * Print the confirmation prompt, in a style that hints that it's from the
-	 * interpreter, not the game.
-	 */
-	gms_standout_string(prompt);
-
-	/* Wait for a single 'Y' or 'N' character response. */
-	response = ' ';
-	do {
-		g_vm->glk_request_char_event(gms_main_window);
-		gms_event_wait(evtype_CharInput, &event);
-
-		if (event.val1 <= BYTE_MAX)
-			response = g_vm->glk_char_to_upper(event.val1);
-	} while (!(response == 'Y' || response == 'N'));
-
-	/* Echo the confirmation response, and a blank line. */
-	g_vm->glk_set_style(style_Input);
-	g_vm->glk_put_string(response == 'Y' ? "Yes" : "No");
-	g_vm->glk_set_style(style_Normal);
-	g_vm->glk_put_string("\n\n");
-
-	return response == 'Y';
-}
-#endif
-
 /*---------------------------------------------------------------------*/
 /*  Glk port event functions                                           */
 /*---------------------------------------------------------------------*/
 
-/*
- * gms_event_wait()
- *
- * Process Glk events until one of the expected type arrives.  Return
- * the event of that type.
- */
-static void gms_event_wait(glui32 wait_type, event_t *event) {
+void Magnetic::gms_event_wait(glui32 wait_type, event_t *event) {
 	assert(event);
 
 	do {
-		g_vm->glk_select(event);
+		glk_select(event);
 
 		switch (event->type) {
 		case evtype_Arrange:
@@ -4907,65 +3769,28 @@ static void gms_event_wait(glui32 wait_type, event_t *event) {
 /*  Functions intercepted by link-time wrappers                        */
 /*---------------------------------------------------------------------*/
 
-/*
- * __wrap_toupper()
- * __wrap_tolower()
- *
- * Wrapper functions around toupper() and tolower().  The Linux linker's
- * --wrap option will convert calls to mumble() to __wrap_mumble() if we
- * give it the right options.  We'll use this feature to translate all
- * toupper() and tolower() calls in the interpreter code into calls to
- * Glk's versions of these functions.
- *
- * It's not critical that we do this.  If a linker, say a non-Linux one,
- * won't do --wrap, then just do without it.  It's unlikely that there
- * will be much noticeable difference.
- */
-int __wrap_toupper(int ch) {
+int Magnetic::__wrap_toupper(int ch) {
 	unsigned char uch;
 
-	uch = g_vm->glk_char_to_upper((unsigned char) ch);
+	uch = glk_char_to_upper((unsigned char) ch);
 	return (int) uch;
 }
 
-int __wrap_tolower(int ch) {
+int Magnetic::__wrap_tolower(int ch) {
 	unsigned char lch;
 
-	lch = g_vm->glk_char_to_lower((unsigned char) ch);
+	lch = glk_char_to_lower((unsigned char) ch);
 	return (int) lch;
 }
 
-
 /*---------------------------------------------------------------------*/
 /*  main() and options parsing                                         */
 /*---------------------------------------------------------------------*/
 
-/*
- * The following values need to be passed between the startup_code and main
- * functions.
- */
-static const char *gms_game_message = NULL;  /* Error message. */
-
-
-/*
- * gms_establish_filenames()
- *
- * Given a game name, try to establish three filenames from it - the main game
- * text file, the (optional) graphics data file, and the (optional) hints
- * file.  Given an input "file" X, the function looks for X.MAG or X.mag for
- * game data, X.GFX or X.gfx for graphics, and X.HNT or X.hnt for hints.
- * If the input file already ends with .MAG, .GFX, or .HNT, the extension
- * is stripped first.
- *
- * The function returns NULL for filenames not available.  It's not fatal if
- * the graphics filename or hints filename is NULL, but it is if the main game
- * filename is NULL.  Filenames are malloc'ed, and need to be freed by the
- * caller.
- */
-static void gms_establish_filenames(const char *name, char **text, char **graphics, char **hints) {
+void Magnetic::gms_establish_filenames(const char *name, char **text, char **graphics, char **hints_) {
 	char *base, *text_file, *graphics_file, *hints_file;
 	Common::File stream;
-	assert(name && text && graphics && hints);
+	assert(name && text && graphics && hints_);
 
 	/* Take a destroyable copy of the input filename. */
 	base = (char *)gms_malloc(strlen(name) + 1);
@@ -4994,11 +3819,11 @@ static void gms_establish_filenames(const char *name, char **text, char **graphi
 		if (!stream.open(text_file)) {
 			/*
 			 * No access to a usable game text file.  Return immediately,
-			 * without looking for any associated graphics or hints files.
+			 * without looking for any associated graphics or hints_ files.
 			 */
 			*text = NULL;
 			*graphics = NULL;
-			*hints = NULL;
+			*hints_ = NULL;
 
 			free(text_file);
 			free(base);
@@ -5030,7 +3855,7 @@ static void gms_establish_filenames(const char *name, char **text, char **graphi
 	}
 	stream.close();
 
-	/* Now allocate space for the return hints file. */
+	/* Now allocate space for the return hints_ file. */
 	hints_file = (char *)gms_malloc(strlen(base) + strlen(".HNT") + 1);
 
 	/* As above, form a candidate graphics file, using a .HNT extension. */
@@ -5044,8 +3869,8 @@ static void gms_establish_filenames(const char *name, char **text, char **graphi
 
 		if (!stream.open(hints_file)) {
 			/*
-			 * No access to any hints file.  In this case, free memory and
-			 * reset hints file to NULL.
+			 * No access to any hints_ file.  In this case, free memory and
+			 * reset hints_ file to NULL.
 			 */
 			free(hints_file);
 			hints_file = NULL;
@@ -5053,35 +3878,35 @@ static void gms_establish_filenames(const char *name, char **text, char **graphi
 	}
 	stream.close();
 
-	/* Return the text file, and graphics and hints, which may be NULL. */
+	/* Return the text file, and graphics and hints_, which may be NULL. */
 	*text = text_file;
 	*graphics = graphics_file;
-	*hints = hints_file;
+	*hints_ = hints_file;
 
 	free(base);
 }
 
-void gms_main() {
+void Magnetic::gms_main() {
 	char *text_file = NULL, *graphics_file = NULL, *hints_file = NULL;
 	int ms_init_status, is_running;
 
 	/* Create the main Glk window, and set its stream as current. */
-	gms_main_window = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
+	gms_main_window = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
 	if (!gms_main_window) {
 		gms_fatal("GLK: Can't open main window");
-		g_vm->glk_exit();
+		glk_exit();
 		return;
 	}
-	g_vm->glk_window_clear(gms_main_window);
-	g_vm->glk_set_window(gms_main_window);
-	g_vm->glk_set_style(style_Normal);
+	glk_window_clear(gms_main_window);
+	glk_set_window(gms_main_window);
+	glk_set_style(style_Normal);
 
 	/*
 	 * Given the basic game name, try to come up with usable text, graphics,
 	 * and hints filenames.  The graphics and hints files may be null, but the
 	 * text file may not.
 	 */
-	Common::String gameFile = g_vm->getFilename();
+	Common::String gameFile = getFilename();
 	gms_establish_filenames(gameFile.c_str(), &text_file, &graphics_file, &hints_file);
 
 	/* Set the possibility of pictures depending on graphics file. */
@@ -5091,10 +3916,10 @@ void gms_main() {
 		 * the library can't offer both graphics and timers.  We need timers to
 		 * create the background "thread" for picture updates.
 		 */
-		gms_graphics_possible = g_vm->glk_gestalt(gestalt_Graphics, 0)
-		                        && g_vm->glk_gestalt(gestalt_Timer, 0);
+		gms_graphics_possible = glk_gestalt(gestalt_Graphics, 0)
+		                        && glk_gestalt(gestalt_Timer, 0);
 	} else
-		gms_graphics_possible = FALSE;
+		gms_graphics_possible = false;
 
 
 	/*
@@ -5103,11 +3928,11 @@ void gms_main() {
 	 * been.  If pictures are impossible, they can never be enabled.
 	 */
 	if (!gms_graphics_possible)
-		g_vm->gms_graphics_enabled = FALSE;
+		gms_graphics_enabled = false;
 
 	/* Try to create a one-line status window.  We can live without it. */
-	g_vm->glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
-	gms_status_window = g_vm->glk_window_open(gms_main_window,
+	glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
+	gms_status_window = glk_window_open(gms_main_window,
 	                    winmethod_Above | winmethod_Fixed,
 	                    1, wintype_TextGrid, 0);
 
@@ -5124,7 +3949,7 @@ void gms_main() {
 	/* Look for a complete failure to load the game. */
 	if (ms_init_status == 0) {
 		if (gms_status_window)
-			g_vm->glk_window_close(gms_status_window, NULL);
+			glk_window_close(gms_status_window, NULL);
 		gms_header_string("Glk Magnetic Error\n\n");
 		gms_normal_string("Can't load game '");
 		gms_normal_string(gameFile.c_str());
@@ -5140,7 +3965,7 @@ void gms_main() {
 		free(graphics_file);
 		free(hints_file);
 		ms_freemem();
-		g_vm->glk_exit();
+		glk_exit();
 	}
 
 	/* Try to identify the game from its text file header. */
@@ -5155,13 +3980,13 @@ void gms_main() {
 		gms_standout_string("Error: Unable to open graphics file\n"
 		                    "Continuing without pictures...\n\n");
 
-		gms_graphics_possible = FALSE;
+		gms_graphics_possible = false;
 	}
 
-	/* Run the game opcodes -- ms_rungame() returns FALSE on game end. */
+	/* Run the game opcodes -- ms_rungame() returns false on game end. */
 	do {
-		is_running = ms_rungame() && !g_vm->shouldQuit();
-		g_vm->glk_tick();
+		is_running = ms_rungame() && !shouldQuit();
+		glk_tick();
 	} while (is_running);
 
 	/* Handle any updated status and pending buffered output. */
@@ -5182,15 +4007,15 @@ void gms_main() {
 
 	/* Close any open transcript, input log, and/or read log. */
 	if (gms_transcript_stream) {
-		g_vm->glk_stream_close(gms_transcript_stream, NULL);
+		glk_stream_close(gms_transcript_stream, NULL);
 		gms_transcript_stream = NULL;
 	}
 	if (gms_inputlog_stream) {
-		g_vm->glk_stream_close(gms_inputlog_stream, NULL);
+		glk_stream_close(gms_inputlog_stream, NULL);
 		gms_inputlog_stream = NULL;
 	}
 	if (gms_readlog_stream) {
-		g_vm->glk_stream_close(gms_readlog_stream, NULL);
+		glk_stream_close(gms_readlog_stream, NULL);
 		gms_readlog_stream = NULL;
 	}
 
@@ -5200,42 +4025,28 @@ void gms_main() {
 	free(hints_file);
 }
 
-
 /*---------------------------------------------------------------------*/
 /*  Linkage between Glk entry/exit calls and the Magnetic interpreter  */
 /*---------------------------------------------------------------------*/
 
-/*
- * Safety flags, to ensure we always get startup before main, and that
- * we only get a call to main once.
- */
-static int gms_startup_called = FALSE,
-           gms_main_called = FALSE;
-
-/*
- * glk_main()
- *
- * Main entry point for Glk.  Here, all startup is done, and we call our
- * function to run the game.
- */
-void glk_main() {
+void Magnetic::glk_main() {
 	assert(gms_startup_called && !gms_main_called);
-	gms_main_called = TRUE;
+	gms_main_called = true;
 
 	/* Call the interpreter main function. */
 	gms_main();
 }
 
-void write(const char *fmt, ...) {
+void Magnetic::write(const char *fmt, ...) {
 	va_list ap;
 	va_start(ap, fmt);
 	Common::String s = Common::String::format(fmt, ap);
 	va_end(ap);
-	g_vm->glk_put_buffer(s.c_str(), s.size());
+	glk_put_buffer(s.c_str(), s.size());
 }
 
-void writeChar(char c) {
-	g_vm->glk_put_char(c);
+void Magnetic::writeChar(char c) {
+	glk_put_char(c);
 }
 
 } // End of namespace Magnetic
diff --git a/engines/glk/magnetic/magnetic.cpp b/engines/glk/magnetic/magnetic.cpp
index b4f1b2e..f8ab15a 100644
--- a/engines/glk/magnetic/magnetic.cpp
+++ b/engines/glk/magnetic/magnetic.cpp
@@ -21,7 +21,7 @@
  */
 
 #include "glk/magnetic/magnetic.h"
-#include "glk/magnetic/defs.h"
+#include "glk/magnetic/magnetic_defs.h"
 #include "common/config-manager.h"
 
 namespace Glk {
@@ -32,7 +32,57 @@ Magnetic *g_vm;
 Magnetic::Magnetic(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
 		gms_gamma_mode(GAMMA_NORMAL), gms_animation_enabled(true),
 		gms_prompt_enabled(true), gms_abbreviations_enabled(true), gms_commands_enabled(true),
-		gms_graphics_enabled(false) {
+		gms_graphics_enabled(false), GMS_PORT_VERSION(0x00010601),
+		gms_main_window(nullptr), gms_status_window(nullptr), gms_graphics_window(nullptr),
+		gms_hint_menu_window(nullptr), gms_hint_text_window(nullptr),
+		gms_transcript_stream(nullptr), gms_readlog_stream(nullptr),
+		gms_inputlog_stream(nullptr), gms_graphics_possible(true),
+		GMS_INPUT_PROMPT(">"), gms_gameid_game_name(nullptr), gms_graphics_bitmap(nullptr),
+		gms_graphics_width(0), gms_graphics_height(0), gms_graphics_animated(false),
+		gms_graphics_picture(0), gms_graphics_new_picture(false),
+		gms_graphics_repaint(false), gms_graphics_active(false),
+		gms_graphics_interpreter(false), gms_graphics_off_screen(nullptr),
+		gms_graphics_on_screen(nullptr),// gms_graphics_current_gamma(Magnetic::GMS_GAMMA_TABLE),
+		gms_graphics_color_count(GMS_PALETTE_SIZE), gms_status_length(0),
+		gms_help_requested(false), gms_help_hints_silenced(false),
+		gms_output_buffer(nullptr), gms_output_allocation(0),gms_output_length(0),
+		gms_output_prompt(false), gms_hints(nullptr), gms_current_hint_node(0),
+		gms_hint_cursor(nullptr), gms_input_length(0), gms_input_cursor(0),
+		gms_undo_notification(false), gms_game_message(nullptr), gms_startup_called(false),
+		gms_main_called(false), gms_graphics_current_gamma(nullptr),
+		i_count(0), string_size(0), rseed(0), pc(0), arg1i(0), mem_size(0), properties(0),
+		fl_sub(0), fl_tab(0), fl_size(0), fp_tab(0), fp_size(0), zflag(0), nflag(0),
+		cflag(0), vflag(0), byte1(0), byte2(0), regnr(0), admode(0), opsize(0),
+		arg1(nullptr), arg2(nullptr), is_reversible(0), running(0), lastchar(0), version(0),
+		sd(0), decode_table(nullptr), restart(nullptr), code(nullptr), string(nullptr),
+		string2(nullptr), string3(nullptr), dict(nullptr), quick_flag(0), gfx_ver(0),
+		gfx_buf(nullptr), gfx_data(nullptr), gfx2_hdr(nullptr), gfx2_buf(nullptr),
+		gfx2_name(nullptr), gfx2_hsize(0), gfx_fp(nullptr), snd_buf(nullptr), snd_hdr(nullptr),
+		snd_hsize(0), snd_fp(nullptr), undo_pc(0), undo_size(0), gfxtable(0), table_dist(0),
+		v4_id(0), next_table(1)
+#ifndef NO_ANIMATION
+		, pos_table_size(0), command_table(nullptr), command_index(-1),
+		pos_table_index(-1), pos_table_max(-1), anim_repeat(0)
+#endif
+		, hints(nullptr), hint_contents(nullptr), xpos(0), bufpos(0), log_on(0),
+		ms_gfx_enabled(0), log1(nullptr), log2(nullptr) {
+
+	Common::fill(&gms_graphics_palette[0], &gms_graphics_palette[GMS_PALETTE_SIZE], 0);
+	Common::fill(&gms_status_buffer[0], &gms_status_buffer[GMS_STATBUFFER_LENGTH], '\0');
+	Common::fill(&gms_input_buffer[0], &gms_input_buffer[GMS_INPUTBUFFER_LENGTH], '\0');
+	Common::fill(&dreg[0], &dreg[8], 0);
+	Common::fill(&areg[0], &areg[8], 0);
+	Common::fill(&tmparg[0], &tmparg[4], 0);
+	Common::fill(&undo_regs[0][0], &undo_regs[3][0], 0);
+	undo[0] = undo[1] = nullptr;
+	undo_stat[0] = undo_stat[1] = 0;
+	Common::fill(&buffer[0], &buffer[80], 0);
+	Common::fill(&filename[0], &filename[256], 0);
+
+#ifndef NO_ANIMATION
+	Common::fill(&pos_table_count[0], &pos_table_count[MAX_POSITIONS], 0);
+#endif
+
 	g_vm = this;
 }
 
diff --git a/engines/glk/magnetic/magnetic.h b/engines/glk/magnetic/magnetic.h
index 4e81e4f..74d5b30 100644
--- a/engines/glk/magnetic/magnetic.h
+++ b/engines/glk/magnetic/magnetic.h
@@ -28,28 +28,1307 @@
 #include "common/scummsys.h"
 #include "glk/glk_api.h"
 #include "glk/magnetic/magnetic_types.h"
+#include "glk/magnetic/magnetic_defs.h"
+#include "glk/magnetic/detection.h"
 
 namespace Glk {
 namespace Magnetic {
 
-enum GammaMode {
-	GAMMA_OFF, GAMMA_NORMAL, GAMMA_HIGH
-};
+class Magnetic;
+
+typedef void (Magnetic::*CommandPtr)(const char *argument);
+
+/* Glk subcommands and handler functions. */
+struct gms_command_t {
+	CommandPtr handler;                 ///< Subcommand handler
+	const char *const command;          ///< Glk subcommand
+	bool takes_argument;                ///< Argument flag
+	bool undo_return;                   ///< "Undo" return value
+} ;
+typedef gms_command_t *gms_commandref_t;
+
 
 /**
  * Magnetic game interpreter
  */
 class Magnetic : public GlkAPI {
 public:
+	static const gms_command_t GMS_COMMAND_TABLE[14];
+private:
 	GammaMode gms_gamma_mode;
 	bool gms_animation_enabled, gms_prompt_enabled;
 	bool gms_abbreviations_enabled, gms_commands_enabled;
 	bool gms_graphics_enabled;
+
+	// Glk Magnetic Scrolls port version number
+	const glui32 GMS_PORT_VERSION;
+
+	/**
+	 * We use a maximum of five Glk windows, one for status, one for pictures,
+	 * two for hints, and one for everything else.  The status and pictures
+	 * windows may be NULL, depending on user selections and the capabilities
+	 * of the Glk library.  The hints windows will normally be NULL, except
+	 * when in the hints subsystem.
+	 */
+	winid_t gms_main_window, gms_status_window, gms_graphics_window;
+	winid_t gms_hint_menu_window, gms_hint_text_window;
+
+	/**
+	 * Transcript stream and input log.  These are NULL if there is no current
+	 * collection of these strings.
+	 */
+	strid_t gms_transcript_stream, gms_inputlog_stream;
+
+	// Input read log stream, for reading back an input log
+	strid_t gms_readlog_stream;
+
+	/* Note about whether graphics is possible, or not. */
+	bool gms_graphics_possible;
+
+	/* Magnetic Scrolls standard input prompt string. */
+	const char *const GMS_INPUT_PROMPT;
+
+	/**
+	 * The game's name, suitable for printing out on a status line, or other
+	 * location where game information is relevant.  Set on game startup, by
+	 * identifying the game from its text file header.
+	 */
+	const char *gms_gameid_game_name;
+
+	/*
+	 * The current picture bitmap being displayed, its width, height, palette,
+	 * animation flag, and picture id.
+	 */
+	type8 *gms_graphics_bitmap;
+	type16 gms_graphics_width, gms_graphics_height;
+	type16 gms_graphics_palette[GMS_PALETTE_SIZE]; /* = { 0, ... }; */
+	bool gms_graphics_animated;
+	type32 gms_graphics_picture;
+
+	/*
+	 * Flags set on new picture, and on resize or arrange events, and a flag
+	 * to indicate whether background repaint is stopped or active.
+	 */
+	bool gms_graphics_new_picture, gms_graphics_repaint;
+	bool gms_graphics_active;
+
+	/* Flag to try to monitor the state of interpreter graphics. */
+	bool gms_graphics_interpreter;
+
+	/*
+	 * 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.
+	 */
+	type8 *gms_graphics_off_screen, *gms_graphics_on_screen;
+
+	/*
+	 * Pointer to the current active gamma table entry.  Because of the way
+	 * it's queried, this may not be NULL, otherwise we risk a race, with
+	 * admittedly a very low probability, with the updater.  So, it's init-
+	 * ialized instead to the gamma table.  The real value in use is inserted
+	 * on the first picture update timeout call for a new picture.
+	 */
+	gms_gammaref_t gms_graphics_current_gamma;
+
+	/*
+	 * The number of colors used in the palette by the current picture.  This
+	 * value is also at risk of a race with the updater, so it too has a mild
+	 * lie for a default value.
+	 */
+	int gms_graphics_color_count;
+
+	/**
+	 * The interpreter feeds us status line characters one at a time, with Tab
+	 * indicating right justify, and CR indicating the line is complete.  To get
+	 * this to fit with the Glk event and redraw model, here we'll buffer each
+	 * completed status line, so we have a stable string to output when needed.
+	 * It's also handy to have this buffer for Glk libraries that don't support
+	 * separate windows.
+	 */
+	char gms_status_buffer[GMS_STATBUFFER_LENGTH];
+	int gms_status_length;
+
+	/*
+	 * 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.
+	 */
+	int gms_help_requested, gms_help_hints_silenced;
+
+	/*
+	 * 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.
+	 */
+	char *gms_output_buffer;
+	int gms_output_allocation, gms_output_length;
+
+	/*
+	 * Flag to indicate if the last buffer flushed looked like it ended in a
+	 * ">" prompt.
+	 */
+	int gms_output_prompt;
+
+	/*
+	 * Note of the interpreter's hints array.  Note that keeping its address
+	 * like this assumes that it's either static or heap in the interpreter.
+	 */
+	ms_hint *gms_hints;
+
+	/* Details of the current hint node on display from the hints array. */
+	type16 gms_current_hint_node;
+
+	/*
+	 * Array of cursors for each hint.  The cursor indicates the current hint
+	 * position in a folder, and the last hint shown in text hints.  Space
+	 * is allocated as needed for a given set of hints, and needs to be freed
+	 * on interpreter exit.
+	 */
+	int *gms_hint_cursor;
+
+	/*
+	 * Input buffer allocated for reading input lines.  The buffer is filled
+	 * from either an input log, if one is currently being read, or from Glk
+	 * line input.  We also need an "undo" notification flag.
+	 */
+	char gms_input_buffer[GMS_INPUTBUFFER_LENGTH];
+	int gms_input_length, gms_input_cursor, gms_undo_notification;
+
+	/*
+	 * The following values need to be passed between the startup_code and main
+	 * functions.
+	 */
+	const char *gms_game_message;  /* Error message. */
+
+	/*
+	 * Safety flags, to ensure we always get startup before main, and that
+	 * we only get a call to main once.
+	 */
+	int gms_startup_called, gms_main_called;
+private:
+	type32 dreg[8], areg[8], i_count, string_size, rseed, pc, arg1i, mem_size;
+	type16 properties, fl_sub, fl_tab, fl_size, fp_tab, fp_size;
+	type8 zflag, nflag, cflag, vflag, byte1, byte2, regnr, admode, opsize;
+	type8 *arg1, *arg2, is_reversible, running, tmparg[4];
+	type8 lastchar, version, sd;
+	type8 *decode_table, *restart, *code, *string, *string2;
+	type8 *string3, *dict;
+	type8 quick_flag, gfx_ver, *gfx_buf, *gfx_data;
+	type8 *gfx2_hdr, *gfx2_buf;
+	const char *gfx2_name;
+	type16 gfx2_hsize;
+	Common::File *gfx_fp;
+	type8 *snd_buf, *snd_hdr;
+	type16 snd_hsize;
+	Common::File *snd_fp;
+
+	type32 undo_regs[2][18], undo_pc, undo_size;
+	type8 *undo[2], undo_stat[2];
+	type16 gfxtable, table_dist;
+	type16 v4_id, next_table;
+
+#ifndef NO_ANIMATION
+	type16 pos_table_size;
+	type8 *command_table;
+	type16s command_index;
+	type16s pos_table_index;
+	type16s pos_table_max;
+	type8 anim_repeat;
+	type16 pos_table_count[MAX_POSITIONS];
+	picture anim_frame_table[MAX_ANIMS];
+	ms_position pos_table[MAX_POSITIONS][MAX_ANIMS];
+	lookup anim_table[MAX_POSITIONS];
+	ms_position pos_array[MAX_FRAMES];
+#endif
+
+	/* Hint support */
+	ms_hint *hints;
+	type8 *hint_contents;
+private:
+	type8 buffer[80], xpos, bufpos, log_on, ms_gfx_enabled, filename[256];
+	Common::DumpFile *log1, *log2;
 private:
 	/**
 	 * Performs initialization
 	 */
 	void initialize();
+
+	/**
+	 * Fatal error handler.  The function returns, expecting the caller to
+	 * abort() or otherwise handle the error.
+	 */
+	void gms_fatal(const char *string);
+
+	/**
+	 * Non-failing malloc. Calls error if memory allocation fails
+	 */
+	void *gms_malloc(size_t size);
+
+	/**
+	 * Non-failing realloc. Calls error if memory allocation fails
+	 */
+	void *gms_realloc(void *ptr, size_t size);
+
+	/**
+	 * Local comparison routine that doesn't have an ANSI standard
+	 */
+	int gms_strncasecmp(const char *s1, const char *s2, size_t n);
+
+	/**
+	 * Local comparison routine that doesn't have an ANSI standard
+	 */
+	int gms_strcasecmp(const char *s1, const char *s2);
+
+	/**
+	 * Return the CRC of the bytes in buffer[0..length-1].
+	 *
+	 * This algorithm is taken from the PNG specification, version 1.0.
+	 */
+	glui32 gms_get_buffer_crc(const void *void_buffer, size_t length);
+
+	/**
+	 * Endian-safe unsigned 32 bit integer read from game text file.  Returns
+	 * 0 on error, a known unused table value.
+	 */
+	type32 gms_gameid_read_uint32(int offset, Common::SeekableReadStream *stream);
+
+	/**
+	 * Identify a game from its text file header, and cache the game's name for
+	 * later queries.  Sets the cache to NULL if not found.
+	 */
+	void gms_gameid_identify_game(const Common::String &text_file);
+
+	/**
+	 * Return the name of the game, or NULL if not identifiable.
+	 */
+	const char *gms_gameid_get_game_name() const {
+		return gms_gameid_game_name;
+	}
+
+	/**
+	 * If it's not open, open the graphics window.  Returns TRUE if graphics
+	 * was successfully started, or already on.
+	 */
+	int gms_graphics_open();
+
+	/**
+	 * If open, close the graphics window and set back to NULL.
+	 */
+	void gms_graphics_close();
+
+	/**
+	 * If graphics enabled, start any background picture update processing.
+	 */
+	void gms_graphics_start();
+
+	/**
+	 * Stop any background picture update processing.
+	 */
+	void gms_graphics_stop();
+
+	/**
+	 * Return TRUE if graphics are currently being displayed, FALSE otherwise.
+	 */
+	int gms_graphics_are_displayed() const {
+		return gms_graphics_window != nullptr;
+	}
+
+	/**
+	 * 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.
+	 */
+	void gms_graphics_paint();
+
+	/**
+	 * Restart graphics as if the current picture is a new picture.  This
+	 * function should be called whenever graphics is re-enabled after being
+	 * disabled, on change of gamma color correction policy, and on change
+	 * of animation policy.
+	 */
+	void gms_graphics_restart();
+
+	/**
+	 * Analyze an image, and return the usage count of each palette color, and
+	 * an overall count of how many colors out of the palette are used.  NULL
+	 * arguments indicate no interest in the return value.
+	 */
+	void gms_graphics_count_colors(type8 bitmap[], type16 width, type16 height,
+		int *color_count, long color_usage[]);
+
+	/**
+	 * General graphics color conversion
+	 */
+	void gms_graphics_game_to_rgb_color(type16 color, gms_gammaref_t gamma,
+		gms_rgbref_t rgb_color);
+
+	/**
+	 * General graphics color conversion
+	 */
+	void gms_graphics_split_color(glui32 color, gms_rgbref_t rgb_color);
+
+	/**
+	 * General graphics color conversion
+	 */
+	glui32 gms_graphics_combine_color(gms_rgbref_t rgb_color);
+
+	/**
+	 * General graphics color conversion
+	 */
+	int gms_graphics_color_luminance(gms_rgbref_t rgb_color);
+
+	/**
+	 * Calculate the contrast variance of the given palette and color usage, at
+	 * the given gamma correction level.  Helper functions for automatic gamma
+	 * correction.
+	 */
+	static int gms_graphics_compare_luminance(const void *void_first, const void *void_second);
+
+	/**
+	 * Calculate the contrast variance of the given palette and color usage, at
+	 * the given gamma correction level.  Helper functions for automatic gamma
+	 * correction.
+	 */
+	long gms_graphics_contrast_variance(type16 palette[], long color_usage[],
+		gms_gammaref_t gamma);
+
+	/**
+	 * Try to find a gamma correction for the given palette and color usage that
+	 * gives relatively equal contrast among the displayed colors.
+	 *
+	 * To do this, we search the gamma tables, computing color luminance for each
+	 * color in the palette given this gamma.  From luminances, we then compute
+	 * the contrasts between the colors, and settle on the gamma correction that
+	 * gives the most even and well-distributed picture contrast.  We ignore
+	 * colors not used in the palette.
+	 *
+	 * Note that we don't consider how often a palette color is used, only whether
+	 * it's represented, or not.  Some weighting might improve things, but the
+	 * simple method seems to work adequately.  In practice, as there are only 16
+	 * colors in a palette, most pictures use most colors in a relatively well
+	 * distributed manner.  This algorithm probably wouldn't work well on real
+	 * photographs, though.
+	 */
+	gms_gammaref_t gms_graphics_equal_contrast_gamma(type16 palette[], long color_usage[]);
+
+	/**
+	 * Select a suitable gamma for the picture, based on the current gamma mode.
+	 *
+	 * The function returns either the linear gamma, a gamma value half way
+	 * between linear and the gamma that gives the most even contrast, or just
+	 * the gamma that gives the most even contrast.
+	 *
+	 * In the normal case, a value half way to the extreme case of making color
+	 * contrast equal for all colors is, subjectively, a reasonable value to use.
+	 * The problem cases are the darkest pictures, and selecting this value
+	 * brightens them while at the same time not making them look overbright or
+	 * too "sunny".
+	 */
+	gms_gammaref_t gms_graphics_select_gamma(type8 bitmap[], type16 width,
+		type16 height, type16 palette[]);
+
+	/**
+	 * 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.
+	 */
+	void gms_graphics_clear_and_border(winid_t glk_window,
+		int x_offset, int y_offset, int pixel_size, type16 width, type16 height);
+
+	/**
+	 * Convert a Magnetic Scrolls color palette to a Glk one, using the given
+	 * gamma corrections.
+	 */
+	void gms_graphics_convert_palette(type16 ms_palette[], gms_gammaref_t gamma,
+		glui32 glk_palette[]);
+
+	/**
+	 * Given a picture width and height, return the x and y offsets to center
+	 * this picture in the current graphics window.
+	 */
+	void gms_graphics_position_picture(winid_t glk_window,
+		int pixel_size, type16 width, type16 height,
+		int *x_offset, int *y_offset);
+
+	/**
+	 * Apply a single animation frame to the given off-screen image buffer, using
+	 * the frame bitmap, width, height and mask, the off-screen buffer, and the
+	 * width and height of the main picture.
+	 *
+	 * Note that 'mask' may be NULL, implying that no frame pixel is transparent.
+	 */
+	void gms_graphics_apply_animation_frame(type8 bitmap[],
+		type16 frame_width, type16 frame_height, type8 mask[],
+		int frame_x, int frame_y, type8 off_screen[], type16 width, type16 height);
+
+	/**
+	 * This function finds and applies the next set of animation frames to the
+	 * given off-screen image buffer.  It's handed the width and height of the
+	 * main picture, and the off-screen buffer.
+	 *
+	 * It returns FALSE if at the end of animations, TRUE if more animations
+	 * remain.
+	 */
+	int gms_graphics_animate(type8 off_screen[], type16 width, type16 height);
+
+#ifndef GARGLK
+	/**
+	 * 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.
+	 */
+	int gms_graphics_is_vertex(type8 off_screen[], type16 width, type16 height,
+		int x, int y);
+
+	/**
+	 * gms_graphics_compare_layering_inverted()
+	 * gms_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, as it could be
+	 * in some animation frames) 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.
+	 */
+	int gms_graphics_compare_layering_inverted(const void *void_first,
+		const void *void_second);
+
+	void gms_graphics_assign_layers(type8 off_screen[], type8 on_screen[],
+		type16 width, type16 height, int layers[], long layer_usage[]);
+
+	/**
+	 * 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.
+	 */
+	void gms_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
+		type8 off_screen[], type8 on_screen[], int x, int y, int x_offset, int y_offset,
+		int pixel_size, type16 width, type16 height);
+	#endif
+
+	void gms_graphics_paint_everything(winid_t glk_window,
+		glui32 palette[], type8 off_screen[], int x_offset, int y_offset,
+		type16 width, type16 height);
+
+	/**
+	 * 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.
+	 */
+	void gms_graphics_timeout();
+
+	/**
+	 * 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.
+	 *
+	 * The graphics window is opened if required, or closed if mode is zero.
+	 *
+	 * The function checks for changes of actual picture by calculating the
+	 * CRC for picture data; this helps to prevent unnecessary repaints in
+	 * cases where the interpreter passes us the same picture as we're already
+	 * displaying.  There is a less than 1 in 4,294,967,296 chance that a new
+	 * picture will be missed.  We'll live with that.
+	 *
+	 * Why use CRCs, rather than simply storing the values of picture passed in
+	 * a static variable?  Because some games, typically Magnetic Windows, use
+	 * the picture argument as a form of string pointer, and can pass in the
+	 * same value for several, perhaps all, game pictures.  If we just checked
+	 * for a change in the picture argument, we'd never see one.  So we must
+	 * instead look for changes in the real picture data.
+	 */
+	void ms_showpic(type32 picture, type8 mode);
+
+	/**
+	 * Return TRUE if the graphics module data is loaded with a usable picture,
+	 * FALSE if there is no picture available to display.
+	 */
+	int gms_graphics_picture_is_available() const {
+		return gms_graphics_bitmap != nullptr;
+	}
+
+	/**
+	 * Return the width, height, and animation flag of the currently loaded
+	 * picture.  The function returns FALSE if no picture is loaded, otherwise
+	 * TRUE, with picture details in the return arguments.
+	 */
+	int gms_graphics_get_picture_details(int *width, int *height, int *is_animated);
+
+	/**
+	 * Returns the current level of applied gamma correction, 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 gamma, 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
+	 * gamma and color count values are returned.
+	 */
+	int gms_graphics_get_rendering_details(const char **gamma, int *color_count,
+		int *is_active);
+
+	/**
+	 * Return TRUE if it looks like interpreter graphics are turned on, FALSE
+	 * otherwise.
+	 */
+	int gms_graphics_interpreter_enabled();
+
+	/*
+	 * gms_graphics_cleanup()
+	 *
+	 * Free memory resources allocated by graphics functions.  Called on game
+	 * end.
+	 */
+	void gms_graphics_cleanup();
+
+	/*---------------------------------------------------------------------*/
+	/*  Glk port status line functions                                     */
+	/*---------------------------------------------------------------------*/
+
+	/**
+	 * Receive one status character from the interpreter.  Characters are
+	 * buffered internally, and on CR, the buffer is copied to the main static
+	 * status buffer for use by the status line printing function.
+	 */
+	void ms_statuschar(type8 c);
+
+	/*
+	 * Update the information in the status window with the current contents of
+	 * the completed status line buffer, or a default string if no completed
+	 * status line.
+	 */
+	void gms_status_update();
+
+	/**
+	 * Print the current contents of the completed status line buffer out in the
+	 * main window, if it has changed since the last call.  This is for non-
+	 * windowing Glk libraries.
+	 */
+	void gms_status_print();
+
+	/*
+	 * gms_status_notify()
+	 *
+	 * Front end function for updating status.  Either updates the status window
+	 * or prints the status line to the main window.
+	 */
+	void gms_status_notify();
+
+	/*
+	 * gms_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.
+	 */
+	void gms_status_redraw();
+
+	/*---------------------------------------------------------------------*/
+	/*  Glk port output functions                                          */
+	/*---------------------------------------------------------------------*/
+
+	/*
+	 * gms_output_register_help_request()
+	 * gms_output_silence_help_hints()
+	 * gms_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.
+	 */
+	void gms_output_register_help_request();
+
+	void gms_output_silence_help_hints();
+
+	void gms_output_provide_help_hint();
+
+	/*
+	 * gms_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.
+	 */
+	int gms_game_prompted();
+
+	/*
+	 * gms_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.
+	 */
+	void gms_detect_game_prompt();
+
+	/*
+	 * gms_output_delete()
+	 *
+	 * Delete all buffered output text.  Free all malloc'ed buffer memory, and
+	 * return the buffer variables to their initial values.
+	 */
+	void gms_output_delete();
+
+	/*
+	 * gms_output_flush()
+	 *
+	 * Flush any buffered output text to the Glk main window, and clear the
+	 * buffer.
+	 */
+	void gms_output_flush();
+
+	/*
+	 * ms_putchar()
+	 *
+	 * Buffer a character for eventual printing to the main window.
+	 */
+	void ms_putchar(type8 c);
+
+	/*
+	 * gms_styled_string()
+	 * gms_styled_char()
+	 * gms_standout_string()
+	 * gms_standout_char()
+	 * gms_normal_string()
+	 * gms_normal_char()
+	 * gms_header_string()
+	 * gms_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.
+	 */
+	void gms_styled_string(glui32 style, const char *message);
+
+	void gms_styled_char(glui32 style, char c);
+
+	void gms_standout_string(const char *message);
+
+	void gms_normal_string(const char *message);
+
+	void gms_normal_char(char c);
+
+	void gms_header_string(const char *message);
+
+	void gms_banner_string(const char *message);
+
+	/**
+	 * 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 gms_output_flush() when reading line input.
+	 *
+	 * Taking ms_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 ms_flush();
+
+
+	/*---------------------------------------------------------------------*/
+	/*  Glk port hint functions                                            */
+	/*---------------------------------------------------------------------*/
+
+	/**
+	 * Return the maximum hint node referred to by the tree under the given node.
+	 * The result is the largest index found, or node, if greater.  Because the
+	 * interpreter doesn't supply it, we need to uncover it the hard way.  The
+	 * function is recursive, and since it is a tree search, assumes that hints
+	 * is a tree, not a graph.
+	 */
+	type16 gms_get_hint_max_node(const ms_hint hints_[], type16 node);
+
+	/**
+	 * Return the content string for a given hint number within a given node.
+	 * This counts over 'number' ASCII NULs in the node's content, returning
+	 * the address of the string located this way.
+	 */
+	const char *gms_get_hint_content(const ms_hint hints_[], type16 node, int number);
+
+	/**
+	 * Return the topic string for a given hint node.  This is found by searching
+	 * the parent node for a link to the node handed in.  For the root node, the
+	 * string is defaulted, since the root node has no parent.
+	 */
+	const char *gms_get_hint_topic(const ms_hint hints_[], type16 node);
+
+	/**
+	 * If not already open, open the hints windows.  Returns TRUE if the windows
+	 * opened, or were already open.
+	 *
+	 * The function creates two hints windows -- a text grid on top, for menus,
+	 * and a text buffer below for hints.
+	 */
+	int gms_hint_open();
+
+	/**
+	 * If open, close the hints windows.
+	 */
+	void gms_hint_close();
+
+	/**
+	 * Return TRUE if hints windows are available.  If they're not, the hints
+	 * system will need to use alternative output methods.
+	 */
+	int gms_hint_windows_available();
+
+	/**
+	 * gms_hint_menu_print()
+	 * gms_hint_menu_header()
+	 * gms_hint_menu_justify()
+	 * gms_hint_text_print()
+	 * gms_hint_menutext_done()
+	 * gms_hint_menutext_start()
+	 *
+	 * Output functions for writing hints.  These functions will write to hints
+	 * windows where available, and to the main window where not.  When writing
+	 * to hints windows, they also take care not to line wrap in the menu window.
+	 * Limited formatting is available.
+	 */
+	void gms_hint_menu_print(int line, int column, const char *string_,
+		glui32 width, glui32 height);
+
+	void gms_hint_menu_header(int line, const char *string_,
+		glui32 width, glui32 height);
+
+	void gms_hint_menu_justify(int line, const char *left_string,
+		const char *right_string, glui32 width, glui32 height);
+
+	void gms_hint_text_print(const char *string_);
+
+	void gms_hint_menutext_start();
+
+	void gms_hint_menutext_done();
+
+	/**
+	 * Request and return a character event from the hints windows.  In practice,
+	 * this means either of the hints windows if available, or the main window
+	 * if not.
+	 */
+	void gms_hint_menutext_char_event(event_t *event);
+
+	/**
+	 * Arrange the hints windows so that the hint menu window has the requested
+	 * number of lines.  Returns the actual hint menu window width and height,
+	 * or defaults if no hints windows are available.
+	 */
+	void gms_hint_arrange_windows(int requested_lines, glui32 *width, glui32 *height);
+
+	/**
+	 * Update the hints windows for the given folder hint node.
+	 */
+	void gms_hint_display_folder(const struct ms_hint hints_[],
+		const int cursor[], type16 node);
+
+	/**
+	 * Update the hints windows for the given text hint node.
+	 */
+	void gms_hint_display_text(const struct ms_hint hints_[],
+		const int cursor[], type16 node);
+
+	/**
+	 * Display the given hint using the appropriate display function.
+	 */
+	void gms_hint_display(const struct ms_hint hints_[],
+		const int cursor[], type16 node);
+
+	/**
+	 * Handle a Glk keycode for the given folder hint.  Return the next node to
+	 * handle, or the special end-hints on Quit at the root node.
+	 */
+	type16 gms_hint_handle_folder(const ms_hint hints_[],
+		int cursor[], type16 node, glui32 keycode);
+
+	/**
+	 * Handle a Glk keycode for the given text hint.  Return the next node to
+	 * handle.
+	 */
+	type16 gms_hint_handle_text(const ms_hint hints[],
+		int cursor[], type16 node, glui32 keycode);
+
+	/**
+	 * Handle a Glk keycode for the given hint using the appropriate handler
+	 * function.  Return the next node to handle.
+	 */
+	type16 gms_hint_handle(const ms_hint hints_[], int cursor[],
+		type16 node, glui32 keycode);
+
+	/**
+	 * Start game hints.  These are modal, though there's no overriding Glk
+	 * reason why.  It's just that this matches the way they're implemented by
+	 * most Inform games.  This may not be the best way of doing help, but at
+	 * least it's likely to be familiar, and anything more ambitious may be
+	 * beyond the current Glk capabilities.
+	 *
+	 * This function uses CRCs to detect any change of hints data.  Normally,
+	 * we'd expect none, at least within a given game run, but we can probably
+	 * handle it okay if it happens.
+	 */
+	type8 ms_showhints(ms_hint *hints_);
+
+	/**
+	 * Update the hints windows for the current hint.  This function should be
+	 * called from the event handler on resize events, to repaint the hints
+	 * display.  It does nothing if no hints windows have been opened, since
+	 * in this case, there's no resize action required -- either we're not in
+	 * the hints subsystem, or hints are being displayed in the main game
+	 * window, for whatever reason.
+	 */
+	void gms_hint_redraw();
+
+	/**
+	 * Free memory resources allocated by hints functions.  Called on game
+	 * end.
+	 */
+	void gms_hints_cleanup();
+
+	void ms_playmusic(type8 *midi_data, type32 length, type16 tempo);
+
+	/*---------------------------------------------------------------------*/
+	/*  Glk command escape functions                                       */
+	/*---------------------------------------------------------------------*/
+
+	/**
+	 * Stub function for the undo command.  The real work is to return the
+	 * undo code to the input functions.
+	 */
+	void gms_command_undo(const char *argument);
+
+	/**
+	 * Turn game output scripting (logging) on and off.
+	 */
+	void gms_command_script(const char *argument);
+
+	/**
+	 * Turn game input logging on and off.
+	 */
+	void gms_command_inputlog(const char *argument);
+
+	/**
+	 * Set the game input log, to read input from a file.
+	 */
+	void gms_command_readlog(const char *argument);
+
+	/**
+	 * Turn abbreviation expansions on and off.
+	 */
+	void gms_command_abbreviations(const char *argument);
+
+	/**
+	 * 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.
+	 */
+	void gms_command_graphics(const char *argument);
+
+	/**
+	 * Enable or disable picture gamma corrections.
+	 */
+	void gms_command_gamma(const char *argument);
+
+	/**
+	 * Enable or disable picture animations.
+	 */
+	void gms_command_animations(const char *argument);
+
+	/**
+	 * Turn the extra "> " prompt output on and off.
+	 */
+	void gms_command_prompts(const char *argument);
+
+	/**
+	 * gms_command_print_version_number()
+	 * gms_command_version()
+	 *
+	 * Print out the Glk library version number.
+	 */
+	void gms_command_print_version_number(glui32 version);
+
+	void gms_command_version(const char *argument);
+
+	/**
+	 * Turn command escapes off.  Once off, there's no way to turn them back on.
+	 * Commands must be on already to enter this function.
+	 */
+	void gms_command_commands(const char *argument);
+
+	/**
+	 * Report all current Glk settings.
+	 */
+	void gms_command_summary(const char *argument);
+
+	/**
+	 * Document the available Glk commands.
+	 */
+	void gms_command_help(const char *command);
+
+	/**
+	 * This function is handed each input line.  If the line contains a specific
+	 * Glk port command, handle it and return TRUE, otherwise return FALSE.
+	 *
+	 * On unambiguous returns, it will also set the value for undo_command to the
+	 * table undo return value.
+	 */
+	int gms_command_escape(const char *string_, int *undo_command);
+
+	/**
+	 * This function makes a special case of the input line containing the single
+	 * word "undo", treating it as if it is "glk undo".  This makes life a bit
+	 * more convenient for the player, since it's the same behavior that most
+	 * other IF systems have.  It returns TRUE if "undo" found, FALSE otherwise.
+	 */
+	int gms_command_undo_special(const char *string_);
+
+	/*---------------------------------------------------------------------*/
+	/*  Glk port input functions                                           */
+	/*---------------------------------------------------------------------*/
+
+	/**
+	 * Expand a few common one-character abbreviations commonly found in other
+	 * game systems, but not always normal in Magnetic Scrolls games.
+	 */
+	void gms_expand_abbreviations(char *buffer_, int size);
+
+	/**
+	 * Read and buffer a line of input.  If there is an input log active, then
+	 * data is taken by reading this first.  Otherwise, the function gets a
+	 * line from Glk.
+	 *
+	 * It also makes special cases of some lines read from the user, either
+	 * handling commands inside them directly, or expanding abbreviations as
+	 * appropriate.  This is not reflected in the buffer, which is adjusted as
+	 * required before returning.
+	 */
+	void gms_buffer_input();
+
+	/**
+	 * Return the single next character to the interpreter.  This function
+	 * extracts characters from the input buffer until empty, when it then
+	 * tries to buffer more data.
+	 */
+	type8 ms_getchar(type8 trans);
+
+	/*---------------------------------------------------------------------*/
+	/*  Glk port event functions                                           */
+	/*---------------------------------------------------------------------*/
+
+	/**
+	 * Process Glk events until one of the expected type arrives.  Return
+	 * the event of that type.
+	 */
+	void gms_event_wait(glui32 wait_type, event_t *event);
+
+	/*---------------------------------------------------------------------*/
+	/*  Functions intercepted by link-time wrappers                        */
+	/*---------------------------------------------------------------------*/
+
+	/**
+	 * __wrap_toupper()
+	 * __wrap_tolower()
+	 *
+	 * Wrapper functions around toupper() and tolower().  The Linux linker's
+	 * --wrap option will convert calls to mumble() to __wrap_mumble() if we
+	 * give it the right options.  We'll use this feature to translate all
+	 * toupper() and tolower() calls in the interpreter code into calls to
+	 * Glk's versions of these functions.
+	 *
+	 * It's not critical that we do this.  If a linker, say a non-Linux one,
+	 * won't do --wrap, then just do without it.  It's unlikely that there
+	 * will be much noticeable difference.
+	 */
+	int __wrap_toupper(int ch);
+
+	int __wrap_tolower(int ch);
+
+	/*---------------------------------------------------------------------*/
+	/*  main and options parsing                                           */
+	/*---------------------------------------------------------------------*/
+
+	/**
+	 * Given a game name, try to establish three filenames from it - the main game
+	 * text file, the (optional) graphics data file, and the (optional) hints
+	 * file.  Given an input "file" X, the function looks for X.MAG or X.mag for
+	 * game data, X.GFX or X.gfx for graphics, and X.HNT or X.hnt for hints.
+	 * If the input file already ends with .MAG, .GFX, or .HNT, the extension
+	 * is stripped first.
+	 *
+	 * The function returns NULL for filenames not available.  It's not fatal if
+	 * the graphics filename or hints filename is NULL, but it is if the main game
+	 * filename is NULL.  Filenames are malloc'ed, and need to be freed by the
+	 * caller.
+	 */
+	void gms_establish_filenames(const char *name, char **text, char **graphics, char **hints_);
+
+	void gms_main();
+
+	/*---------------------------------------------------------------------*/
+	/*  Linkage between Glk entry/exit calls and the Magnetic interpreter  */
+	/*---------------------------------------------------------------------*/
+
+	/*
+	 * glk_main()
+	 *
+	 * Main entry point for Glk.  Here, all startup is done, and we call our
+	 * function to run the game.
+	 */
+	void glk_main();
+
+	void write(const char *fmt, ...);
+
+	void writeChar(char c);
+private:
+	/* Convert virtual pointer to effective pointer */
+	type8 *effective(type32 ptr);
+
+	static type32 read_l(type8 *ptr) {
+		return (type32)((type32)ptr[0] << 24 | (type32)ptr[1] << 16 | (type32)ptr[2] << 8 | (type32)ptr[3]);
+	}
+
+	static type16 read_w(type8 *ptr) {
+		return (type16)(ptr[0] << 8 | ptr[1]);
+	}
+
+	static type32 read_l2(type8 *ptr) {
+		return ((type32)ptr[1] << 24 | (type32)ptr[0] << 16 | (type32)ptr[3] << 8 | (type32)ptr[2]);
+	}
+
+	static type16 read_w2(type8 *ptr) {
+		return (type16)(ptr[1] << 8 | ptr[0]);
+	}
+
+	static void write_l(type8 *ptr, type32 val);
+
+	static void write_w(type8 *ptr, type16 val);
+
+	/* Standard rand - for equal cross-platform behaviour */
+	void ms_seed(type32 seed) {
+		rseed = seed;
+	}
+
+	type32 rand_emu();
+
+	void ms_freemem();
+
+	type8 ms_is_running() const {
+		return running;
+	}
+
+	type8 ms_is_magwin() const {
+		return (version == 4) ? 1 : 0;
+	}
+
+	void ms_stop() {
+		running = 0;
+	}
+
+	type8 init_gfx1(type8 *header);
+
+	type8 init_gfx2(type8 *header);
+
+	type8 init_snd(type8 *header);
+
+	/* zero all registers and flags and load the game */
+	type8 ms_init(const char *name, const char *gfxname, const char *hntname, const char *sndname);
+
+	type8 is_blank(type16 line, type16 width);
+
+	type8 *ms_extract1(type8 pic, type16 *w, type16 *h, type16 *pal);
+
+	type16s find_name_in_header(const char *name, type8 upper);
+
+	void extract_frame(struct picture *pic);
+
+	type8 *ms_extract2(const char *name, type16 *w, type16 *h, type16 *pal, type8 *is_anim);
+
+	type8 *ms_extract(type32 pic, type16 *w, type16 *h, type16 *pal, type8 *is_anim);
+
+	type8 ms_animate(struct ms_position **positions, type16 *count);
+
+	type8 *ms_get_anim_frame(type16s number, type16 *width, type16 *height, type8 **mask);
+
+	type8 ms_anim_is_repeating() const;
+
+	type16s find_name_in_sndheader(const char *name);
+
+	type8 *sound_extract(const char *name, type32 *length, type16 *tempo);
+
+	void save_undo();
+
+	type8 ms_undo();
+
+#ifdef LOGEMU
+	void log_status();
+#endif
+
+	void ms_status();
+
+	type32 ms_count() const {
+		return i_count;
+	}
+
+	/* align register pointer for word/byte accesses */
+	type8 *reg_align(type8 *ptr, type8 size);
+
+	type32 read_reg(int i, int s);
+
+	void write_reg(int i, int s, type32 val);
+
+	/* [35c4] */
+	void char_out(type8 c);
+
+	/* extract addressing mode information [1c6f] */
+	void set_info(type8 b);
+
+	/* read a word and increase pc */
+	void read_word();
+
+	/* get addressing mode and set arg1 [1c84] */
+	void set_arg1();
+
+	/* get addressing mode and set arg2 [1bc5] */
+	void set_arg2_nosize(int use_dx, type8 b);
+
+	void set_arg2(int use_dx, type8 b);
+
+	/* [1b9e] */
+	void swap_args();
+
+	/* [1cdc] */
+	void push(type32 c);
+
+	/* [1cd1] */
+	type32 pop();
+
+	/* check addressing mode and get argument [2e85] */
+	void get_arg();
+
+	void set_flags();
+
+	/* [263a] */
+	int condition(type8 b);
+
+	/* [26dc] */
+	void branch(type8 b);
+
+	/* [2869] */
+	void do_add(type8 adda);
+
+	/* [2923] */
+	void do_sub(type8 suba);
+
+	/* [283b] */
+	void do_eor();
+
+	/* [280d] */
+	void do_and();
+
+	/* [27df] */
+	void do_or();
+
+	/* [289f] */
+	void do_cmp();
+
+	/* [2973] */
+	void do_move();
+
+	type8 do_btst(type8 a);
+
+	/* bit operation entry point [307c] */
+	void do_bop(type8 b, type8 a);
+
+	void check_btst();
+
+	void check_lea();
+
+	/* [33cc] */
+	void check_movem();
+
+	/* [3357] */
+	void check_movem2();
+
+	/* [30e4] in Jinxter, ~540 lines of 6510 spaghetti-code */
+	/* The mother of all bugs, but hey - no gotos used :-) */
+	void dict_lookup();
+
+	/* A0=findproperties(D0) [2b86], properties_ptr=[2b78] A0FE */
+	void do_findprop();
+
+	void write_string();
+
+	void output_number(type16 number);
+
+	type16 output_text(const char *text);
+
+	type16s hint_input();
+
+	type16 show_hints_text(ms_hint *hintsData, type16 index);
+
+	void do_line_a();
+
+	/* emulate an instruction [1b7e] */
+	type8 ms_rungame();
+private:
+	type8 ms_load_file(const char *name, type8 *ptr, type16 size);
+
+	type8 ms_save_file(const char *name, type8 *ptr, type16 size);
+
+	void script_write(type8 c);
+
+	void transcript_write(type8 c);
 public:
 	/**
 	 * Constructor
diff --git a/engines/glk/magnetic/magnetic_defs.h b/engines/glk/magnetic/magnetic_defs.h
new file mode 100644
index 0000000..f2b18c0
--- /dev/null
+++ b/engines/glk/magnetic/magnetic_defs.h
@@ -0,0 +1,113 @@
+/* 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 MAGNETIC_DEFS_H
+#define MAGNETIC_DEFS_H
+
+#include "common/scummsys.h"
+
+namespace Glk {
+namespace Magnetic {
+
+/*****************************************************************************\
+* Type definitions for Magnetic
+*
+* Note: When running into trouble please ensure that these types have the
+*       correct number of bits on your system !!!
+\*****************************************************************************/
+
+typedef byte type8;
+typedef int8 type8s;
+typedef uint16 type16;
+typedef int16 type16s;
+typedef uint32 type32;
+typedef int32 type32s;
+
+#ifndef BYTE_MAX
+#define BYTE_MAX 255
+#endif
+#ifndef CHAR_BIT
+#define CHAR_BIT 8
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX 0xffff
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX 0x7fffffff
+#endif
+
+#define MAX_HINTS 260
+#define MAX_HCONTENTS 30000
+
+#define MAX_POSITIONS 20
+#define MAX_ANIMS 200
+#define MAX_FRAMES 20
+#define MAX_STRING_SIZE  0xFF00
+#define MAX_PICTURE_SIZE 0xC800
+#define MAX_MUSIC_SIZE   0x4E20
+#define MAX_HITEMS 25
+
+/****************************************************************************\
+* Compile time switches
+\****************************************************************************/
+
+/* Switch:  SAVEMEM
+   Purpose: Magnetic loads a complete graphics file into memory by default.
+            Setting this switch you tell Magnetic to load images on request
+            (saving memory, wasting load time)
+
+#define SAVEMEM
+*/
+
+/* Switch:  NO_ANIMATION
+   Purpose: By default Magnetic plays animated graphics.
+            Setting this switch to ignore animations, Magnetic shows the
+            static parts of the images anyway!
+
+#define NO_ANIMATION
+*/
+
+/****************************************************************************\
+* Miscellaneous enums/types
+*
+\****************************************************************************/
+
+enum { GMS_PALETTE_SIZE = 16 };
+
+enum { GMS_INPUTBUFFER_LENGTH = 256 };
+
+enum { GMS_STATBUFFER_LENGTH = 1024 };
+
+enum GammaMode {
+	GAMMA_OFF, GAMMA_NORMAL, GAMMA_HIGH
+};
+
+/* Hint type definitions. */
+enum {
+	GMS_HINT_TYPE_FOLDER = 1,
+	GMS_HINT_TYPE_TEXT = 2
+};
+
+} // End of namespace Magnetic
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/magnetic/magnetic_types.h b/engines/glk/magnetic/magnetic_types.h
index 5c8cad5..0d18614 100644
--- a/engines/glk/magnetic/magnetic_types.h
+++ b/engines/glk/magnetic/magnetic_types.h
@@ -24,22 +24,11 @@
 #define GLK_MAGNETIC_TYPES
 
 #include "common/scummsys.h"
-#include "common/algorithm.h"
+#include "glk/magnetic/magnetic_defs.h"
 
 namespace Glk {
 namespace Magnetic {
 
-#define MAX_HINTS 260
-#define MAX_HCONTENTS 30000
-
-#define MAX_POSITIONS 20
-#define MAX_ANIMS 200
-#define MAX_FRAMES 20
-#define MAX_STRING_SIZE  0xFF00
-#define MAX_PICTURE_SIZE 0xC800
-#define MAX_MUSIC_SIZE   0x4E20
-#define MAX_HITEMS 25
-
 struct lookup {
 	int16 flag;
 	int16 count;
@@ -131,6 +120,34 @@ struct ms_hint {
 	}
 };
 
+struct gms_gamma_t {
+	const char *const level;       ///< Gamma correction level
+	const unsigned char table[8];  ///< Color lookup table
+	const bool is_corrected;       ///< Flag if non-linear
+};
+typedef const gms_gamma_t *gms_gammaref_t;
+
+/* R,G,B color triple definition. */
+struct gms_rgb_t {
+	int red, green, blue;
+};
+typedef gms_rgb_t *gms_rgbref_t;
+
+#ifndef GARGLK
+struct gms_layering_t {
+	long complexity;  /* Count of vertices for this color. */
+	long usage;       /* Color usage count. */
+	int color;        /* Color index into palette. */
+};
+#endif
+
+/* Table of single-character command abbreviations. */
+struct gms_abbreviation_t {
+	const char abbreviation;       /* Abbreviation character. */
+	const char *const expansion;   /* Expansion string. */
+};
+typedef gms_abbreviation_t *gms_abbreviationref_t;
+
 } // End of namespace Magnetic
 } // End of namespace Glk
 
diff --git a/engines/glk/magnetic/main.cpp b/engines/glk/magnetic/main.cpp
index 1f0128f..444aa0a 100644
--- a/engines/glk/magnetic/main.cpp
+++ b/engines/glk/magnetic/main.cpp
@@ -20,7 +20,7 @@
  *
  */
 
-#include "glk/magnetic/defs.h"
+#include "glk/magnetic/magnetic.h"
 #include "common/file.h"
 #include "common/savefile.h"
 #include "common/system.h"
@@ -30,10 +30,7 @@ namespace Magnetic {
 
 #define WIDTH 78
 
-static type8 buffer[80], xpos = 0, bufpos = 0, log_on = 0, ms_gfx_enabled, filename[256];
-static Common::DumpFile *log1 = 0, *log2 = 0;
-
-type8 ms_load_file(const char *name, type8 *ptr, type16 size) {
+type8 Magnetic::ms_load_file(const char *name, type8 *ptr, type16 size) {
 	assert(name);
 	Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(name);
 	if (!file)
@@ -48,7 +45,7 @@ type8 ms_load_file(const char *name, type8 *ptr, type16 size) {
 	return 0;
 }
 
-type8 ms_save_file(const char *name, type8 *ptr, type16 size) {
+type8 Magnetic::ms_save_file(const char *name, type8 *ptr, type16 size) {
 	assert(name);
 	Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving(name);
 	if (!file)
@@ -64,113 +61,15 @@ type8 ms_save_file(const char *name, type8 *ptr, type16 size) {
 	return 0;
 }
 
-void script_write(type8 c) {
+void Magnetic::script_write(type8 c) {
 	if (log_on == 2)
 		log1->writeByte(c);
 }
 
-void transcript_write(type8 c) {
+void Magnetic::transcript_write(type8 c) {
 	if (log2)
 		log2->writeByte(c);
 }
 
-void ms_fatal(const char *txt) {
-	error("Fatal error: %s", txt);
-}
-
-#if 0
-main(int argc, char **argv) {
-	type8 running, i, *gamename = 0, *gfxname = 0, *hintname = 0;
-	type32 dlimit, slimit;
-
-	if (sizeof(type8) != 1 || sizeof(type16) != 2 || sizeof(type32) != 4) {
-		fprintf(stderr,
-		        "You have incorrect typesizes, please edit the typedefs and recompile\n"
-		        "or proceed on your own risk...\n");
-		exit(1);
-	}
-	dlimit = slimit = 0xffffffff;
-	for (i = 1; i < argc; i++) {
-		if (argv[i][0] == '-') {
-			switch (tolower(argv[i][1])) {
-			case 'd':
-				if (strlen(argv[i]) > 2)
-					dlimit = atoi(&argv[i][2]);
-				else
-					dlimit = 0;
-				break;
-			case 's':
-				if (strlen(argv[i]) > 2)
-					slimit = atoi(&argv[i][2]);
-				else
-					slimit = 655360;
-				break;
-			case 't':
-				if (!(log2 = fopen(&argv[i][2], "w")))
-					printf("Failed to open \"%s\" for writing.\n", &argv[i][2]);
-				break;
-			case 'r':
-				if (log1 = fopen(&argv[i][2], "r"))
-					log_on = 1;
-				else
-					printf("Failed to open \"%s\" for reading.\n", &argv[i][2]);
-				break;
-			case 'w':
-				if (log1 = fopen(&argv[i][2], "w"))
-					log_on = 2;
-				else
-					printf("Failed to open \"%s\" for writing.\n", &argv[i][2]);
-				break;
-			default:
-				printf("Unknown option -%c, ignoring.\n", argv[i][1]);
-				break;
-			}
-		} else if (!gamename)
-			gamename = argv[i];
-		else if (!gfxname)
-			gfxname = argv[i];
-		else if (!hintname)
-			hintname = argv[i];
-	}
-	if (!gamename) {
-		printf("Magnetic 2.3 - a Magnetic Scrolls interpreter\n\n");
-		printf("Usage: %s [options] game [gfxfile] [hintfile]\n\n"
-		       "Where the options are:\n"
-		       " -dn    activate register dump (after n instructions)\n"
-		       " -rname read script file\n"
-		       " -sn    safety mode, exits automatically (after n instructions)\n"
-		       " -tname write transcript file\n"
-		       " -wname write script file\n\n"
-		       "The interpreter commands are:\n"
-		       " #undo   undo - don't use it near are_you_sure prompts\n"
-		       " #logoff turn off script writing\n\n", argv[0]);
-		exit(1);
-	}
-
-	if (!(ms_gfx_enabled = ms_init(gamename, gfxname, hintname, 0))) {
-		printf("Couldn't start up game \"%s\".\n", gamename);
-		exit(1);
-	}
-	ms_gfx_enabled--;
-	running = 1;
-	while ((ms_count() < slimit) && running) {
-		if (ms_count() >= dlimit)
-			ms_status();
-		running = ms_rungame();
-	}
-	if (ms_count() == slimit) {
-		printf("\n\nSafety limit (%d) reached.\n", slimit);
-		ms_status();
-	}
-	ms_freemem();
-	if (log_on)
-		fclose(log1);
-	if (log2)
-		fclose(log2);
-	printf("\nExiting.\n");
-	return 0;
-}
-#endif
-
 } // End of namespace Magnetic
 } // End of namespace Glk


Commit: 69f186c66591e6cff41d1444e6da40e45e4d5103
    https://github.com/scummvm/scummvm/commit/69f186c66591e6cff41d1444e6da40e45e4d5103
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-11-22T18:49:07-08:00

Commit Message:
GLK: MAGNETIC: Moving local method static variables to class fields

Changed paths:
    engines/glk/magnetic/glk.cpp
    engines/glk/magnetic/magnetic.cpp
    engines/glk/magnetic/magnetic.h
    engines/glk/magnetic/magnetic_types.h


diff --git a/engines/glk/magnetic/glk.cpp b/engines/glk/magnetic/glk.cpp
index fec8691..ee4faeb 100644
--- a/engines/glk/magnetic/glk.cpp
+++ b/engines/glk/magnetic/glk.cpp
@@ -44,9 +44,7 @@ const gms_command_t Magnetic::GMS_COMMAND_TABLE[14] = {
 	{ nullptr, nullptr, false, false}
 };
 
-
-
-static gms_gamma_t GMS_GAMMA_TABLE[] = {
+const gms_gamma_t Magnetic::GMS_GAMMA_TABLE[38] = {
 	{ "0.90", { 0,  29,  63,  99, 137, 175, 215, 255 }, true },
 	{ "0.95", { 0,  33,  68, 105, 141, 179, 217, 255 }, true },
 	{ "1.00", { 0,  36,  73, 109, 146, 182, 219, 255 }, false },
@@ -99,9 +97,6 @@ static gms_abbreviation_t GMS_ABBREVIATIONS[] = {
 /*  Module constants                                                   */
 /*---------------------------------------------------------------------*/
 
-/* CRC table initialization polynomial. */
-static const glui32 GMS_CRC_POLYNOMIAL = 0xedb88320;
-
 /* Glk Magnetic Scrolls port version number. */
 static const glui32 GMS_PORT_VERSION = 0x00010601;
 
@@ -109,15 +104,6 @@ static const glui32 GMS_PORT_VERSION = 0x00010601;
 static const char *const GMS_INPUT_PROMPT = ">";
 
 /*
- * Weighting values for calculating the luminance of a color.  There are
- * two commonly used sets of values for these -- 299,587,114, taken from
- * NTSC (Never The Same Color) 1953 standards, and 212,716,72, which is the
- * set that modern CRTs tend to match.  The NTSC ones seem to give the best
- * subjective results.
- */
-static const gms_rgb_t GMS_LUMINANCE_WEIGHTS = { 299, 587, 114 };
-
-/*
  * 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
@@ -293,31 +279,10 @@ int Magnetic::gms_strcasecmp(const char *s1, const char *s2) {
 /*---------------------------------------------------------------------*/
 
 glui32 Magnetic::gms_get_buffer_crc(const void *void_buffer, size_t length) {
-	static int is_initialized = false;
-	static glui32 crc_table[BYTE_MAX + 1];
-
 	const char *buf = (const char *) void_buffer;
-	glui32 crc;
+	uint32 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 = (glui32) index;
-			for (bit = 0; bit < CHAR_BIT; bit++)
-				crc = crc & 1 ? GMS_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
-
-			crc_table[index] = crc;
-		}
-
-		is_initialized = true;
-
-		/* CRC lookup table self-test, after is_initialized set -- recursion. */
-		assert(gms_get_buffer_crc("123456789", 9) == 0xcbf43926);
-	}
-
 	/*
 	 * Start with all ones in the crc, then update using table entries.  Xor
 	 * with all ones again, finally, before returning.
@@ -508,26 +473,13 @@ glui32 Magnetic::gms_graphics_combine_color(gms_rgbref_t rgb_color) {
 }
 
 int Magnetic::gms_graphics_color_luminance(gms_rgbref_t rgb_color) {
-	static int is_initialized = false;
-	static int weighting = 0;
-
-	long luminance;
-
-	/* On the first call, calculate the overall weighting. */
-	if (!is_initialized) {
-		weighting = GMS_LUMINANCE_WEIGHTS.red + GMS_LUMINANCE_WEIGHTS.green
-		            + GMS_LUMINANCE_WEIGHTS.blue;
-
-		is_initialized = true;
-	}
-
 	/* Calculate the luminance and scale back by 1000 to 0-255 before return. */
-	luminance = ((long) rgb_color->red   * (long) GMS_LUMINANCE_WEIGHTS.red
+	long luminance = ((long) rgb_color->red   * (long) GMS_LUMINANCE_WEIGHTS.red
 	             + (long) rgb_color->green * (long) GMS_LUMINANCE_WEIGHTS.green
 	             + (long) rgb_color->blue  * (long) GMS_LUMINANCE_WEIGHTS.blue);
 
-	assert(weighting > 0);
-	return (int)(luminance / weighting);
+	assert(luminance_weighting > 0);
+	return (int)(luminance / luminance_weighting);
 }
 
 int Magnetic::gms_graphics_compare_luminance(const void *void_first,
@@ -627,26 +579,9 @@ gms_gammaref_t Magnetic::gms_graphics_equal_contrast_gamma(type16 palette[], lon
 
 gms_gammaref_t Magnetic::gms_graphics_select_gamma(type8 bitmap[],
         type16 width, type16 height, type16 palette[]) {
-	static int is_initialized = false;
-	static gms_gammaref_t linear_gamma = NULL;
-
 	long color_usage[GMS_PALETTE_SIZE];
 	int color_count;
 	gms_gammaref_t contrast_gamma;
-
-	/* On first call, find and cache the uncorrected gamma table entry. */
-	if (!is_initialized) {
-		gms_gammaref_t gamma;
-
-		for (gamma = GMS_GAMMA_TABLE; gamma->level; gamma++) {
-			if (!gamma->is_corrected) {
-				linear_gamma = gamma;
-				break;
-			}
-		}
-
-		is_initialized = true;
-	}
 	assert(linear_gamma);
 
 	/*
@@ -1517,8 +1452,6 @@ void Magnetic::gms_graphics_timeout() {
 }
 
 void Magnetic::ms_showpic(type32 picture, type8 mode) {
-	static glui32 current_crc = 0;  /* CRC of the current picture */
-
 	type8 *bitmap, animated;
 	type16 width, height, palette[GMS_PALETTE_SIZE];
 	long picture_bytes;
@@ -1567,7 +1500,7 @@ void Magnetic::ms_showpic(type32 picture, type8 mode) {
 	 */
 	if (width == gms_graphics_width
 	        && height == gms_graphics_height
-	        && crc == current_crc
+	        && crc == pic_current_crc
 	        && gms_graphics_enabled && gms_graphics_are_displayed())
 		return;
 
@@ -1591,7 +1524,7 @@ void Magnetic::ms_showpic(type32 picture, type8 mode) {
 	gms_graphics_animated = animated;
 
 	/* Retain the new picture CRC. */
-	current_crc = crc;
+	pic_current_crc = crc;
 
 	/*
 	 * If graphics are enabled, ensure the window is displayed, set the
@@ -2525,9 +2458,6 @@ type16 Magnetic::gms_hint_handle(const ms_hint hints_[],
 }
 
 type8 Magnetic::ms_showhints(ms_hint *hints_) {
-	static int is_initialized = false;
-	static glui32 current_crc = 0;
-
 	type16 hint_count;
 	glui32 crc;
 	assert(hints_);
@@ -2545,7 +2475,7 @@ type8 Magnetic::ms_showhints(ms_hint *hints_) {
 	 * this is the first call, assign a new cursor array.
 	 */
 	crc = gms_get_buffer_crc(hints_, hint_count * sizeof(*hints_));
-	if (crc != current_crc || !is_initialized) {
+	if (crc != hints_current_crc || !hints_crc_initialized) {
 		int bytes;
 
 		/* Allocate new cursors, and set all to zero initial state. */
@@ -2558,8 +2488,8 @@ type8 Magnetic::ms_showhints(ms_hint *hints_) {
 		 * Retain the hints_ CRC, for later comparisons, and set is_initialized
 		 * flag.
 		 */
-		current_crc = crc;
-		is_initialized = true;
+		hints_current_crc = crc;
+		hints_crc_initialized = true;
 	}
 
 	/*
diff --git a/engines/glk/magnetic/magnetic.cpp b/engines/glk/magnetic/magnetic.cpp
index f8ab15a..00aa540 100644
--- a/engines/glk/magnetic/magnetic.cpp
+++ b/engines/glk/magnetic/magnetic.cpp
@@ -42,14 +42,13 @@ Magnetic::Magnetic(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(s
 		gms_graphics_picture(0), gms_graphics_new_picture(false),
 		gms_graphics_repaint(false), gms_graphics_active(false),
 		gms_graphics_interpreter(false), gms_graphics_off_screen(nullptr),
-		gms_graphics_on_screen(nullptr),// gms_graphics_current_gamma(Magnetic::GMS_GAMMA_TABLE),
+		gms_graphics_on_screen(nullptr), gms_graphics_current_gamma(Magnetic::GMS_GAMMA_TABLE),
 		gms_graphics_color_count(GMS_PALETTE_SIZE), gms_status_length(0),
-		gms_help_requested(false), gms_help_hints_silenced(false),
-		gms_output_buffer(nullptr), gms_output_allocation(0),gms_output_length(0),
-		gms_output_prompt(false), gms_hints(nullptr), gms_current_hint_node(0),
-		gms_hint_cursor(nullptr), gms_input_length(0), gms_input_cursor(0),
-		gms_undo_notification(false), gms_game_message(nullptr), gms_startup_called(false),
-		gms_main_called(false), gms_graphics_current_gamma(nullptr),
+		gms_help_requested(false), gms_help_hints_silenced(false), gms_output_buffer(nullptr),
+		gms_output_allocation(0),gms_output_length(0), gms_output_prompt(false),
+		gms_hints(nullptr), gms_current_hint_node(0), gms_hint_cursor(nullptr),
+		gms_input_length(0), gms_input_cursor(0), gms_undo_notification(false),
+		gms_game_message(nullptr), gms_startup_called(false), gms_main_called(false),
 		i_count(0), string_size(0), rseed(0), pc(0), arg1i(0), mem_size(0), properties(0),
 		fl_sub(0), fl_tab(0), fl_size(0), fp_tab(0), fp_size(0), zflag(0), nflag(0),
 		cflag(0), vflag(0), byte1(0), byte2(0), regnr(0), admode(0), opsize(0),
@@ -65,7 +64,9 @@ Magnetic::Magnetic(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(s
 		pos_table_index(-1), pos_table_max(-1), anim_repeat(0)
 #endif
 		, hints(nullptr), hint_contents(nullptr), xpos(0), bufpos(0), log_on(0),
-		ms_gfx_enabled(0), log1(nullptr), log2(nullptr) {
+		ms_gfx_enabled(0), log1(nullptr), log2(nullptr), GMS_LUMINANCE_WEIGHTS(299, 587, 114),
+		linear_gamma(nullptr), pic_current_crc(0), hints_current_crc(0),
+		hints_crc_initialized(false) {
 
 	Common::fill(&gms_graphics_palette[0], &gms_graphics_palette[GMS_PALETTE_SIZE], 0);
 	Common::fill(&gms_status_buffer[0], &gms_status_buffer[GMS_STATBUFFER_LENGTH], '\0');
@@ -78,11 +79,15 @@ Magnetic::Magnetic(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(s
 	undo_stat[0] = undo_stat[1] = 0;
 	Common::fill(&buffer[0], &buffer[80], 0);
 	Common::fill(&filename[0], &filename[256], 0);
+	Common::fill(&crc_table[0], &crc_table[BYTE_MAX + 1], 0);
 
 #ifndef NO_ANIMATION
 	Common::fill(&pos_table_count[0], &pos_table_count[MAX_POSITIONS], 0);
 #endif
 
+	luminance_weighting = GMS_LUMINANCE_WEIGHTS.red + GMS_LUMINANCE_WEIGHTS.green
+		+ GMS_LUMINANCE_WEIGHTS.blue;
+
 	g_vm = this;
 }
 
@@ -92,6 +97,15 @@ void Magnetic::runGame() {
 }
 
 void Magnetic::initialize() {
+	initializeSettings();
+	initializeCRC();
+	initializeLinearGamma();
+
+	// Close the already opened gamefile, since the Magnetic code will open it on it's own
+	_gameFile.close();
+}
+
+void Magnetic::initializeSettings() {
 	// Local handling for Glk special commands
 	if (ConfMan.hasKey("commands_enabled"))
 		gms_commands_enabled = ConfMan.getBool("commands_enabled");
@@ -110,9 +124,37 @@ void Magnetic::initialize() {
 	// Prompt enabled
 	if (ConfMan.hasKey("prompt_enabled"))
 		gms_prompt_enabled = ConfMan.getBool("prompt_enabled");
+}
 
-	// Close the already opened gamefile, since the Magnetic code will open it on it's own
-	_gameFile.close();
+void Magnetic::initializeCRC() {
+	/* CRC table initialization polynomial. */
+	const glui32 GMS_CRC_POLYNOMIAL = 0xedb88320;
+	uint32 crc;
+
+	for (uint index = 0; index < BYTE_MAX + 1; ++index) {
+		int bit;
+
+		crc = index;
+		for (bit = 0; bit < CHAR_BIT; bit++)
+			crc = crc & 1 ? GMS_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
+
+		crc_table[index] = crc;
+	}
+
+	/* CRC lookup table self-test, after is_initialized set -- recursion. */
+	assert(gms_get_buffer_crc("123456789", 9) == 0xcbf43926);
+}
+
+void Magnetic::initializeLinearGamma() {
+	/* Find and cache the uncorrected gamma table entry. */
+	gms_gammaref_t gamma;
+
+	for (gamma = GMS_GAMMA_TABLE; gamma->level; gamma++) {
+		if (!gamma->is_corrected) {
+			linear_gamma = gamma;
+			break;
+		}
+	}
 }
 
 Common::Error Magnetic::readSaveData(Common::SeekableReadStream *rs) {
diff --git a/engines/glk/magnetic/magnetic.h b/engines/glk/magnetic/magnetic.h
index 74d5b30..6f81df4 100644
--- a/engines/glk/magnetic/magnetic.h
+++ b/engines/glk/magnetic/magnetic.h
@@ -54,6 +54,7 @@ typedef gms_command_t *gms_commandref_t;
 class Magnetic : public GlkAPI {
 public:
 	static const gms_command_t GMS_COMMAND_TABLE[14];
+	static const gms_gamma_t GMS_GAMMA_TABLE[38];
 private:
 	GammaMode gms_gamma_mode;
 	bool gms_animation_enabled, gms_prompt_enabled;
@@ -244,16 +245,48 @@ private:
 	/* Hint support */
 	ms_hint *hints;
 	type8 *hint_contents;
+
+	/**
+	 * Weighting values for calculating the luminance of a color.  There are
+	 * two commonly used sets of values for these -- 299,587,114, taken from
+	 * NTSC (Never The Same Color) 1953 standards, and 212,716,72, which is the
+	 * set that modern CRTs tend to match.  The NTSC ones seem to give the best
+	 * subjective results.
+	 */
+	const gms_rgb_t GMS_LUMINANCE_WEIGHTS;
 private:
 	type8 buffer[80], xpos, bufpos, log_on, ms_gfx_enabled, filename[256];
 	Common::DumpFile *log1, *log2;
 private:
+	/* Method local statics in original code */
+	glui32 crc_table[BYTE_MAX + 1];
+	int luminance_weighting;
+	gms_gammaref_t linear_gamma;
+	uint32 pic_current_crc;			/* CRC of the current picture */
+	uint32 hints_current_crc;		/* CRC of hints */
+	bool hints_crc_initialized;
+private:
 	/**
 	 * Performs initialization
 	 */
 	void initialize();
 
 	/**
+	 * Initializes settings from the ScummVM configuration
+	 */
+	void initializeSettings();
+
+	/**
+	 * Initializes the CRC table
+	 */
+	void initializeCRC();
+
+	/**
+	 * Initializes the linear gamma entry
+	 */
+	void initializeLinearGamma();
+
+	/**
 	 * Fatal error handler.  The function returns, expecting the caller to
 	 * abort() or otherwise handle the error.
 	 */
diff --git a/engines/glk/magnetic/magnetic_types.h b/engines/glk/magnetic/magnetic_types.h
index 0d18614..4c39ee3 100644
--- a/engines/glk/magnetic/magnetic_types.h
+++ b/engines/glk/magnetic/magnetic_types.h
@@ -130,6 +130,9 @@ typedef const gms_gamma_t *gms_gammaref_t;
 /* R,G,B color triple definition. */
 struct gms_rgb_t {
 	int red, green, blue;
+
+	gms_rgb_t() : red(0), green(0), blue(0) {}
+	gms_rgb_t(int r, int g, int b) : red(r), green(b), blue(b) {}
 };
 typedef gms_rgb_t *gms_rgbref_t;
 


Commit: 53d9b6e1c67898f309fbb538c0edf937fd435e56
    https://github.com/scummvm/scummvm/commit/53d9b6e1c67898f309fbb538c0edf937fd435e56
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-11-22T18:49:07-08:00

Commit Message:
GLK: Fixing defines clashes between sub-engines

Changed paths:
    engines/glk/adrift/scare.h
    engines/glk/level9/detection.cpp
    engines/glk/level9/os_glk.h
    engines/glk/magnetic/glk.cpp
    engines/glk/magnetic/magnetic.cpp
    engines/glk/magnetic/magnetic_defs.h
    engines/glk/tads/tads2/line_source_file.cpp


diff --git a/engines/glk/adrift/scare.h b/engines/glk/adrift/scare.h
index 04a2872..f3bdc5a 100644
--- a/engines/glk/adrift/scare.h
+++ b/engines/glk/adrift/scare.h
@@ -47,8 +47,8 @@ typedef long sc_int;
 typedef unsigned long sc_uint;
 typedef int sc_bool;
 
-#define BYTE_MAX 0xff
-#define INTEGER_MAX 0x7fff
+enum { BYTE_MAX = 0xff };
+enum { INTEGER_MAX = 0x7fff };
 
 /* Enumerated confirmation types, passed to os_confirm(). */
 enum {
diff --git a/engines/glk/level9/detection.cpp b/engines/glk/level9/detection.cpp
index a366bc0..fef1bae 100644
--- a/engines/glk/level9/detection.cpp
+++ b/engines/glk/level9/detection.cpp
@@ -542,8 +542,8 @@ gln_game_tableref_t GameDetection::gln_gameid_identify_game() {
 		&& _startData[21] == _startData[23];
 
 	length = is_version2
-		? _startData[28] | _startData[29] << BITS_PER_CHAR
-		: _startData[0] | _startData[1] << BITS_PER_CHAR;
+		? _startData[28] | _startData[29] << BITS_PER_BYTE
+		: _startData[0] | _startData[1] << BITS_PER_BYTE;
 	if (length >= _fileSize)
 		return nullptr;
 
@@ -596,7 +596,7 @@ uint16 GameDetection::gln_get_buffer_crc(const void *void_buffer, size_t length,
 			int bit;
 
 			crc = (uint16)index;
-			for (bit = 0; bit < BITS_PER_CHAR; bit++)
+			for (bit = 0; bit < BITS_PER_BYTE; bit++)
 				crc = crc & 1 ? GLN_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
 
 			_crcTable[index] = crc;
@@ -611,11 +611,11 @@ uint16 GameDetection::gln_get_buffer_crc(const void *void_buffer, size_t length,
 	/* 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);
+		crc = _crcTable[(crc ^ buffer[index]) & BYTE_MAX] ^ (crc >> BITS_PER_BYTE);
 
 	/* Add in any requested NUL padding bytes. */
 	for (index = 0; index < padding; index++)
-		crc = _crcTable[crc & BYTE_MAX] ^ (crc >> BITS_PER_CHAR);
+		crc = _crcTable[crc & BYTE_MAX] ^ (crc >> BITS_PER_BYTE);
 
 	return crc;
 }
diff --git a/engines/glk/level9/os_glk.h b/engines/glk/level9/os_glk.h
index d617fb3..a4880b9 100644
--- a/engines/glk/level9/os_glk.h
+++ b/engines/glk/level9/os_glk.h
@@ -26,8 +26,8 @@
 namespace Glk {
 namespace Level9 {
 
-#define BYTE_MAX 0xff
-#define BITS_PER_CHAR 8
+enum { BYTE_MAX = 0xff };
+enum { BITS_PER_BYTE = 8 };
 
 extern bool gln_graphics_enabled;
 extern bool gln_graphics_possible;
diff --git a/engines/glk/magnetic/glk.cpp b/engines/glk/magnetic/glk.cpp
index ee4faeb..67fed76 100644
--- a/engines/glk/magnetic/glk.cpp
+++ b/engines/glk/magnetic/glk.cpp
@@ -289,7 +289,7 @@ glui32 Magnetic::gms_get_buffer_crc(const void *void_buffer, size_t length) {
 	 */
 	crc = 0xffffffff;
 	for (index = 0; index < length; index++)
-		crc = crc_table[(crc ^ buf[index]) & BYTE_MAX] ^ (crc >> CHAR_BIT);
+		crc = crc_table[(crc ^ buf[index]) & BYTE_MAX] ^ (crc >> BITS_PER_BYTE);
 	return crc ^ 0xffffffff;
 }
 
@@ -767,8 +767,8 @@ void Magnetic::gms_graphics_apply_animation_frame(type8 bitmap[],
 	 * even.  Here we'll calculate the real width of a mask, and also set a high
 	 * bit for later on.
 	 */
-	mask_width = (((frame_width - 1) / CHAR_BIT) + 2) & (~1);
-	mask_hibit = 1 << (CHAR_BIT - 1);
+	mask_width = (((frame_width - 1) / BITS_PER_BYTE) + 2) & (~1);
+	mask_hibit = 1 << (BITS_PER_BYTE - 1);
 
 	/*
 	 * Initialize row index components; these are optimizations to avoid the
@@ -808,8 +808,8 @@ void Magnetic::gms_graphics_apply_animation_frame(type8 bitmap[],
 				type8 mask_byte;
 
 				/* Isolate the mask byte, and test the transparency bit. */
-				mask_byte = mask[mask_row + (x / CHAR_BIT)];
-				if ((mask_byte & (mask_hibit >> (x % CHAR_BIT))) != 0)
+				mask_byte = mask[mask_row + (x / BITS_PER_BYTE)];
+				if ((mask_byte & (mask_hibit >> (x % BITS_PER_BYTE))) != 0)
 					continue;
 			}
 
diff --git a/engines/glk/magnetic/magnetic.cpp b/engines/glk/magnetic/magnetic.cpp
index 00aa540..e768dee 100644
--- a/engines/glk/magnetic/magnetic.cpp
+++ b/engines/glk/magnetic/magnetic.cpp
@@ -135,7 +135,7 @@ void Magnetic::initializeCRC() {
 		int bit;
 
 		crc = index;
-		for (bit = 0; bit < CHAR_BIT; bit++)
+		for (bit = 0; bit < BITS_PER_BYTE; bit++)
 			crc = crc & 1 ? GMS_CRC_POLYNOMIAL ^ (crc >> 1) : crc >> 1;
 
 		crc_table[index] = crc;
diff --git a/engines/glk/magnetic/magnetic_defs.h b/engines/glk/magnetic/magnetic_defs.h
index f2b18c0..6760569 100644
--- a/engines/glk/magnetic/magnetic_defs.h
+++ b/engines/glk/magnetic/magnetic_defs.h
@@ -42,18 +42,10 @@ typedef int16 type16s;
 typedef uint32 type32;
 typedef int32 type32s;
 
-#ifndef BYTE_MAX
-#define BYTE_MAX 255
-#endif
-#ifndef CHAR_BIT
-#define CHAR_BIT 8
-#endif
-#ifndef UINT16_MAX
-#define UINT16_MAX 0xffff
-#endif
-#ifndef INT32_MAX
-#define INT32_MAX 0x7fffffff
-#endif
+enum { BYTE_MAX = 255 };
+enum { BITS_PER_BYTE = 8 };
+enum { UINT16_MAX = 0xffff };
+enum { INT32_MAX = 0x7fffffff };
 
 #define MAX_HINTS 260
 #define MAX_HCONTENTS 30000
diff --git a/engines/glk/tads/tads2/line_source_file.cpp b/engines/glk/tads/tads2/line_source_file.cpp
index 1cf90b2..24a7d67 100644
--- a/engines/glk/tads/tads2/line_source_file.cpp
+++ b/engines/glk/tads/tads2/line_source_file.cpp
@@ -31,7 +31,7 @@ namespace Glk {
 namespace TADS {
 namespace TADS2 {
 
-#define BYTE_MAX 0xff
+enum { BYTE_MAX = 0xff };
 
 /* initialize a pre-allocated linfdef, skipping debugger page setup */
 void linfini2(mcmcxdef *mctx, linfdef *linf,


Commit: 852e3592771ebb440f505f130ef22bcb68c3720a
    https://github.com/scummvm/scummvm/commit/852e3592771ebb440f505f130ef22bcb68c3720a
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2019-11-22T18:49:07-08:00

Commit Message:
GLK: MAGNETIC: Hooking up loading/saving games

Changed paths:
  R engines/glk/magnetic/main.cpp
    engines/glk/magnetic/glk.cpp
    engines/glk/magnetic/magnetic.cpp
    engines/glk/magnetic/magnetic.h
    engines/glk/module.mk


diff --git a/engines/glk/magnetic/glk.cpp b/engines/glk/magnetic/glk.cpp
index 67fed76..1ef0d83 100644
--- a/engines/glk/magnetic/glk.cpp
+++ b/engines/glk/magnetic/glk.cpp
@@ -3979,5 +3979,15 @@ void Magnetic::writeChar(char c) {
 	glk_put_char(c);
 }
 
+void Magnetic::script_write(type8 c) {
+	if (log_on == 2)
+		log1->writeByte(c);
+}
+
+void Magnetic::transcript_write(type8 c) {
+	if (log2)
+		log2->writeByte(c);
+}
+
 } // End of namespace Magnetic
 } // End of namespace Glk
diff --git a/engines/glk/magnetic/magnetic.cpp b/engines/glk/magnetic/magnetic.cpp
index e768dee..588b66e 100644
--- a/engines/glk/magnetic/magnetic.cpp
+++ b/engines/glk/magnetic/magnetic.cpp
@@ -66,7 +66,7 @@ Magnetic::Magnetic(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(s
 		, hints(nullptr), hint_contents(nullptr), xpos(0), bufpos(0), log_on(0),
 		ms_gfx_enabled(0), log1(nullptr), log2(nullptr), GMS_LUMINANCE_WEIGHTS(299, 587, 114),
 		linear_gamma(nullptr), pic_current_crc(0), hints_current_crc(0),
-		hints_crc_initialized(false) {
+		hints_crc_initialized(false), _saveData(nullptr), _saveSize(0) {
 
 	Common::fill(&gms_graphics_palette[0], &gms_graphics_palette[GMS_PALETTE_SIZE], 0);
 	Common::fill(&gms_status_buffer[0], &gms_status_buffer[GMS_STATBUFFER_LENGTH], '\0');
@@ -157,14 +157,26 @@ void Magnetic::initializeLinearGamma() {
 	}
 }
 
+type8 Magnetic::ms_load_file(const char *name, type8 *ptr, type16 size) {
+	_saveData = ptr;
+	_saveSize = size;
+
+	return loadGame().getCode() == Common::kNoError ? 0 : 1;
+}
+
 Common::Error Magnetic::readSaveData(Common::SeekableReadStream *rs) {
-	// TODO
-	return Common::kReadingFailed;
+	return rs->read(_saveData, _saveSize) == _saveSize ? Common::kNoError : Common::kReadingFailed;
+}
+
+type8 Magnetic::ms_save_file(const char *name, type8 *ptr, type16 size) {
+	_saveData = ptr;
+	_saveSize = size;
+
+	return saveGame().getCode() == Common::kNoError ? 0 : 1;
 }
 
 Common::Error Magnetic::writeGameData(Common::WriteStream *ws) {
-	// TODO
-	return Common::kWritingFailed;
+	return ws->write(_saveData, _saveSize) == _saveSize ? Common::kNoError : Common::kWritingFailed;
 }
 
 } // End of namespace Magnetic
diff --git a/engines/glk/magnetic/magnetic.h b/engines/glk/magnetic/magnetic.h
index 6f81df4..bb6e935 100644
--- a/engines/glk/magnetic/magnetic.h
+++ b/engines/glk/magnetic/magnetic.h
@@ -254,6 +254,9 @@ private:
 	 * subjective results.
 	 */
 	const gms_rgb_t GMS_LUMINANCE_WEIGHTS;
+
+	type8 *_saveData;
+	size_t _saveSize;
 private:
 	type8 buffer[80], xpos, bufpos, log_on, ms_gfx_enabled, filename[256];
 	Common::DumpFile *log1, *log2;
@@ -1381,6 +1384,20 @@ public:
 	}
 
 	/**
+	 * The Magnetic engine currently doesn't support loading savegames from the GMM
+	 */
+	virtual bool canLoadGameStateCurrently() override {
+		return false;
+	}
+
+	/**
+	 * The Magnetic engine currently doesn't support saving games from the GMM
+	 */
+	virtual bool canSaveGameStateCurrently() override {
+		return false;
+	}
+
+	/**
 	 * Load a savegame from the passed Quetzal file chunk stream
 	 */
 	virtual Common::Error readSaveData(Common::SeekableReadStream *rs) override;
diff --git a/engines/glk/magnetic/main.cpp b/engines/glk/magnetic/main.cpp
deleted file mode 100644
index 444aa0a..0000000
--- a/engines/glk/magnetic/main.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- */
-
-#include "glk/magnetic/magnetic.h"
-#include "common/file.h"
-#include "common/savefile.h"
-#include "common/system.h"
-
-namespace Glk {
-namespace Magnetic {
-
-#define WIDTH 78
-
-type8 Magnetic::ms_load_file(const char *name, type8 *ptr, type16 size) {
-	assert(name);
-	Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(name);
-	if (!file)
-		return 1;
-
-	if (file->read(ptr, size) != size) {
-		delete file;
-		return 1;
-	}
-
-	delete file;
-	return 0;
-}
-
-type8 Magnetic::ms_save_file(const char *name, type8 *ptr, type16 size) {
-	assert(name);
-	Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving(name);
-	if (!file)
-		return 1;
-
-	if (file->write(ptr, size) != size) {
-		delete file;
-		return 1;
-	}
-
-	file->finalize();
-	delete file;
-	return 0;
-}
-
-void Magnetic::script_write(type8 c) {
-	if (log_on == 2)
-		log1->writeByte(c);
-}
-
-void Magnetic::transcript_write(type8 c) {
-	if (log2)
-		log2->writeByte(c);
-}
-
-} // End of namespace Magnetic
-} // End of namespace Glk
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 7c6308f..74d3321 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -263,8 +263,7 @@ MODULE_OBJS += \
 	magnetic/detection.o \
 	magnetic/emu.o \
 	magnetic/glk.o \
-	magnetic/magnetic.o \
-	magnetic/main.o
+	magnetic/magnetic.o
 endif
 
 ifdef ENABLE_GLK_QUEST




More information about the Scummvm-git-logs mailing list