aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordreammaster2019-11-19 02:24:54 +0000
committerPaul Gilbert2019-11-22 18:49:07 -0800
commit1c6762104792fe84390c114b6eafe85dc7867d13 (patch)
treedf902f872856d67d1c036d73cb6968725137fcf9
parentaff46219ef5189da48bf13838b5d4b6885d21a03 (diff)
downloadscummvm-rg350-1c6762104792fe84390c114b6eafe85dc7867d13.tar.gz
scummvm-rg350-1c6762104792fe84390c114b6eafe85dc7867d13.tar.bz2
scummvm-rg350-1c6762104792fe84390c114b6eafe85dc7867d13.zip
GLK: MAGNETIC: Added subengine files
-rw-r--r--engines/glk/magnetic/defs.h471
-rw-r--r--engines/glk/magnetic/emu.cpp3613
-rw-r--r--engines/glk/magnetic/glk.cpp5393
-rw-r--r--engines/glk/magnetic/graphics.cpp507
-rw-r--r--engines/glk/magnetic/magnetic.cpp58
-rw-r--r--engines/glk/magnetic/magnetic.h148
-rw-r--r--engines/glk/magnetic/magnetic_types.h5
-rw-r--r--engines/glk/magnetic/main.cpp176
-rw-r--r--engines/glk/magnetic/myth.cpp133
-rw-r--r--engines/glk/magnetic/sound.cpp92
-rw-r--r--engines/glk/module.mk4
11 files changed, 9685 insertions, 915 deletions
diff --git a/engines/glk/magnetic/defs.h b/engines/glk/magnetic/defs.h
new file mode 100644
index 0000000000..b448200b72
--- /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 1d63f5944a..fe6c66c53d 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;
+}
- if (restarting) {
+/* zero all registers and flags and load the game */
+
+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;
+
+ sd = (type8)((dict_size != 0L) ? 1 : 0); /* if (sd) => separate dict */
- if (!(code = new byte[mem_size]) || !(string2 = new byte[string2_size]) ||
- !(restart = new byte[undo_size]) || (sd &&
- !(dict = new byte[dict_size]))) {
+ 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 */
+ 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 (!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;
+ }
+
+ 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;
+}
+
+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;
+
+ offset = read_l(gfx_data + 4 * pic);
+#ifdef SAVEMEM
+ type32 datasize;
+
+ 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
+
+ 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;
+#endif
+
+#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;
+
+ 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++;
}
}
- // Try loading a music file
- if (_sndFile.isOpen() && _sndFile.size() >= 8) {
- _sndFile.seek(0);
+ if (got_anim == 0) {
+ type8 command = command_table[command_index];
+ command_index++;
+
+ pos_table_max = -1;
+ pos_table_index = -1;
- if (_sndFile.readUint32BE() != MKTAG('M', 'a', 'S', 'd'))
+ 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++;
- init_snd(_sndFile.readUint32LE());
+ /* 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
+}
- if (!_gfxFile.isOpen() || _gfxFile.size() < 8)
- return 1;
- _gfxFile.seek(0);
- uint tag = _gfxFile.readUint32BE();
+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;
+}
- 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());
+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;
}
-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;
+#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;
- delete[] gfx_data;
- delete[] gfx_buf;
- delete[] gfx2_hdr;
- delete[] gfx2_buf;
- gfx_data = gfx_buf = gfx2_hdr = gfx2_buf = 0;
+ Common::String s = "D0:";
+ for (j = 0; j < 8; j++)
+ s += Common::String::format(" %8.8lx", (long) read_reg(j, 3));
+ s += "\nA0:";
- gfx_fp.close();
+ 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());
+}
- gfx2_name.clear();
- gfx_ver = 0;
- gfxtable = table_dist = 0;
+type32 ms_count(void) {
+ return i_count;
+}
- pos_table_size = 0;
- command_index = 0;
- anim_repeat = 0;
- pos_table_index = -1;
- pos_table_max = -1;
+/* align register pointer for word/byte accesses */
- lastchar = 0;
- delete[] hints;
- delete[] hint_contents;
- hints = nullptr;
- hint_contents = nullptr;
+type8 *reg_align(type8 *ptr, type8 size) {
+ if (size == 1)
+ ptr += 2;
+ if (size == 0)
+ ptr += 3;
+ return ptr;
+}
- delete[] snd_hdr;
- delete[] snd_buf;
- snd_hdr = nullptr;
- snd_hsize = 0;
- snd_buf = nullptr;
+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 0000000000..db15cd136c
--- /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@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 d9c62a1b89..0000000000
--- 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");
- return 0;
- }
-
- pos_array[*count] = pos_table[i][anim_table[i].flag];
-
- (*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)
- {
- byte 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:
- ttable = command_table[command_index];
- command_index++;
-
- if (ttable - 1 >= MAX_POSITIONS)
- {
- error("animation table too short");
- return 0;
- }
-
- anim_table[ttable - 1].flag = (int16)(command_table[command_index] - 1);
- command_index++;
- anim_table[ttable - 1].count = (int16)(command_table[command_index] - 1);
- command_index++;
-
- /* Workaround for Wonderland "catter" animation */
- if (v4_id == 0)
- {
- if (gfx2_name == "catter") {
- 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:
- pos_table_max = command_table[command_index];
- pos_table_index = 0;
- command_index++;
- break;
- case 0x03:
- 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;
-
- case 0x04:
- 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:
- error("unknown animation command");
- command_index = -1;
- return 0;
- }
- }
- }
-
- return got_anim;
-}
-
-byte *Magnetic::ms_get_anim_frame(int16 number, uint16 *width, uint16 *height, byte **mask) {
- 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;
- }
-
- return nullptr;
-}
-
-} // End of namespace Magnetic
-} // End of namespace Glk
diff --git a/engines/glk/magnetic/magnetic.cpp b/engines/glk/magnetic/magnetic.cpp
index 3f9917bf7b..3689668789 100644
--- a/engines/glk/magnetic/magnetic.cpp
+++ b/engines/glk/magnetic/magnetic.cpp
@@ -21,41 +21,20 @@
*/
#include "glk/magnetic/magnetic.h"
-#include "common/config-manager.h"
-#include "common/translation.h"
+#include "glk/magnetic/defs.h"
namespace Glk {
namespace Magnetic {
-Magnetic::Magnetic(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
-
- vm_exited_cleanly(false), dlimit(0xffffffff), slimit(0xffffffff), log_on(0),
- 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),
- 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(0), gfx2_buf(nullptr), gfx2_hsize(0),
- snd_buf(nullptr), snd_hdr(nullptr), snd_hsize(0), undo_pc(0), undo_size(0),
- gfxtable(0), table_dist(0), v4_id(0), next_table(1), pos_table_size(0),
- command_table(nullptr), command_index(-1), pos_table_index(-1), pos_table_max(-1),
- anim_repeat(0) {
+Magnetic *g_vm;
- // Emu fields
- undo[0] = undo[1] = nullptr;
- undo_stat[0] = undo_stat[1] = 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[2][18], 0),
- Common::fill(&pos_table_count[0], &pos_table_count[MAX_POSITIONS], 0);
+Magnetic::Magnetic(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
+ g_vm = this;
}
void Magnetic::runGame() {
- if (!is_gamefile_valid())
- return;
-
- // TODO
+ gms_startup_code(0, nullptr);
+ gms_main();
}
Common::Error Magnetic::readSaveData(Common::SeekableReadStream *rs) {
@@ -68,30 +47,5 @@ Common::Error Magnetic::writeGameData(Common::WriteStream *ws) {
return Common::kWritingFailed;
}
-bool Magnetic::is_gamefile_valid() {
- if (_gameFile.size() < 8) {
- GUIErrorMessage(_("This is too short to be a valid Glulx file."));
- return false;
- }
-
- if (_gameFile.readUint32BE() != MKTAG('G', 'l', 'u', 'l')) {
- GUIErrorMessage(_("This is not a valid Glulx file."));
- return false;
- }
-
- // We support version 2.0 through 3.1.*
- uint32 vers = _gameFile.readUint32BE();
- if (vers < 0x20000) {
- GUIErrorMessage(_("This Glulx file is too old a version to execute."));
- return false;
- }
- if (vers >= 0x30200) {
- GUIErrorMessage(_("This Glulx file is too new a version to execute."));
- return false;
- }
-
- return true;
-}
-
} // End of namespace Magnetic
} // End of namespace Glk
diff --git a/engines/glk/magnetic/magnetic.h b/engines/glk/magnetic/magnetic.h
index c926ede9e6..33f9a4da5a 100644
--- a/engines/glk/magnetic/magnetic.h
+++ b/engines/glk/magnetic/magnetic.h
@@ -20,6 +20,8 @@
*
*/
+/* Based on Magnetic interpreter version 2.3 */
+
#ifndef GLK_MAGNETIC_MAGNETIC
#define GLK_MAGNETIC_MAGNETIC
@@ -35,146 +37,6 @@ namespace Magnetic {
*/
class Magnetic : public GlkAPI {
public:
- Common::File _hintFile;
- Common::File _gfxFile;
- Common::File _sndFile;
- bool vm_exited_cleanly;
- uint dlimit, slimit;
- int log_on;
-
- // Emu fields
- uint32 dreg[8], areg[8], i_count, string_size, rseed, pc, arg1i, mem_size;
- uint16 properties, fl_sub, fl_tab, fl_size, fp_tab, fp_size;
- byte zflag, nflag, cflag, vflag, byte1, byte2, regnr, admode, opsize;
- byte *arg1, *arg2, is_reversible, running, tmparg[4];
- byte lastchar, version, sd;
- byte *decode_table, *restart, *code, *string, *string2;
- byte *string3, *dict;
- byte quick_flag, gfx_ver, *gfx_buf, *gfx_data;
- byte *gfx2_hdr, *gfx2_buf;
- Common::String gfx2_name;
- uint16 gfx2_hsize;
- byte *snd_buf, *snd_hdr;
- uint16 snd_hsize;
- Common::File gfx_fp;
- uint32 undo_regs[2][18], undo_pc, undo_size;
- byte *undo[2], undo_stat[2];
- uint16 gfxtable, table_dist;
- uint16 v4_id, next_table;
-
- ms_hint *hints;
- byte *hint_contents;
- picture anim_frame_table[MAX_ANIMS];
- uint16 pos_table_size;
- uint16 pos_table_count[MAX_POSITIONS];
- ms_position pos_table[MAX_POSITIONS][MAX_ANIMS];
- byte *command_table;
- int command_index;
- lookup anim_table[MAX_POSITIONS];
- int pos_table_index;
- int pos_table_max;
- ms_position pos_array[MAX_FRAMES];
- byte anim_repeat;
-
-
-private:
- /**
- * Validates the game file, and if it's invalid, displays an error dialog
- */
- bool is_gamefile_valid();
-
- /**
- * \defgroup Emu
- * @{
- */
-
- /**
- * Loads the interpreter with a game
- * @return 0 = failure, 1 = success(without graphics or graphics failed),
- * 2 = success(with graphics)
- */
- int ms_init(bool restarting = false);
-
- /**
- * Stops further processing of opcodes
- */
- void ms_stop() { running = false; }
-
- /**
- * Detects if game is running
- */
- bool ms_is_running() const { return running; }
-
- /**
- * Returns true if running a Magnetic Windows game
- */
- bool ms_is_magwin() const { return version == 4; }
-
- /**
- * Frees all allocated ressources
- */
- void ms_freemem();
-
- /**@}*/
-
- /**
- * \defgroup Graphics support methods
- * @{
- */
-
- byte init_gfx1(size_t size);
-
- byte init_gfx2(size_t size);
-
- /**
- * Displays or hides a picture
- * @param c number of image to be displayed
- * @param mode 0 = means gfx off, 1 gfx on thumbnails, 2 gfx on normal
- *
- * @remarks For retrieving the raw data of a picture call ms_extract
- */
- void ms_showpic(int c, byte mode);
-
- /**
- * Returns true if a given line is blank
- */
- bool is_blank(uint16 line, uint16 width) const;
-
- byte *ms_extract1(byte pic, uint16 *w, uint16 *h, uint16 *pal);
-
- int16 find_name_in_header(const Common::String &name, bool upper);
-
- void extract_frame(const picture *pic);
-
- byte *ms_extract2(const char *name, uint16 *w, uint16 *h, uint16 *pal, byte *is_anim);
-
- byte *ms_extract(uint32 pic, uint16 *w, uint16 *h, uint16 *pal, byte *is_anim);
-
- byte ms_animate(ms_position **positions, uint16 *count);
-
- byte *ms_get_anim_frame(int16 number, uint16 *width, uint16 *height, byte **mask);
-
- bool ms_anim_is_repeating() const { return anim_repeat; }
-
- void write_reg(int i, int s, uint32 val) {
- // TODO
- }
-
- /**@}*/
-
- /**
- * \defgroup Sound support methods
- * @{
- */
-
- byte init_snd(size_t size);
-
- int16 find_name_in_sndheader(const Common::String &name);
-
- byte *sound_extract(const Common::String &name, uint32 *length, uint16 *tempo);
-
- /**@}*/
-public:
/**
* Constructor
*/
@@ -188,7 +50,9 @@ public:
/**
* Returns the running interpreter type
*/
- virtual InterpreterType getInterpreterType() const override { return INTERPRETER_MAGNETIC; }
+ virtual InterpreterType getInterpreterType() const override {
+ return INTERPRETER_MAGNETIC;
+ }
/**
* Load a savegame from the passed Quetzal file chunk stream
@@ -202,6 +66,8 @@ public:
virtual Common::Error writeGameData(Common::WriteStream *ws) override;
};
+extern Magnetic *g_vm;
+
} // End of namespace Magnetic
} // End of namespace Glk
diff --git a/engines/glk/magnetic/magnetic_types.h b/engines/glk/magnetic/magnetic_types.h
index cfef76557e..5c8cad5077 100644
--- a/engines/glk/magnetic/magnetic_types.h
+++ b/engines/glk/magnetic/magnetic_types.h
@@ -24,6 +24,7 @@
#define GLK_MAGNETIC_TYPES
#include "common/scummsys.h"
+#include "common/algorithm.h"
namespace Glk {
namespace Magnetic {
@@ -47,7 +48,7 @@ struct lookup {
};
struct picture {
- byte * data;
+ byte *data;
uint32 data_size;
uint16 width;
uint16 height;
@@ -121,7 +122,7 @@ struct ms_position {
struct ms_hint {
uint16 elcount;
uint16 nodetype;
- byte *content;
+ const char *content;
uint16 links[MAX_HITEMS];
uint16 parent;
diff --git a/engines/glk/magnetic/main.cpp b/engines/glk/magnetic/main.cpp
new file mode 100644
index 0000000000..1f0128f36c
--- /dev/null
+++ b/engines/glk/magnetic/main.cpp
@@ -0,0 +1,176 @@
+/* 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 "common/file.h"
+#include "common/savefile.h"
+#include "common/system.h"
+
+namespace Glk {
+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) {
+ 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 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 script_write(type8 c) {
+ if (log_on == 2)
+ log1->writeByte(c);
+}
+
+void 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
diff --git a/engines/glk/magnetic/myth.cpp b/engines/glk/magnetic/myth.cpp
new file mode 100644
index 0000000000..3f8127e613
--- /dev/null
+++ b/engines/glk/magnetic/myth.cpp
@@ -0,0 +1,133 @@
+/* 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.
+ *
+ */
+
+/*
+ * Patches Myth C64 disk image
+ * Written by David Kinder, based on code by Niclas Karlsson
+ */
+
+#define D64_SIZE (174848L)
+unsigned char image[D64_SIZE];
+
+void ungarble(unsigned char *block, signed long key) {
+ unsigned char d;
+ int i, j;
+
+ d = (unsigned char)((key & 0x07) ^ 0xFF);
+ if (d < 0xFF) {
+ i = d;
+ j = d + 1;
+ while (j < 0x100)
+ block[j++] ^= block[i];
+ }
+
+ i = 0xFF;
+ j = d - 1;
+ while (j >= 0)
+ block[j--] ^= block[i--];
+
+ for (i = 0; i < 128; i++) {
+ d = block[i];
+ block[i] = block[255 - i];
+ block[255 - i] = d;
+ }
+}
+
+void garble(unsigned char *block, int i1, int j1, int i2) {
+ unsigned char d;
+ int i, j;
+
+ for (i = 0; i < 128; i++) {
+ d = block[i];
+ block[i] = block[255 - i];
+ block[255 - i] = d;
+ }
+
+ i = i1;
+ j = -1;
+ while (j < j1)
+ block[++j] ^= block[++i];
+
+ i = i2;
+ j = 0x100;
+ while (j > i2 + 1)
+ block[--j] ^= block[i];
+}
+
+/* This routine does the patching. */
+void fixDiskImage(int code) {
+ unsigned char *block = NULL;
+
+ switch (code) {
+ case 0:
+ /* code[0x3056] = 0x60 */
+ block = image + 0x1600;
+ ungarble(block, 0x27L);
+ block[0x56] = 0x60;
+ garble(block, 0x07, 0xF7, 0xF8);
+ break;
+ case 1:
+ /* code[0x3148] = 0x60 */
+ /* code[0x314D] = 0x11 */
+ block = image + 0x1700;
+ ungarble(block, 0x28L);
+ block[0x48] = 0x60;
+ block[0x4D] = 0x11;
+ garble(block, 0x00, 0xFE, 0xFF);
+ break;
+ }
+}
+
+void readDiskImage(const char *filename) {
+ FILE *file = fopen(filename, "r+b");
+ if (file == NULL)
+ exit(1);
+ fread(image, 1, D64_SIZE, file);
+}
+
+void writeDiskImage(const char *filename) {
+ FILE *file = fopen(filename, "w+b");
+ if (file == NULL)
+ exit(1);
+ fwrite(image, 1, D64_SIZE, file);
+}
+
+int main(int argc, char **argv) {
+ if (argc == 4) {
+ int code = -1;
+ if (sscanf(argv[3], "%d", &code) == 1) {
+ if ((code >= 0) && (code <= 1)) {
+ readDiskImage(argv[1]);
+ fixDiskImage(code);
+ writeDiskImage(argv[2]);
+ printf("Myth patched.\n");
+ return 0;
+ }
+ }
+ }
+
+ printf("Removes password protection from Magnetic Scrolls' Myth.\n");
+ printf("Use: myth input.d64 output.d64 code\n");
+ printf("If code is 0, the password protection is completely removed.\n");
+ printf("If code is 1, any user name and password will be accepted.\n");
+ return 0;
+}
diff --git a/engines/glk/magnetic/sound.cpp b/engines/glk/magnetic/sound.cpp
deleted file mode 100644
index 3ea74fb8fe..0000000000
--- a/engines/glk/magnetic/sound.cpp
+++ /dev/null
@@ -1,92 +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_snd(size_t size) {
- if (!(snd_buf = new byte[MAX_MUSIC_SIZE])) {
- return 1;
- }
-
- snd_hsize = _sndFile.readUint16LE();
- if (!(snd_hdr = new byte[snd_hsize])) {
- delete[] snd_buf;
- snd_buf = nullptr;
- return 1;
- }
-
- if (_sndFile.read(snd_hdr, snd_hsize) != snd_hsize) {
- delete[] snd_buf;
- delete[] snd_hdr;
- snd_buf = nullptr;
- snd_hdr = nullptr;
- return 1;
- }
-
- return 2;
-}
-
-int16 Magnetic::find_name_in_sndheader(const Common::String &name) {
- int16 header_pos = 0;
-
- while (header_pos < snd_hsize) {
- const char *hname = (const char *)(snd_hdr + header_pos);
- if (name == hname)
- return header_pos;
- header_pos += 18;
- }
-
- return -1;
-}
-
-byte *Magnetic::sound_extract(const Common::String &name, uint32 *length, uint16 *tempo) {
- uint32 offset = 0;
- int16 header_pos = -1;
-
- if (header_pos < 0)
- header_pos = find_name_in_sndheader(name);
- if (header_pos < 0)
- return 0;
-
- *tempo = READ_BE_UINT16(snd_hdr + header_pos + 8);
- offset = READ_BE_UINT32(snd_hdr + header_pos + 10);
- *length = READ_BE_UINT32(snd_hdr + header_pos + 14);
-
- if (offset != 0) {
- if (!snd_buf)
- return nullptr;
- if (!_sndFile.seek(offset))
- return nullptr;
- if (_sndFile.read(snd_buf, *length) != *length)
- return nullptr;
-
- return snd_buf;
- }
-
- return nullptr;
-}
-
-} // End of namespace Magnetic
-} // End of namespace Glk
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 8c1daeeb13..7c6308fd06 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -262,9 +262,9 @@ ifdef ENABLE_GLK_MAGNETIC
MODULE_OBJS += \
magnetic/detection.o \
magnetic/emu.o \
- magnetic/graphics.o \
+ magnetic/glk.o \
magnetic/magnetic.o \
- magnetic/sound.o
+ magnetic/main.o
endif
ifdef ENABLE_GLK_QUEST