diff options
author | Paul Gilbert | 2019-09-02 20:57:19 -0700 |
---|---|---|
committer | Paul Gilbert | 2019-09-25 20:13:26 -0700 |
commit | 60c860c6a6c37b95ab8265d658cbcc144d51153b (patch) | |
tree | e21dac197fcd374bb8d348873c91230141e1fddf /engines/glk/adrift/os_glk.cpp | |
parent | c893c5a60b8298aa99ae1a47dea51c6f04c419bc (diff) | |
download | scummvm-rg350-60c860c6a6c37b95ab8265d658cbcc144d51153b.tar.gz scummvm-rg350-60c860c6a6c37b95ab8265d658cbcc144d51153b.tar.bz2 scummvm-rg350-60c860c6a6c37b95ab8265d658cbcc144d51153b.zip |
GLK: ADRIFT: Skeleton sub-engine commit
Diffstat (limited to 'engines/glk/adrift/os_glk.cpp')
-rw-r--r-- | engines/glk/adrift/os_glk.cpp | 3565 |
1 files changed, 3565 insertions, 0 deletions
diff --git a/engines/glk/adrift/os_glk.cpp b/engines/glk/adrift/os_glk.cpp new file mode 100644 index 0000000000..98f4f34169 --- /dev/null +++ b/engines/glk/adrift/os_glk.cpp @@ -0,0 +1,3565 @@ +/* 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/adrift/adrift.h" +#include "glk/adrift/scare.h" +#include "glk/adrift/scprotos.h" +#include "glk/glk.h" +#include "glk/streams.h" +#include "glk/windows.h" +#include "common/textconsole.h" + +namespace Glk { +namespace Adrift { + +/* + * Module notes: + * + * o The Glk interface makes no effort to set text colors, background colors, + * and so forth, and minimal effort to set fonts and other style effects. + */ + +#undef _WIN32 /* Gargoyle */ + +/* + * True and false definitions -- usually defined in glkstart.h, but we need + * them early, so we'll define them here too. We also need nullptr, but that's + * normally from stdio.h or one of it's cousins. + */ +#ifndef FALSE +# define FALSE 0 +#endif +#ifndef TRUE +# define TRUE (!FALSE) +#endif + + +/*---------------------------------------------------------------------*/ +/* Module variables, miscellaneous other stuff */ +/*---------------------------------------------------------------------*/ + +/* Glk SCARE interface version number. */ +static const glui32 GSC_PORT_VERSION = 0x00010310; + +/* Two windows, one for the main text, and one for a status line. */ +static winid_t gsc_main_window = nullptr, + gsc_status_window = nullptr; + +/* + * Transcript stream and input log. These are nullptr if there is no current + * collection of these strings. + */ +static strid_t gsc_transcript_stream = nullptr, + gsc_inputlog_stream = nullptr; + +/* Input read log stream, for reading back an input log. */ +static strid_t gsc_readlog_stream = nullptr; + +/* Options that may be turned off or set by command line flags. */ +static int gsc_commands_enabled = TRUE, + gsc_abbreviations_enabled = TRUE, + gsc_unicode_enabled = TRUE; + +/* Adrift game to interpret. */ +static sc_game gsc_game = nullptr; + +/* Special out-of-band os_confirm() options used locally with os_glk. */ +static const sc_int GSC_CONF_SUBTLE_HINT = INT_MAX, + GSC_CONF_UNSUBTLE_HINT = INT_MAX - 1, + GSC_CONF_CONTINUE_HINTS = INT_MAX - 2; + +/* Forward declaration of event wait functions, and a short delay. */ +static void gsc_event_wait_2 (glui32 wait_type_1, + glui32 wait_type_2, event_t *event); +static void gsc_event_wait (glui32 wait_type, event_t *event); +static void gsc_short_delay(); + + +/*---------------------------------------------------------------------*/ +/* Glk port utility functions */ +/*---------------------------------------------------------------------*/ + +/* + * gsc_fatal() + * + * Fatal error handler. The function returns, expecting the caller to + * abort() or otherwise handle the error. + */ +static void +gsc_fatal (const char *string) +{ + /* + * If the failure happens too early for us to have a window, print + * the message to stderr. + */ + if (!gsc_main_window) { + error("\n\nINTERNAL ERROR: %s\n", string); + } + + /* Cancel all possible pending window input events. */ + g_vm->glk_cancel_line_event (gsc_main_window, nullptr); + g_vm->glk_cancel_char_event (gsc_main_window); + + /* Print a message indicating the error, and exit. */ + g_vm->glk_set_window (gsc_main_window); + g_vm->glk_set_style (style_Normal); + g_vm->glk_put_string ("\n\nINTERNAL ERROR: "); + g_vm->glk_put_string ((char *) 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"); +} + + +/* + * gsc_malloc() + * + * Non-failing malloc; call gsc_fatal and exit if memory allocation fails. + */ +static void * +gsc_malloc (size_t size) +{ + void *pointer; + + pointer = malloc (size > 0 ? size : 1); + if (!pointer) + { + gsc_fatal ("GLK: Out of system memory"); + g_vm->glk_exit (); + } + + return pointer; +} + + +/*---------------------------------------------------------------------*/ +/* Glk port locale data */ +/*---------------------------------------------------------------------*/ + +/* Unicode values up to 256 are equivalent to iso 8859-1. */ +static const glui32 GSC_ISO_8859_EQUIVALENCE = 256; + +/* + * Lookup table pair for converting a given single character into unicode and + * iso 8859-1 (the lower byte of the unicode representation, assuming an upper + * byte of zero), and an ascii substitute should nothing else be available. + * Tables are 256 elements; although the first 128 characters of a codepage + * are usually standard ascii, making tables full-sized allows for support of + * codepages where they're not (dingbats, for example). + */ +enum { GSC_TABLE_SIZE = 256 }; +typedef struct { + const glui32 unicode[GSC_TABLE_SIZE]; + const sc_char *const ascii[GSC_TABLE_SIZE]; +} gsc_codepages_t; + +/* + * Locale contains a name and a pair of codepage structures, a main one and + * an alternate. The latter is intended for monospaced output. + */ +typedef struct { + const sc_char *const name; + const gsc_codepages_t main; + const gsc_codepages_t alternate; +} gsc_locale_t; + + +/* + * Locale for Latin1 -- cp1252 and cp850. + * + * The ascii representations of characters in this table are based on the + * general look of the characters, rather than pronounciation. Accented + * characters are generally rendered unaccented, and box drawing, shading, + * and other non-alphanumeric glyphs as either a similar shape, or as a + * character that might be recognizable as what it's trying to emulate. + */ +static const gsc_locale_t GSC_LATIN1_LOCALE = { + "Latin1", + /* cp1252 to unicode. */ +{ { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x000a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0022, 0x0023, + 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, + 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, + 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, + 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, + 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, + 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, + 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, + 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, + 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, + 0x007e, 0x0000, 0x20ac, 0x0000, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, + 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0000, 0x017d, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, + 0x2122, 0x0161, 0x203a, 0x0153, 0x0000, 0x017e, 0x0178, 0x00a0, 0x00a1, + 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, + 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0, 0x00b1, 0x00b2, 0x00b3, + 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, + 0x00bd, 0x00be, 0x00bf, 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, + 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, + 0x00cf, 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, 0x00e0, + 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, + 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2, + 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, + 0x00fc, 0x00fd, 0x00fe, 0x00ff }, + /* cp1252 to ascii. */ + { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, " ", + "\n", nullptr, nullptr, "\n", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", + "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", + "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", + "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", + "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "}", "~", nullptr, "E", nullptr, ",", + "f", ",,", "...", "+", "#", "^", "%", "S", "<", "OE", nullptr, + "Z", nullptr, nullptr, "'", "'", "\"", "\"", "*", "-", "-", "~", + "[TM]","s", ">", "oe", nullptr, "z", "Y", " ", "!", "c", "GBP", + "*", "Y", "|", "S", "\"", "(C)", "a", "<<", "-", "-", "(R)", + "-", "o", "+/-", "2", "3", "'", "u", "P", "*", ",", "1", + "o", ">>", "1/4", "1/2", "3/4", "?", "A", "A", "A", "A", "A", + "A", "AE", "C", "E", "E", "E", "E", "I", "I", "I", "I", + "D", "N", "O", "O", "O", "O", "O", "x", "O", "U", "U", + "U", "U", "Y", "p", "ss", "a", "a", "a", "a", "a", "a", + "ae", "c", "e", "e", "e", "e", "i", "i", "i", "i", "d", + "n", "o", "o", "o", "o", "o", "/", "o", "u", "u", "u", + "u", "y", "P", "y" } }, + /* cp850 to unicode. */ +{ { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x000a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0022, 0x0023, + 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, + 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, + 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, + 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, + 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, + 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, + 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, + 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, + 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, + 0x007e, 0x0000, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, + 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, + 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, + 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192, 0x00e1, 0x00ed, + 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00ae, 0x00ac, + 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, + 0x2524, 0x00c1, 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, + 0x00a2, 0x00a5, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, + 0x00e3, 0x00c3, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, + 0x00a4, 0x00f0, 0x00d0, 0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce, + 0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, 0x00d3, + 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, 0x00de, 0x00da, + 0x00db, 0x00d9, 0x00fd, 0x00dd, 0x00af, 0x00b4, 0x00ad, 0x00b1, 0x2017, + 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, 0x00b0, 0x00a8, 0x00b7, 0x00b9, + 0x00b3, 0x00b2, 0x25a0, 0x00a0 }, + /* cp850 to ascii. */ + { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, " ", + "\n", nullptr, nullptr, "\n", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", + "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", + "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", + "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", + "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "}", "~", nullptr, "C", "u", "e", + "a", "a", "a", "a", "c", "e", "e", "e", "i", "i", "i", + "A", "A", "E", "ae", "AE", "o", "o", "o", "u", "u", "y", + "O", "U", "o", "GBP", "O", "x", "f", "a", "i", "o", "u", + "n", "N", "a", "o", "?", "(R)", "-", "1/2", "1/4", "i", "<<", + ">>", "#", "#", "#", "|", "+", "A", "A", "A", "(C)", "+", + "|", "+", "+", "c", "Y", "+", "+", "+", "+", "+", "-", + "+", "a", "A", "+", "+", "+", "+", "+", "=", "+", "*", + "d", "D", "E", "E", "E", "i", "I", "I", "I", "+", "+", + ".", ".", "|", "I", ".", "O", "ss", "O", "O", "o", "O", + "u", "p", "P", "U", "U", "U", "y", "Y", "-", "'", "-", + "+/-", "=", "3/4", "P", "S", "/", ",", "deg", "\"", "*", "1", + "3", "2", ".", " " } } +}; + + +/* + * Locale for Cyrillic -- cp1251 and cp866. + * + * The ascii representations in this table, for alphabetic characters, follow + * linguistic rather than appearance rules, the essence of gost 16876-71. + * Capitalized cyrillic letters that translate to multiple ascii characters + * have the first ascii character only of the sequence translated. This gives + * the best appearance in normal sentences, but is not optimal in a run of + * all capitals (headings, for example). For non-alphanumeric characters, + * the general appearance and shape of the character being emulated is used. + */ +static const gsc_locale_t GSC_CYRILLIC_LOCALE = { + "Cyrillic", + /* cp1251 to unicode. */ +{ { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x000a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0022, 0x0023, + 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, + 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, + 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, + 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, + 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, + 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, + 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, + 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, + 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, + 0x007e, 0x0000, 0x0402, 0x0403, 0x201a, 0x0453, 0x201e, 0x2026, 0x2020, + 0x2021, 0x20ac, 0x2030, 0x0409, 0x2039, 0x040a, 0x040c, 0x040b, 0x040f, + 0x0452, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x0000, + 0x2122, 0x0459, 0x203a, 0x045a, 0x045c, 0x045b, 0x045f, 0x00a0, 0x040e, + 0x045e, 0x0408, 0x00a4, 0x0490, 0x00a6, 0x00a7, 0x0401, 0x00a9, 0x0404, + 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0407, 0x00b0, 0x00b1, 0x0406, 0x0456, + 0x0491, 0x00b5, 0x00b6, 0x00b7, 0x0451, 0x2116, 0x0454, 0x00bb, 0x0458, + 0x0405, 0x0455, 0x0457, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, + 0x0416, 0x0417, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, + 0x041f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, 0x0430, + 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, + 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, 0x0440, 0x0441, 0x0442, + 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044a, 0x044b, + 0x044c, 0x044d, 0x044e, 0x044f }, + /* cp1251 to gost 16876-71 ascii. */ + { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, " ", + "\n", nullptr, nullptr, "\n", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", + "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", + "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", + "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", + "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "}", "~", nullptr, nullptr, nullptr, ",", + nullptr, ",,", "...", "+", "#", "E", "%", nullptr, "<", nullptr, nullptr, + nullptr, nullptr, nullptr, "'", "'", "\"", "\"", "*", "-", "-", nullptr, + "[TM]",nullptr, ">", nullptr, nullptr, nullptr, nullptr, " ", nullptr, nullptr, nullptr, + "*", "G", "|", "S", "Jo", "(C)", "Je", "<<", "-", "-", "(R)", + "Ji", "o", "+/-", "I", "i", "g", "u", "P", "*", "jo", nullptr, + "je", ">>", "j", "S", "s", "ji", "A", "B", "V", "G", "D", + "E", "Zh", "Z", "I", "Jj", "K", "L", "M", "N", "O", "P", + "R", "S", "T", "U", "F", "Kh", "C", "Ch", "Sh", "Shh", "\"", + "Y", "'", "Eh", "Ju", "Ja", "a", "b", "v", "g", "d", "e", + "zh", "z", "i", "jj", "k", "l", "m", "n", "o", "p", "r", + "s", "t", "u", "f", "kh", "c", "ch", "sh", "shh", "\"", "y", + "'", "eh", "ju", "ja" } }, + /* cp866 to unicode. */ +{ { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x000a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0022, 0x0023, + 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, + 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, + 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, + 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, + 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, + 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, + 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, + 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, + 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, + 0x007e, 0x0000, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, + 0x0417, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, + 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, 0x0430, 0x0431, + 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043a, + 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, 0x2591, 0x2592, 0x2593, 0x2502, + 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, + 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, + 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, + 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, + 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x0440, + 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, + 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, 0x0401, 0x0451, 0x0404, + 0x0454, 0x0407, 0x0457, 0x040e, 0x045e, 0x00b0, 0x2022, 0x00b7, 0x221a, + 0x2116, 0x00a4, 0x25a0, 0x00a0 }, + /* cp866 to gost 16876-71 ascii. */ + { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, " ", + "\n", nullptr, nullptr, "\n", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", + "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", + "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", + "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", + "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "}", "~", nullptr, "A", "B", "V", + "G", "D", "E", "Zh", "Z", "I", "Jj", "K", "L", "M", "N", + "O", "P", "R", "S", "T", "U", "F", "Kh", "C", "Ch", "Sh", + "Shh", "\"", "Y", "'", "Eh", "Ju", "Ja", "a", "b", "v", "g", + "d", "e", "zh", "z", "i", "jj", "k", "l", "m", "n", "o", + "p", "#", "#", "#", "|", "+", "+", "+", "+", "+", "+", + "|", "+", "+", "+", "+", "+", "+", "+", "+", "+", "-", + "+", "+", "+", "+", "+", "+", "+", "+", "|", "+", "+", + "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", + "+", ".", ".", ".", ".", "r", "s", "t", "u", "f", "kh", + "c", "ch", "sh", "shh", "\"", "y", "'", "eh", "ju", "ja", "Jo", + "jo", "Je", "je", "Ji", "ji", nullptr, nullptr, "deg", "*", "*", nullptr, + nullptr, "*", ".", " " } } +}; + + +/*---------------------------------------------------------------------*/ +/* Glk port locale control and conversion functions */ +/*---------------------------------------------------------------------*/ + +#ifdef GLK_MODULE_UNICODE +static const sc_bool gsc_has_unicode = TRUE; +#else +static const sc_bool gsc_has_unicode = FALSE; + +/* Gestalt selector and stubs for non-unicode capable libraries. */ +static const glui32 gestalt_Unicode = 15; + +static void glk_put_char_uni (glui32 ch) +{ + glui32 unused; + unused = ch; + gsc_fatal ("GLK: Stub unicode function called"); +} + +static void glk_request_line_event_uni (winid_t win, + glui32 *buf, glui32 maxlen, glui32 initlen) +{ + winid_t unused1; + glui32 *unused2; + glui32 unused3, unused4; + unused1 = win; + unused2 = buf; + unused3 = maxlen; + unused4 = initlen; + gsc_fatal ("GLK: Stub unicode function called"); +} + +#endif + +/* + * Known valid character printing range. Some Glk libraries aren't accurate + * about what will and what won't print when queried with g_vm->glk_gestalt(), so + * we also make an explicit range check against guaranteed to print chars. + */ +static const glui32 GSC_MIN_PRINTABLE = ' ', + GSC_MAX_PRINTABLE = '~'; + + +/* List of pointers to supported and available locales, nullptr terminated. */ +static const gsc_locale_t *const GSC_AVAILABLE_LOCALES[] = { + &GSC_LATIN1_LOCALE, + &GSC_CYRILLIC_LOCALE, + nullptr +}; + +/* + * The locale for the game, set below explicitly or on game startup, and + * a fallback locale to use in case none has been set. + */ +static const gsc_locale_t *gsc_locale = nullptr; +static const gsc_locale_t *const gsc_fallback_locale = &GSC_LATIN1_LOCALE; + + +/* + * gsc_set_locale() + * + * Set a locale explicitly from the name passed in. + */ +static void +gsc_set_locale (const sc_char *name) +{ + const gsc_locale_t *matched = nullptr; + const gsc_locale_t *const *iterator; + assert (name); + + /* + * Search locales for a matching name, abbreviated if necessary. Stop on + * the first match found. + */ + for (iterator = GSC_AVAILABLE_LOCALES; *iterator; iterator++) + { + const gsc_locale_t *const locale = *iterator; + + if (sc_strncasecmp (name, locale->name, strlen (name)) == 0) + { + matched = locale; + break; + } + } + + /* If matched, set the global locale. */ + if (matched) + gsc_locale = matched; +} + + +/* + * gsc_put_char_uni() + * + * Wrapper around g_vm->glk_put_char_uni(). Handles, inelegantly, the problem of + * having to write transcripts as ascii. + */ +static void +gsc_put_char_uni (glui32 unicode, const char *ascii) +{ + /* If there is an transcript stream, temporarily disconnect it. */ + if (gsc_transcript_stream) + g_vm->glk_window_set_echo_stream (gsc_main_window, nullptr); + + g_vm->glk_put_char_uni (unicode); + + /* Print ascii to the transcript, then reattach it. */ + if (gsc_transcript_stream) + { + if (ascii) + g_vm->glk_put_string_stream (gsc_transcript_stream, (char *) ascii); + else + g_vm->glk_put_char_stream (gsc_transcript_stream, '?'); + + g_vm->glk_window_set_echo_stream (gsc_main_window, gsc_transcript_stream); + } +} + + +/* + * gsc_put_char_locale() + * + * Write a single character using the supplied locale. Select either the + * main or the alternate codepage depending on the flag passed in. + */ +static void +gsc_put_char_locale (sc_char ch, + const gsc_locale_t *locale, sc_bool is_alternate) +{ + const gsc_codepages_t *codepage; + unsigned char character; + glui32 unicode; + const char *ascii; + + /* + * Select either the main or the alternate codepage for this locale, and + * retrieve the unicode and ascii representations of the character. + */ + codepage = is_alternate ? &locale->alternate : &locale->main; + character = (unsigned char) ch; + unicode = codepage->unicode[character]; + ascii = codepage->ascii[character]; + + /* + * If a unicode representation exists, use for either iso 8859-1 or, if + * possible, direct unicode output. + */ + if (unicode > 0) + { + /* + * If unicode is in the range 1-255, this value is directly equivalent + * to the iso 8859-1 representation; otherwise the character has no + * direct iso 8859-1 glyph. + */ + if (unicode < GSC_ISO_8859_EQUIVALENCE) + { + /* + * If the iso 8859-1 character is one that this Glk library will + * print exactly, print and return. We add a check here for the + * guaranteed printable characters, since some Glk libraries don't + * return the correct values for gestalt_CharOutput for these. + */ + if (unicode == '\n' + || (unicode >= GSC_MIN_PRINTABLE && unicode <= GSC_MAX_PRINTABLE) + || g_vm->glk_gestalt (gestalt_CharOutput, + unicode) == gestalt_CharOutput_ExactPrint) + { + g_vm->glk_put_char ((unsigned char) unicode); + return; + } + } + + /* + * If no usable iso 8859-1 representation, see if unicode is enabled and + * if the Glk library can print the character exactly. If yes, output + * the character that way. + * + * TODO Using unicode output currently disrupts transcript output. Any + * echo stream connected for a transcript here will be a text rather than + * a unicode stream, so probably won't output the character correctly. + * For now, if there's a transcript, we try to write ascii output. + */ + if (gsc_unicode_enabled) + { + if (g_vm->glk_gestalt (gestalt_CharOutput, + unicode) == gestalt_CharOutput_ExactPrint) + { + gsc_put_char_uni (unicode, ascii); + return; + } + } + } + + /* + * No success with iso 8859-1 or unicode, so try for an ascii substitute. + * Substitute strings use only 7-bit ascii, and so all are safe to print + * directly with Glk. + */ + if (ascii) + { + g_vm->glk_put_string ((char *) ascii); + return; + } + + /* No apparent way to output this character, so print a '?'. */ + g_vm->glk_put_char ('?'); +} + + +/* + * gsc_put_char() + * gsc_put_char_alternate() + * gsc_put_buffer_using() + * gsc_put_buffer() + * gsc_put_string() + * gsc_put_string_alternate() + * + * Public functions for writing using the current or fallback locale. + */ +static void +gsc_put_char (const sc_char character) +{ + const gsc_locale_t *locale; + + locale = gsc_locale ? gsc_locale : gsc_fallback_locale; + gsc_put_char_locale (character, locale, FALSE); +} + +static void +gsc_put_char_alternate (const sc_char character) +{ + const gsc_locale_t *locale; + + locale = gsc_locale ? gsc_locale : gsc_fallback_locale; + gsc_put_char_locale (character, locale, TRUE); +} + +static void +gsc_put_buffer_using (const sc_char *buffer, + sc_int length, void (*putchar_function) (sc_char)) +{ + sc_int index_; + + for (index_ = 0; index_ < length; index_++) + putchar_function (buffer[index_]); +} + +static void +gsc_put_buffer (const sc_char *buffer, sc_int length) +{ + assert (buffer); + + gsc_put_buffer_using (buffer, length, gsc_put_char); +} + +static void +gsc_put_string (const sc_char *string) +{ + assert (string); + + gsc_put_buffer_using (string, strlen (string), gsc_put_char); +} + +static void +gsc_put_string_alternate (const sc_char *string) +{ + assert (string); + + gsc_put_buffer_using (string, strlen (string), gsc_put_char_alternate); +} + + +/* + * gsc_unicode_to_locale() + * gsc_unicode_buffer_to_locale() + * + * Convert a unicode character back to an sc_char through a locale. Used for + * reverse translations in line input. Returns '?' if there is no translation + * available. + */ +static sc_char +gsc_unicode_to_locale (glui32 unicode, const gsc_locale_t *locale) +{ + const gsc_codepages_t *codepage; + sc_int character; + + /* Always use the main codepage for input. */ + codepage = &locale->main; + + /* + * Search the unicode table sequentially for the input character. This is + * inefficient, but because game input is usually not copious, excusable. + */ + for (character = 0; character < GSC_TABLE_SIZE; character++) + { + if (codepage->unicode[character] == unicode) + break; + } + + /* Return the character translation, or '?' if none. */ + return character < GSC_TABLE_SIZE ? (sc_char) character : '?'; +} + +static void +gsc_unicode_buffer_to_locale (const glui32 *unicode, sc_int length, + sc_char *buffer, const gsc_locale_t *locale) +{ + sc_int index_; + + for (index_ = 0; index_ < length; index_++) + buffer[index_] = gsc_unicode_to_locale (unicode[index_], locale); +} + + +/* + * gsc_read_line_locale() + * + * Read in a line and translate out of the given locale. Returns the count + * of characters placed in the buffer. + */ +static sc_int +gsc_read_line_locale (sc_char *buffer, + sc_int length, const gsc_locale_t *locale) +{ + event_t event; + + /* + * If we have unicode, we have to use it to ensure that characters not in + * the Latin1 locale are properly translated. + */ + if (gsc_unicode_enabled) + { + glui32 *unicode; + + /* + * Allocate a unicode buffer long enough to hold all the characters, + * then read in a unicode line. + */ + unicode = (glui32 *)gsc_malloc (length * sizeof (*unicode)); + g_vm->glk_request_line_event_uni (gsc_main_window, unicode, length, 0); + gsc_event_wait (evtype_LineInput, &event); + + /* Convert the unicode buffer out, then free it. */ + gsc_unicode_buffer_to_locale (unicode, event.val1, buffer, locale); + free (unicode); + + /* Return the count of characters placed in the buffer. */ + return event.val1; + } + + /* No success with unicode, so fall back to standard line input. */ + g_vm->glk_request_line_event (gsc_main_window, buffer, length, 0); + gsc_event_wait (evtype_LineInput, &event); + + /* Return the count of characters placed in the buffer. */ + return event.val1; +} + + +/* + * gsc_read_line() + * + * Public function for reading using the current or fallback locale. + */ +static sc_int +gsc_read_line (sc_char *buffer, sc_int length) +{ + const gsc_locale_t *locale; + + locale = gsc_locale ? gsc_locale : gsc_fallback_locale; + return gsc_read_line_locale (buffer, length, locale); +} + + +/*---------------------------------------------------------------------*/ +/* Glk port status line functions */ +/*---------------------------------------------------------------------*/ + +/* + * Slop for right-justification of status lines, as an attempt to compensate + * for the fact that some characters in a games status line may use more than + * one position when printed, a particular problem in gost 16876-71 Cyrillic. + */ +static const sc_int GSC_STATUS_SLOP = 10; + +/* Size of saved status buffer used for non-windowing Glk status lines. */ +enum { GSC_STATUS_BUFFER_LENGTH = 74 }; + +/* Whitespace characters, used to detect empty status elements. */ +static const sc_char *const GSC_WHITESPACE = "\t\n\v\f\r "; + + +/* + * gsc_is_string_usable() + * + * Return TRUE if string is non-null, not zero-length or contains characters + * other than whitespace. + */ +static sc_bool +gsc_is_string_usable (const sc_char *string) +{ + /* If non-null, scan for any non-space character. */ + if (string) + { + sc_int index_; + + for (index_ = 0; string[index_] != '\0'; index_++) + { + if (!strchr (GSC_WHITESPACE, string[index_])) + return TRUE; + } + } + + /* nullptr, or no characters other than whitespace. */ + return FALSE; +} + + +/* + * gsc_status_update() + * + * Update the status line from the current game state. This is for windowing + * Glk libraries. + */ +static void +gsc_status_update() +{ + glui32 width, height; + uint index; + assert (gsc_status_window); + + g_vm->glk_window_get_size (gsc_status_window, &width, &height); + if (height > 0) + { + const sc_char *room; + + g_vm->glk_window_clear (gsc_status_window); + g_vm->glk_window_move_cursor (gsc_status_window, 0, 0); + g_vm->glk_set_window (gsc_status_window); + + g_vm->glk_set_style(style_User1); + for (index = 0; index < width; index++) + g_vm->glk_put_char (' '); + g_vm->glk_window_move_cursor (gsc_status_window, 0, 0); + + /* See if the game is indicating any current player room. */ + room = sc_get_game_room (gsc_game); + if (!gsc_is_string_usable (room)) + { + /* + * Player location is indeterminate, so print out a generic status, + * showing the game name and author. + */ + g_vm->glk_window_move_cursor (gsc_status_window, 1, 0); + gsc_put_string (sc_get_game_name (gsc_game)); + g_vm->glk_put_string (" | "); + gsc_put_string (sc_get_game_author (gsc_game)); + } + else + { + const sc_char *status; + char score[64]; + + /* Print the player location. */ + g_vm->glk_window_move_cursor (gsc_status_window, 1, 0); + gsc_put_string (room); + + /* Get the game's status line, or if none, format score. */ + status = sc_get_game_status_line (gsc_game); + if (!gsc_is_string_usable (status)) + { + sprintf (score, "Score: %ld", sc_get_game_score (gsc_game)); + status = score; + } + + /* Print the status line or score at window right, if it fits. */ + if (width > strlen (status) + GSC_STATUS_SLOP + 1) + { + glui32 position; + + position = width - strlen (status) - GSC_STATUS_SLOP; + g_vm->glk_window_move_cursor (gsc_status_window, position - 1, 0); + gsc_put_string (status); + } + } + + g_vm->glk_set_window (gsc_main_window); + } +} + + +/* + * gsc_status_safe_strcat() + * + * Helper for gsc_status_print(), concatenates strings only up to the + * available length. + */ +static void +gsc_status_safe_strcat (char *dest, size_t length, const char *src) +{ + size_t available, src_length; + + /* Append only as many characters as will fit. */ + src_length = strlen (src); + available = length - strlen (dest) - 1; + if (available > 0) + strncat (dest, src, src_length < available ? src_length : available); +} + + +/* + * gsc_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 +gsc_status_print() +{ + static char current_status[GSC_STATUS_BUFFER_LENGTH + 1]; + + const sc_char *room; + + /* Do nothing if the game isn't indicating any current player room. */ + room = sc_get_game_room (gsc_game); + if (gsc_is_string_usable (room)) + { + char buffer[GSC_STATUS_BUFFER_LENGTH + 1]; + const sc_char *status; + char score[64]; + + /* Make an attempt at a status line, starting with player location. */ + strcpy (buffer, ""); + gsc_status_safe_strcat (buffer, sizeof (buffer), room); + + /* Get the game's status line, or if none, format score. */ + status = sc_get_game_status_line (gsc_game); + if (!gsc_is_string_usable (status)) + { + sprintf (score, "Score: %ld", sc_get_game_score (gsc_game)); + status = score; + } + + /* Append the status line or score. */ + gsc_status_safe_strcat (buffer, sizeof (buffer), " | "); + gsc_status_safe_strcat (buffer, sizeof (buffer), status); + + /* If this matches the current saved status line, do nothing more. */ + if (strcmp (buffer, current_status) != 0) + { + /* Bracket, and output the status line buffer. */ + g_vm->glk_put_string ("[ "); + gsc_put_string (buffer); + g_vm->glk_put_string (" ]\n"); + + /* Save the details of the printed status buffer. */ + strcpy (current_status, buffer); + } + } +} + + +/* + * gsc_status_notify() + * + * Front end function for updating status. Either updates the status window + * or prints the status line to the main window. + */ +static void +gsc_status_notify() +{ + if (gsc_status_window) + gsc_status_update (); + else + gsc_status_print (); +} + + +/* + * gsc_status_redraw() + * + * Redraw the contents of any status window with the constructed status string. + * This function should be called on the appropriate Glk window resize and + * arrange events. + */ +static void +gsc_status_redraw() +{ + if (gsc_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 (gsc_status_window); + g_vm->glk_window_set_arrangement (parent, + winmethod_Above | winmethod_Fixed, 1, nullptr); + gsc_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 gsc_help_requested = FALSE, + gsc_help_hints_silenced = FALSE; + +/* Font descriptor type, encapsulating size and monospaced boolean. */ +typedef struct { + sc_bool is_monospaced; + sc_int size; +} gsc_font_size_t; + +/* Font stack and attributes for nesting tags. */ +enum { GSC_MAX_STYLE_NESTING = 32 }; +static gsc_font_size_t gsc_font_stack[GSC_MAX_STYLE_NESTING]; +static glui32 gsc_font_index = 0; +static glui32 gsc_attribute_bold = 0, + gsc_attribute_italic = 0, + gsc_attribute_underline = 0, + gsc_attribute_secondary_color = 0; + +/* Notional default font size, and limit font sizes. */ +static const sc_int GSC_DEFAULT_FONT_SIZE = 12, + GSC_MEDIUM_FONT_SIZE = 14, + GSC_LARGE_FONT_SIZE = 16; + +/* Milliseconds per second and timeouts count for delay tags. */ +static const glui32 GSC_MILLISECONDS_PER_SECOND = 1000; +static const glui32 GSC_TIMEOUTS_COUNT = 10; + +/* Number of hints to refuse before offering to end hint display. */ +static const sc_int GSC_HINT_REFUSAL_LIMIT = 5; + +/* The keypresses used to cancel any <wait x.x> early. */ +static const glui32 GSC_CANCEL_WAIT_1 = ' ', + GSC_CANCEL_WAIT_2 = keycode_Return; + + +/* + * gsc_output_register_help_request() + * gsc_output_silence_help_hints() + * gsc_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 +gsc_output_register_help_request() +{ + gsc_help_requested = TRUE; +} + +static void +gsc_output_silence_help_hints() +{ + gsc_help_hints_silenced = TRUE; +} + +static void +gsc_output_provide_help_hint() +{ + if (gsc_help_requested && !gsc_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"); + + gsc_help_requested = FALSE; + g_vm->glk_set_style (style_Normal); + } +} + + +/* + * gsc_set_glk_style() + * + * Set a Glk style based on the top of the font stack and attributes. + */ +static void gsc_set_glk_style() +{ + sc_bool is_monospaced; + sc_int font_size; + + /* Get the current font stack top, or default value. */ + if (gsc_font_index > 0) + { + is_monospaced = gsc_font_stack[gsc_font_index - 1].is_monospaced; + font_size = gsc_font_stack[gsc_font_index - 1].size; + } + else + { + is_monospaced = FALSE; + font_size = GSC_DEFAULT_FONT_SIZE; + } + + /* + * Map the font and current attributes into a Glk style. Because Glk styles + * aren't cumulative this has to be done by precedences. + */ + if (is_monospaced) + { + /* + * No matter the size or attributes, if monospaced use Preformatted + * style, as it's all we have. + */ + g_vm->glk_set_style (style_Preformatted); + } + else + { + /* + * For large and medium point sizes, use Header or Subheader styles + * respectively. + */ + if (font_size >= GSC_LARGE_FONT_SIZE) + g_vm->glk_set_style (style_Header); + else if (font_size >= GSC_MEDIUM_FONT_SIZE) + g_vm->glk_set_style (style_Subheader); + else + { + /* + * For bold, use Subheader; for italics, underline, or secondary + * color, use Emphasized. + */ + if (gsc_attribute_bold > 0) + g_vm->glk_set_style (style_Subheader); + else if (gsc_attribute_italic > 0 + || gsc_attribute_underline > 0 + || gsc_attribute_secondary_color > 0) + g_vm->glk_set_style (style_Emphasized); + else + { + /* + * There's nothing special about this text, so drop down to + * Normal style. + */ + g_vm->glk_set_style (style_Normal); + } + } + } +} + + +/* + * gsc_handle_font_tag() + * gsc_handle_endfont_tag() + * + * Push the settings of a font tag onto the font stack, and pop on end of + * font tag. Set the appropriate Glk style. + */ +static void +gsc_handle_font_tag (const sc_char *argument) +{ + /* Ignore the call on stack overrun. */ + if (gsc_font_index < GSC_MAX_STYLE_NESTING) + { + sc_char *lower, *face, *size; + sc_bool is_monospaced; + sc_int index_, font_size; + + /* Get the current top of stack, or default on empty stack. */ + if (gsc_font_index > 0) + { + is_monospaced = gsc_font_stack[gsc_font_index - 1].is_monospaced; + font_size = gsc_font_stack[gsc_font_index - 1].size; + } + else + { + is_monospaced = FALSE; + font_size = GSC_DEFAULT_FONT_SIZE; + } + + /* Copy and convert argument to all lowercase. */ + lower = (sc_char *)gsc_malloc (strlen (argument) + 1); + strcpy (lower, argument); + for (index_ = 0; lower[index_] != '\0'; index_++) + lower[index_] = g_vm->glk_char_to_lower (lower[index_]); + + /* Find any face= portion of the tag argument. */ + face = strstr (lower, "face="); + if (face) + { + /* + * There may be plenty of monospaced fonts, but we do only courier + * and terminal. + */ + is_monospaced = strncmp (face, "face=\"courier\"", 14) == 0 + || strncmp (face, "face=\"terminal\"", 15) == 0; + } + + /* Find the size= portion of the tag argument. */ + size = strstr (lower, "size="); + if (size) + { + sc_uint value; + + /* Deal with incremental and absolute sizes. */ + if (strncmp (size, "size=+", 6) == 0 + && sscanf (size, "size=+%lu", &value) == 1) + font_size += value; + else if (strncmp (size, "size=-", 6) == 0 + && sscanf (size, "size=-%lu", &value) == 1) + font_size -= value; + else if (sscanf (size, "size=%lu", &value) == 1) + font_size = value; + } + + /* Done with tag argument copy. */ + free (lower); + + /* + * Push the new font setting onto the font stack, and set Glk style. + */ + gsc_font_stack[gsc_font_index].is_monospaced = is_monospaced; + gsc_font_stack[gsc_font_index++].size = font_size; + gsc_set_glk_style(); + } +} + +static void +gsc_handle_endfont_tag() +{ + /* Unless underrun, pop the font stack and set Glk style. */ + if (gsc_font_index > 0) + { + gsc_font_index--; + gsc_set_glk_style(); + } +} + + +/* + * gsc_handle_attribute_tag() + * + * Increment the required attribute nesting counter, or decrement on end + * tag. Set the appropriate Glk style. + */ +static void +gsc_handle_attribute_tag (sc_int tag) +{ + /* + * Increment the required attribute nesting counter, and set Glk style. + */ + switch (tag) + { + case SC_TAG_BOLD: + gsc_attribute_bold++; + break; + case SC_TAG_ITALICS: + gsc_attribute_italic++; + break; + case SC_TAG_UNDERLINE: + gsc_attribute_underline++; + break; + case SC_TAG_COLOR: + gsc_attribute_secondary_color++; + break; + default: + break; + } + gsc_set_glk_style(); +} + +static void +gsc_handle_endattribute_tag (sc_int tag) +{ + /* + * Decrement the required attribute nesting counter, unless underrun, and + * set Glk style. + */ + switch (tag) + { + case SC_TAG_ENDBOLD: + if (gsc_attribute_bold > 0) + gsc_attribute_bold--; + break; + case SC_TAG_ENDITALICS: + if (gsc_attribute_italic > 0) + gsc_attribute_italic--; + break; + case SC_TAG_ENDUNDERLINE: + if (gsc_attribute_underline > 0) + gsc_attribute_underline--; + break; + case SC_TAG_ENDCOLOR: + if (gsc_attribute_secondary_color > 0) + gsc_attribute_secondary_color--; + break; + default: + break; + } + gsc_set_glk_style(); +} + + +/* + * gsc_handle_wait_tag() + * + * If Glk offers timers, delay for the requested period. Otherwise, this + * function does nothing. + */ +static void gsc_handle_wait_tag(const sc_char *argument) +{ + double delay = 0.0; + + /* Ignore the wait tag if the Glk doesn't have timers. */ + if (!g_vm->glk_gestalt (gestalt_Timer, 0)) + return; + + /* Determine the delay time, and convert to milliseconds. */ + if (sscanf (argument, "%lf", &delay) == 1 && delay > 0.0) + { + glui32 milliseconds, timeout; + + /* + * Work with timeouts at 1/10 of the wait period, to minimize Glk + * timer jitter. Allow the timeout to be canceled by keypress, as a + * user convenience. + */ + milliseconds = (glui32) (delay * GSC_MILLISECONDS_PER_SECOND); + timeout = milliseconds / GSC_TIMEOUTS_COUNT; + if (timeout > 0) + { + glui32 delayed; + sc_bool is_completed; + + /* Request timer events, and let a keypress cancel the wait. */ + g_vm->glk_request_char_event (gsc_main_window); + g_vm->glk_request_timer_events (timeout); + + /* Loop until delay completed or canceled by a keypress. */ + is_completed = TRUE; + for (delayed = 0; delayed < milliseconds; delayed += timeout) + { + event_t event; + + gsc_event_wait_2 (evtype_CharInput, evtype_Timer, &event); + if (event.type == evtype_CharInput) + { + /* Cancel the delay, or reissue the input request. */ + if (event.val1 == GSC_CANCEL_WAIT_1 + || event.val1 == GSC_CANCEL_WAIT_2) + { + is_completed = FALSE; + break; + } + else + g_vm->glk_request_char_event (gsc_main_window); + } + } + + /* Cancel any pending character input, and stop timers. */ + if (is_completed) + g_vm->glk_cancel_char_event (gsc_main_window); + g_vm->glk_request_timer_events (0); + } + } +} + + +/* + * gsc_reset_glk_style() + * + * Drop all stacked fonts and nested attributes, and return to normal Glk + * style. + */ +static void +gsc_reset_glk_style() +{ + /* Reset the font stack and attributes, and set a normal style. */ + gsc_font_index = 0; + gsc_attribute_bold = 0; + gsc_attribute_italic = 0; + gsc_attribute_underline = 0; + gsc_attribute_secondary_color = 0; + gsc_set_glk_style(); +} + + +/* + * os_print_tag() + * + * Interpret selected Adrift output control tags. Not all are implemented + * here; several are ignored. + */ +void +os_print_tag (sc_int tag, const sc_char *argument) +{ + event_t event; + assert (argument); + + switch (tag) + { + case SC_TAG_CLS: + /* Clear the main text display window. */ + g_vm->glk_window_clear (gsc_main_window); + break; + + case SC_TAG_FONT: + /* Handle with specific tag handler function. */ + gsc_handle_font_tag (argument); + break; + + case SC_TAG_ENDFONT: + /* Handle with specific endtag handler function. */ + gsc_handle_endfont_tag (); + break; + + case SC_TAG_BOLD: + case SC_TAG_ITALICS: + case SC_TAG_UNDERLINE: + case SC_TAG_COLOR: + /* Handle with common attribute tag handler function. */ + gsc_handle_attribute_tag (tag); + break; + + case SC_TAG_ENDBOLD: + case SC_TAG_ENDITALICS: + case SC_TAG_ENDUNDERLINE: + case SC_TAG_ENDCOLOR: + /* Handle with common attribute endtag handler function. */ + gsc_handle_endattribute_tag (tag); + break; + + case SC_TAG_CENTER: + case SC_TAG_RIGHT: + case SC_TAG_ENDCENTER: + case SC_TAG_ENDRIGHT: + /* + * We don't center or justify text, but so that things look right we do + * want a newline on starting or ending such a section. + */ + g_vm->glk_put_char ('\n'); + break; + + case SC_TAG_WAIT: + /* + * Update the status line now only if it has its own window, then + * handle with a specialized handler. + */ + if (gsc_status_window) + gsc_status_notify (); + gsc_handle_wait_tag (argument); + break; + + case SC_TAG_WAITKEY: + /* + * If reading an input log, ignore; it disrupts replay. Write a newline + * to separate off any unterminated game output instead. + */ + if (!gsc_readlog_stream) + { + /* Update the status line now only if it has its own window. */ + if (gsc_status_window) + gsc_status_notify (); + + /* Request a character event, and wait for it to be filled. */ + g_vm->glk_request_char_event (gsc_main_window); + gsc_event_wait (evtype_CharInput, &event); + } + else + g_vm->glk_put_char ('\n'); + break; + + default: + /* Ignore unimplemented and unknown tags. */ + break; + } +} + + +/* + * os_print_string() + * + * Print a text string to the main output window. + */ +void os_print_string(const sc_char *string) { + sc_bool is_monospaced; + assert (string); + assert (g_vm->glk_stream_get_current ()); + + /* + * Get the monospace font setting from the current top of stack, or + * default on empty stack. If set, we may need to use an alternative + * function to write this string. + */ + if (gsc_font_index > 0) + is_monospaced = gsc_font_stack[gsc_font_index - 1].is_monospaced; + else + is_monospaced = FALSE; + + /* + * The main window should always be the currently set window at this point, + * so we never be attempting monospaced output to the status window. + * Nevertheless, check anyway. + */ + if (is_monospaced + && g_vm->glk_stream_get_current () == g_vm->glk_window_get_stream (gsc_main_window)) + gsc_put_string_alternate (string); + else + gsc_put_string (string); +} + + +/* + * os_print_string_debug() + * + * Debugging output goes to the main Glk window -- no special effects or + * dedicated debugging window attempted. + */ +void +os_print_string_debug (const sc_char *string) +{ + assert (string); + assert (g_vm->glk_stream_get_current ()); + + gsc_put_string (string); +} + + +/* + * gsc_styled_string() + * gsc_styled_char() + * gsc_standout_string() + * gsc_standout_char() + * gsc_normal_string() + * gsc_normal_char() + * gsc_header_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 +gsc_styled_string (glui32 style, const char *message) +{ + assert (message); + + g_vm->glk_set_style (style); + g_vm->glk_put_string ((char *) message); + g_vm->glk_set_style (style_Normal); +} + +static void +gsc_styled_char (glui32 style, char c) +{ + char buffer[2]; + + buffer[0] = c; + buffer[1] = '\0'; + gsc_styled_string (style, buffer); +} + +static void +gsc_standout_string (const char *message) +{ + gsc_styled_string (style_Emphasized, message); +} + +static void +gsc_standout_char (char c) +{ + gsc_styled_char (style_Emphasized, c); +} + +static void +gsc_normal_string (const char *message) +{ + gsc_styled_string (style_Normal, message); +} + +static void +gsc_normal_char (char c) +{ + gsc_styled_char (style_Normal, c); +} + +static void +gsc_header_string (const char *message) +{ + gsc_styled_string (style_Header, message); +} + + +/* + * os_display_hints() + * + * This is a very basic hints display. In mitigation, very few games use + * hints at all, and those that do are usually sparse in what they hint at, so + * it's sort of good enough for the moment. + */ +void +os_display_hints (sc_game game) +{ + sc_game_hint hint; + sc_int refused; + + /* For each hint, print the question, and confirm hint display. */ + refused = 0; + for (hint = sc_get_first_game_hint (game); + hint; hint = sc_get_next_game_hint (game, hint)) + { + const sc_char *hint_question, *hint_text; + + /* If enough refusals, offer a way out of the loop. */ + if (refused >= GSC_HINT_REFUSAL_LIMIT) + { + if (!os_confirm (GSC_CONF_CONTINUE_HINTS)) + break; + refused = 0; + } + + /* Pop the question. */ + hint_question = sc_get_game_hint_question (game, hint); + gsc_normal_char ('\n'); + gsc_standout_string (hint_question); + gsc_normal_char ('\n'); + + /* Print the subtle hint, or on to the next hint. */ + hint_text = sc_get_game_subtle_hint (game, hint); + if (hint_text) + { + if (!os_confirm (GSC_CONF_SUBTLE_HINT)) + { + refused++; + continue; + } + gsc_normal_char ('\n'); + gsc_standout_string (hint_text); + gsc_normal_string ("\n\n"); + } + + /* Print the less than subtle hint, or on to the next hint. */ + hint_text = sc_get_game_unsubtle_hint (game, hint); + if (hint_text) + { + if (!os_confirm (GSC_CONF_UNSUBTLE_HINT)) + { + refused++; + continue; + } + gsc_normal_char ('\n'); + gsc_standout_string (hint_text); + gsc_normal_string ("\n\n"); + } + } +} + + +/*---------------------------------------------------------------------*/ +/* Glk resource handling functions */ +/*---------------------------------------------------------------------*/ + +/* + * os_play_sound() + * os_stop_sound() + * + * Stub functions. The unused variables defeat gcc warnings. + */ +void +os_play_sound (const sc_char *filepath, + sc_int offset, sc_int length, sc_bool is_looping) +{ + const sc_char *unused1; + sc_int unused2, unused3; + sc_bool unused4; + unused1 = filepath; + unused2 = offset; + unused3 = length; + unused4 = is_looping; +} + +void +os_stop_sound() +{ +} + + +/* + * os_show_graphic() + * + * For graphic-capable Glk libraries on Linux, attempt graphics using xv. The + * graphic capability test isn't really required, it's just a way of having + * graphics behave without surprises; someone using a non-graphical Glk + * probably won't expect graphics to pop up. + * + * For other cases, this is a stub function, with unused variables to defeat + * gcc warnings. + */ +#ifdef LINUX_GRAPHICS +static int gsclinux_graphics_enabled = TRUE; +static char *gsclinux_game_file = nullptr; +void +os_show_graphic (const sc_char *filepath, sc_int offset, sc_int length) +{ + const sc_char *unused1; + unused1 = filepath; + + if (length > 0 + && gsclinux_graphics_enabled && g_vm->glk_gestalt (gestalt_Graphics, 0)) + { + sc_char *buffer; + + /* + * Try to extract data with dd. Assuming that works, background xv to + * display the image, then background a job to delay ten seconds and + * then delete the temporary file containing the image. Systems lacking + * xv can usually use a small script, named xv, to invoke eog or an + * alternative image display binary. Not exactly finessed. + */ + assert (gsclinux_game_file); + buffer = gsc_malloc (strlen (gsclinux_game_file) + 128); + sprintf (buffer, "dd if=%s ibs=1c skip=%ld count=%ld obs=100k" + " of=/tmp/scare.jpg 2>/dev/null", + gsclinux_game_file, offset, length); + system (buffer); + free (buffer); + system ("xv /tmp/scare.jpg >/dev/null 2>&1 &"); + system ("( sleep 10; rm /tmp/scare.jpg ) >/dev/null 2>&1 &"); + } +} +#else +void +os_show_graphic (const sc_char *filepath, sc_int offset, sc_int length) +{ + const sc_char *unused1; + sc_int unused2, unused3; + unused1 = filepath; + unused2 = offset; + unused3 = length; +} +#endif + + +/*---------------------------------------------------------------------*/ +/* Glk command escape functions */ +/*---------------------------------------------------------------------*/ + +/* + * gsc_command_script() + * + * Turn game output scripting (logging) on and off. + */ +static void +gsc_command_script (const char *argument) +{ + assert (argument); + + if (sc_strcasecmp (argument, "on") == 0) + { + frefid_t fileref; + + if (gsc_transcript_stream) + { + gsc_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) + { + gsc_standout_string ("Glk transcript failed.\n"); + return; + } + + gsc_transcript_stream = g_vm->glk_stream_open_file (fileref, + filemode_WriteAppend, 0); + g_vm->glk_fileref_destroy (fileref); + if (!gsc_transcript_stream) + { + gsc_standout_string ("Glk transcript failed.\n"); + return; + } + + g_vm->glk_window_set_echo_stream (gsc_main_window, gsc_transcript_stream); + + gsc_normal_string ("Glk transcript is now on.\n"); + } + + else if (sc_strcasecmp (argument, "off") == 0) + { + if (!gsc_transcript_stream) + { + gsc_normal_string ("Glk transcript is already off.\n"); + return; + } + + g_vm->glk_stream_close (gsc_transcript_stream, nullptr); + gsc_transcript_stream = nullptr; + + g_vm->glk_window_set_echo_stream (gsc_main_window, nullptr); + + gsc_normal_string ("Glk transcript is now off.\n"); + } + + else if (strlen (argument) == 0) + { + gsc_normal_string ("Glk transcript is "); + gsc_normal_string (gsc_transcript_stream ? "on" : "off"); + gsc_normal_string (".\n"); + } + + else + { + gsc_normal_string ("Glk transcript can be "); + gsc_standout_string ("on"); + gsc_normal_string (", or "); + gsc_standout_string ("off"); + gsc_normal_string (".\n"); + } +} + + +/* + * gsc_command_inputlog() + * + * Turn game input logging on and off. + */ +static void +gsc_command_inputlog (const char *argument) +{ + assert (argument); + + if (sc_strcasecmp (argument, "on") == 0) + { + frefid_t fileref; + + if (gsc_inputlog_stream) + { + gsc_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) + { + gsc_standout_string ("Glk input logging failed.\n"); + return; + } + + gsc_inputlog_stream = g_vm->glk_stream_open_file (fileref, + filemode_WriteAppend, 0); + g_vm->glk_fileref_destroy (fileref); + if (!gsc_inputlog_stream) + { + gsc_standout_string ("Glk input logging failed.\n"); + return; + } + + gsc_normal_string ("Glk input logging is now on.\n"); + } + + else if (sc_strcasecmp (argument, "off") == 0) + { + if (!gsc_inputlog_stream) + { + gsc_normal_string ("Glk input logging is already off.\n"); + return; + } + + g_vm->glk_stream_close (gsc_inputlog_stream, nullptr); + gsc_inputlog_stream = nullptr; + + gsc_normal_string ("Glk input log is now off.\n"); + } + + else if (strlen (argument) == 0) + { + gsc_normal_string ("Glk input logging is "); + gsc_normal_string (gsc_inputlog_stream ? "on" : "off"); + gsc_normal_string (".\n"); + } + + else + { + gsc_normal_string ("Glk input logging can be "); + gsc_standout_string ("on"); + gsc_normal_string (", or "); + gsc_standout_string ("off"); + gsc_normal_string (".\n"); + } +} + + +/* + * gsc_command_readlog() + * + * Set the game input log, to read input from a file. + */ +static void +gsc_command_readlog (const char *argument) +{ + assert (argument); + + if (sc_strcasecmp (argument, "on") == 0) + { + frefid_t fileref; + + if (gsc_readlog_stream) + { + gsc_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) + { + gsc_standout_string ("Glk read log failed.\n"); + return; + } + + if (!g_vm->glk_fileref_does_file_exist (fileref)) + { + g_vm->glk_fileref_destroy (fileref); + gsc_standout_string ("Glk read log failed.\n"); + return; + } + + gsc_readlog_stream = g_vm->glk_stream_open_file (fileref, filemode_Read, 0); + g_vm->glk_fileref_destroy (fileref); + if (!gsc_readlog_stream) + { + gsc_standout_string ("Glk read log failed.\n"); + return; + } + + gsc_normal_string ("Glk read log is now on.\n"); + } + + else if (sc_strcasecmp (argument, "off") == 0) + { + if (!gsc_readlog_stream) + { + gsc_normal_string ("Glk read log is already off.\n"); + return; + } + + g_vm->glk_stream_close (gsc_readlog_stream, nullptr); + gsc_readlog_stream = nullptr; + + gsc_normal_string ("Glk read log is now off.\n"); + } + + else if (strlen (argument) == 0) + { + gsc_normal_string ("Glk read log is "); + gsc_normal_string (gsc_readlog_stream ? "on" : "off"); + gsc_normal_string (".\n"); + } + + else + { + gsc_normal_string ("Glk read log can be "); + gsc_standout_string ("on"); + gsc_normal_string (", or "); + gsc_standout_string ("off"); + gsc_normal_string (".\n"); + } +} + + +/* + * gsc_command_abbreviations() + * + * Turn abbreviation expansions on and off. + */ +static void +gsc_command_abbreviations (const char *argument) +{ + assert (argument); + + if (sc_strcasecmp (argument, "on") == 0) + { + if (gsc_abbreviations_enabled) + { + gsc_normal_string ("Glk abbreviation expansions are already on.\n"); + return; + } + + gsc_abbreviations_enabled = TRUE; + gsc_normal_string ("Glk abbreviation expansions are now on.\n"); + } + + else if (sc_strcasecmp (argument, "off") == 0) + { + if (!gsc_abbreviations_enabled) + { + gsc_normal_string ("Glk abbreviation expansions are already off.\n"); + return; + } + + gsc_abbreviations_enabled = FALSE; + gsc_normal_string ("Glk abbreviation expansions are now off.\n"); + } + + else if (strlen (argument) == 0) + { + gsc_normal_string ("Glk abbreviation expansions are "); + gsc_normal_string (gsc_abbreviations_enabled ? "on" : "off"); + gsc_normal_string (".\n"); + } + + else + { + gsc_normal_string ("Glk abbreviation expansions can be "); + gsc_standout_string ("on"); + gsc_normal_string (", or "); + gsc_standout_string ("off"); + gsc_normal_string (".\n"); + } +} + + +/* + * gsc_command_print_version_number() + * gsc_command_version() + * + * Print out the Glk library version number. + */ +static void +gsc_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); + gsc_normal_string (buffer); +} + +static void +gsc_command_version (const char *argument) +{ + glui32 version; + assert (argument); + + gsc_normal_string ("This is version "); + gsc_command_print_version_number (GSC_PORT_VERSION); + gsc_normal_string (" of the Glk SCARE port.\n"); + + version = g_vm->glk_gestalt (gestalt_Version, 0); + gsc_normal_string ("The Glk library version is "); + gsc_command_print_version_number (version); + gsc_normal_string (".\n"); +} + + +/* + * gsc_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 +gsc_command_commands (const char *argument) +{ + assert (argument); + + if (sc_strcasecmp (argument, "on") == 0) + { + gsc_normal_string ("Glk commands are already on.\n"); + } + + else if (sc_strcasecmp (argument, "off") == 0) + { + gsc_commands_enabled = FALSE; + gsc_normal_string ("Glk commands are now off.\n"); + } + + else if (strlen (argument) == 0) + { + gsc_normal_string ("Glk commands are "); + gsc_normal_string (gsc_commands_enabled ? "on" : "off"); + gsc_normal_string (".\n"); + } + + else + { + gsc_normal_string ("Glk commands can be "); + gsc_standout_string ("on"); + gsc_normal_string (", or "); + gsc_standout_string ("off"); + gsc_normal_string (".\n"); + } +} + + +/* + * gsc_command_license() + * + * Print licensing terms. + */ +static void +gsc_command_license (const char *argument) +{ + assert (argument); + + gsc_normal_string ("This program is free software; you can redistribute it" + " and/or modify it under the terms of version 2 of the" + " GNU General Public License as published by the Free" + " Software Foundation.\n\n"); + + gsc_normal_string ("This program is distributed in the hope that it will be" + " useful, but "); + gsc_standout_string ("WITHOUT ANY WARRANTY"); + gsc_normal_string ("; without even the implied warranty of "); + gsc_standout_string ("MERCHANTABILITY"); + gsc_normal_string (" or "); + gsc_standout_string ("FITNESS FOR A PARTICULAR PURPOSE"); + gsc_normal_string (". See the GNU General Public License for more" + " details.\n\n"); + + gsc_normal_string ("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\n\n"); + + gsc_normal_string ("Please report any bugs, omissions, or misfeatures to "); + gsc_standout_string ("simon_baldwin@yahoo.com"); + gsc_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. */ +} gsc_command_t; +typedef gsc_command_t *gsc_commandref_t; + +static void gsc_command_summary (const char *argument); +static void gsc_command_help (const char *argument); + +static gsc_command_t GSC_COMMAND_TABLE[] = { + {"summary", gsc_command_summary, FALSE}, + {"script", gsc_command_script, TRUE}, + {"inputlog", gsc_command_inputlog, TRUE}, + {"readlog", gsc_command_readlog, TRUE}, + {"abbreviations", gsc_command_abbreviations, TRUE}, + {"version", gsc_command_version, FALSE}, + {"commands", gsc_command_commands, TRUE}, + {"license", gsc_command_license, FALSE}, + {"help", gsc_command_help, TRUE}, + {nullptr, nullptr, FALSE} +}; + + +/* + * gsc_command_summary() + * + * Report all current Glk settings. + */ +static void +gsc_command_summary (const char *argument) +{ + gsc_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 = GSC_COMMAND_TABLE; entry->command; entry++) + { + if (entry->handler == gsc_command_summary + || entry->handler == gsc_command_license + || entry->handler == gsc_command_help) + continue; + + entry->handler (""); + } +} + + +/* + * gsc_command_help() + * + * Document the available Glk commands. + */ +static void +gsc_command_help (const char *command) +{ + gsc_commandref_t entry, matched; + assert (command); + + if (strlen (command) == 0) + { + gsc_normal_string ("Glk commands are"); + for (entry = GSC_COMMAND_TABLE; entry->command; entry++) + { + gsc_commandref_t next; + + next = entry + 1; + gsc_normal_string (next->command ? " " : " and "); + gsc_standout_string (entry->command); + gsc_normal_string (next->command ? "," : ".\n\n"); + } + + gsc_normal_string ("Glk commands may be abbreviated, as long as" + " the abbreviation is unambiguous. Use "); + gsc_standout_string ("glk help"); + gsc_normal_string (" followed by a Glk command name for help on that" + " command.\n"); + return; + } + + matched = nullptr; + for (entry = GSC_COMMAND_TABLE; entry->command; entry++) + { + if (sc_strncasecmp (command, entry->command, strlen (command)) == 0) + { + if (matched) + { + gsc_normal_string ("The Glk command "); + gsc_standout_string (command); + gsc_normal_string (" is ambiguous. Try "); + gsc_standout_string ("glk help"); + gsc_normal_string (" for more information.\n"); + return; + } + matched = entry; + } + } + if (!matched) + { + gsc_normal_string ("The Glk command "); + gsc_standout_string (command); + gsc_normal_string (" is not valid. Try "); + gsc_standout_string ("glk help"); + gsc_normal_string (" for more information.\n"); + return; + } + + if (matched->handler == gsc_command_summary) + { + gsc_normal_string ("Prints a summary of all the current Glk SCARE" + " settings.\n"); + } + + else if (matched->handler == gsc_command_script) + { + gsc_normal_string ("Logs the game's output to a file.\n\nUse "); + gsc_standout_string ("glk script on"); + gsc_normal_string (" to begin logging game output, and "); + gsc_standout_string ("glk script off"); + gsc_normal_string (" to end it. Glk SCARE will ask you for a file" + " when you turn scripts on.\n"); + } + + else if (matched->handler == gsc_command_inputlog) + { + gsc_normal_string ("Records the commands you type into a game.\n\nUse "); + gsc_standout_string ("glk inputlog on"); + gsc_normal_string (", to begin recording your commands, and "); + gsc_standout_string ("glk inputlog off"); + gsc_normal_string (" to turn off input logs. You can play back" + " recorded commands into a game with the "); + gsc_standout_string ("glk readlog"); + gsc_normal_string (" command.\n"); + } + + else if (matched->handler == gsc_command_readlog) + { + gsc_normal_string ("Plays back commands recorded with "); + gsc_standout_string ("glk inputlog on"); + gsc_normal_string (".\n\nUse "); + gsc_standout_string ("glk readlog on"); + gsc_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 == gsc_command_abbreviations) + { + gsc_normal_string ("Controls abbreviation expansion.\n\nGlk SCARE" + " automatically expands several standard single" + " letter abbreviations for you; for example, \"x\"" + " becomes \"examine\". Use "); + gsc_standout_string ("glk abbreviations on"); + gsc_normal_string (" to turn this feature on, and "); + gsc_standout_string ("glk abbreviations off"); + gsc_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 == gsc_command_version) + { + gsc_normal_string ("Prints the version numbers of the Glk library" + " and the Glk SCARE port.\n"); + } + + else if (matched->handler == gsc_command_commands) + { + gsc_normal_string ("Turn off Glk commands.\n\nUse "); + gsc_standout_string ("glk commands off"); + gsc_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 == gsc_command_license) + { + gsc_normal_string ("Prints Glk SCARE's software license.\n"); + } + + else if (matched->handler == gsc_command_help) + gsc_command_help (""); + + else + gsc_normal_string ("There is no help available on that Glk command." + " Sorry.\n"); +} + + +/* + * gsc_command_escape() + * + * This function is handed each input line. If the line contains a specific + * Glk port command, handle it and return TRUE, otherwise return FALSE. + */ +static int +gsc_command_escape (const char *string) +{ + int posn; + char *string_copy, *command, *argument; + assert (string); + + /* + * Return FALSE if the string doesn't begin with the Glk command escape + * introducer. + */ + posn = strspn (string, "\t "); + if (sc_strncasecmp (string + posn, "glk", strlen ("glk")) != 0) + return FALSE; + + /* Take a copy of the string, without any leading space or introducer. */ + string_copy = (char *)gsc_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) + { + gsc_commandref_t entry, matched; + int matches; + + /* + * Search for the first unambiguous table command string matching + * the command passed in. + */ + matches = 0; + matched = nullptr; + for (entry = GSC_COMMAND_TABLE; entry->command; entry++) + { + if (sc_strncasecmp (command, entry->command, strlen (command)) == 0) + { + matches++; + matched = entry; + } + } + + /* If the match was unambiguous, call the command handler. */ + if (matches == 1) + { + gsc_normal_char ('\n'); + matched->handler (argument); + + if (!matched->takes_argument && strlen (argument) > 0) + { + gsc_normal_string ("[The "); + gsc_standout_string (matched->command); + gsc_normal_string (" command ignores arguments.]\n"); + } + } + + /* No match, or the command was ambiguous. */ + else + { + gsc_normal_string ("\nThe Glk command "); + gsc_standout_string (command); + gsc_normal_string (" is "); + gsc_normal_string (matches == 0 ? "not valid" : "ambiguous"); + gsc_normal_string (". Try "); + gsc_standout_string ("glk help"); + gsc_normal_string (" for more information.\n"); + } + } + else + { + gsc_normal_char ('\n'); + gsc_command_help (""); + } + + /* The string contained a Glk command; return TRUE. */ + free (string_copy); + return TRUE; +} + + +/*---------------------------------------------------------------------*/ +/* Glk port input functions */ +/*---------------------------------------------------------------------*/ + +/* Quote used to suppress abbreviation expansion and local commands. */ +static const char GSC_QUOTED_INPUT = '\''; + + +/* Table of single-character command abbreviations. */ +typedef const struct +{ + const char abbreviation; /* Abbreviation character. */ + const char *const expansion; /* Expansion string. */ +} gsc_abbreviation_t; +typedef gsc_abbreviation_t *gsc_abbreviationref_t; + +static gsc_abbreviation_t GSC_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', nullptr} +}; + + +/* + * gsc_expand_abbreviations() + * + * Expand a few common one-character abbreviations commonly found in other + * game systems. + */ +static void +gsc_expand_abbreviations (char *buffer, int size) +{ + char *command, abbreviation; + const char *expansion; + gsc_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 = nullptr; + for (entry = GSC_ABBREVIATIONS; entry->expansion; entry++) + { + if (entry->abbreviation == abbreviation) + { + expansion = entry->expansion; + break; + } + } + + /* + * If a match found, check for a fit, then replace the character with the + * expansion string. + */ + if (expansion) + { + if (strlen (buffer) + strlen (expansion) - 1 >= (unsigned int) size) + return; + + memmove (command + strlen (expansion) - 1, command, strlen (command) + 1); + memcpy (command, expansion, strlen (expansion)); + + gsc_standout_string ("["); + gsc_standout_char (abbreviation); + gsc_standout_string (" -> "); + gsc_standout_string (expansion); + gsc_standout_string ("]\n"); + } +} + + +/* + * os_read_line() + * + * Read and return a line of player input. + */ +sc_bool +os_read_line (sc_char *buffer, sc_int length) +{ + sc_int characters; + assert (buffer && length > 0); + + /* If a help request is pending, provide a user hint. */ + gsc_output_provide_help_hint (); + + /* + * Ensure normal style, update the status line, and issue an input prompt. + */ + gsc_reset_glk_style (); + gsc_status_notify (); + g_vm->glk_put_string (">"); + + /* + * 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 (gsc_readlog_stream) + { + glui32 chars; + + /* Get the next line from the log stream. */ + chars = g_vm->glk_get_line_stream (gsc_readlog_stream, buffer, length); + if (chars > 0) + { + /* Echo the line just read in input style. */ + g_vm->glk_set_style (style_Input); + gsc_put_buffer (buffer, chars); + g_vm->glk_set_style (style_Normal); + + /* Return this line as player input. */ + return TRUE; + } + + /* + * 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 (gsc_readlog_stream, nullptr); + gsc_readlog_stream = nullptr; + } + + /* + * 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. + */ + characters = gsc_read_line (buffer, length - 1); + assert (characters <= length); + buffer[characters] = '\0'; + + /* + * If neither abbreviations nor local commands are enabled, use the data + * read above without further massaging. + */ + if (gsc_abbreviations_enabled || gsc_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 = buffer + strspn (buffer, "\t "); + if (command[0] == GSC_QUOTED_INPUT) + { + /* Delete the quote with memmove(). */ + memmove (command, command + 1, strlen (command)); + } + else + { + /* Check for, and expand, and abbreviated commands. */ + if (gsc_abbreviations_enabled) + gsc_expand_abbreviations (buffer, length); + + /* + * Check for standalone "help", then for Glk port special commands; + * suppress the interpreter's use of this input for Glk commands by + * returning FALSE. + */ + if (gsc_commands_enabled) + { + int posn; + + posn = strspn (buffer, "\t "); + if (sc_strncasecmp (buffer + posn, "help", strlen ("help"))== 0) + { + if (strspn (buffer + posn + strlen ("help"), "\t ") + == strlen (buffer + posn + strlen ("help"))) + { + gsc_output_register_help_request (); + } + } + + if (gsc_command_escape (buffer)) + { + gsc_output_silence_help_hints (); + return FALSE; + } + } + } + } + + /* + * 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 (gsc_inputlog_stream) + { + g_vm->glk_put_string_stream (gsc_inputlog_stream, buffer); + g_vm->glk_put_char_stream (gsc_inputlog_stream, '\n'); + } + + return TRUE; +} + + +/* + * os_read_line_debug() + * + * Read and return a debugger command line. There's no dedicated debugging + * window, so this is just a call to the normal readline, with an additional + * prompt. + */ +sc_bool +os_read_line_debug (sc_char *buffer, sc_int length) +{ + gsc_output_silence_help_hints (); + gsc_reset_glk_style (); + g_vm->glk_put_string ("[SCARE debug]"); + return os_read_line (buffer, length); +} + + +/* + * os_confirm() + * + * Confirm a game action with a yes/no prompt. + */ +sc_bool +os_confirm (sc_int type) +{ + sc_char response; + + /* + * Always allow game saves and hint display, and if we're reading from an + * input log, allow everything no matter what, on the assumption that the + * user knows what they are doing. + */ + if (gsc_readlog_stream + || type == SC_CONF_SAVE || type == SC_CONF_VIEW_HINTS) + return TRUE; + + /* Ensure back to normal style, and update status. */ + gsc_reset_glk_style (); + gsc_status_notify (); + + /* Prompt for the confirmation, based on the type. */ + if (type == GSC_CONF_SUBTLE_HINT) + g_vm->glk_put_string ("View the subtle hint for this topic"); + else if (type == GSC_CONF_UNSUBTLE_HINT) + g_vm->glk_put_string ("View the unsubtle hint for this topic"); + else if (type == GSC_CONF_CONTINUE_HINTS) + g_vm->glk_put_string ("Continue with hints"); + else + { + g_vm->glk_put_string ("Do you really want to "); + switch (type) + { + case SC_CONF_QUIT: + g_vm->glk_put_string ("quit"); + break; + case SC_CONF_RESTART: + g_vm->glk_put_string ("restart"); + break; + case SC_CONF_SAVE: + g_vm->glk_put_string ("save"); + break; + case SC_CONF_RESTORE: + g_vm->glk_put_string ("restore"); + break; + case SC_CONF_VIEW_HINTS: + g_vm->glk_put_string ("view hints"); + break; + default: + g_vm->glk_put_string ("do that"); + break; + } + } + g_vm->glk_put_string ("? "); + + /* Loop until 'yes' or 'no' returned. */ + do + { + event_t event; + + /* Wait for a standard key, ignoring Glk special keys. */ + do + { + g_vm->glk_request_char_event (gsc_main_window); + gsc_event_wait (evtype_CharInput, &event); + } + while (event.val1 > UCHAR_MAX); + response = g_vm->glk_char_to_upper (event.val1); + } + while (response != 'Y' && response != 'N'); + + /* Echo the confirmation response, and a new 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_char ('\n'); + + /* Use a short delay on restarts, if confirmed. */ + if (type == SC_CONF_RESTART && response == 'Y') + gsc_short_delay (); + + /* Return TRUE if 'Y' was entered. */ + return (response == 'Y'); +} + + +/*---------------------------------------------------------------------*/ +/* Glk port event functions */ +/*---------------------------------------------------------------------*/ + +/* Short delay before restarts; 1s, in 100ms segments. */ +static const glui32 GSC_DELAY_TIMEOUT = 100; +static const glui32 GSC_DELAY_TIMEOUTS_COUNT = 10; + +/* + * gsc_short_delay() + * + * Delay for a short period; used before restarting a completed game, to + * improve the display where 'r', or confirming restart, triggers an otherwise + * immediate, and abrupt, restart. + */ +static void +gsc_short_delay() +{ + /* Ignore the call if the Glk doesn't have timers. */ + if (g_vm->glk_gestalt (gestalt_Timer, 0)) + { + glui32 timeout; + + /* Timeout in small chunks to minimize Glk jitter. */ + g_vm->glk_request_timer_events (GSC_DELAY_TIMEOUT); + for (timeout = 0; timeout < GSC_DELAY_TIMEOUTS_COUNT; timeout++) + { + event_t event; + + gsc_event_wait (evtype_Timer, &event); + } + g_vm->glk_request_timer_events (0); + } +} + + +/* + * gsc_event_wait_2() + * gsc_event_wait() + * + * Process Glk events until one of the expected type, or types, arrives. + * Return the event of that type. + */ +static void +gsc_event_wait_2 (glui32 wait_type_1, glui32 wait_type_2, 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. */ + gsc_status_redraw (); + break; + } + } + while (!(event->type == (EvType)wait_type_1 || event->type == (EvType)wait_type_2)); +} + +static void +gsc_event_wait (glui32 wait_type, event_t * event) +{ + assert (event); + + gsc_event_wait_2 (wait_type, evtype_None, event); +} + + +/*---------------------------------------------------------------------*/ +/* Glk port file functions */ +/*---------------------------------------------------------------------*/ + +/* + * os_open_file() + * + * Open a file for save or restore, and return a Glk stream for the opened + * file. + */ +void *os_open_file (sc_bool is_save) { + glui32 usage, fmode; + frefid_t fileref; + strid_t stream; + + usage = fileusage_SavedGame | fileusage_BinaryMode; + fmode = is_save ? filemode_Write : filemode_Read; + + fileref = g_vm->glk_fileref_create_by_prompt(usage, (FileMode)fmode, 0); + if (!fileref) + return nullptr; + + if (!is_save && !g_vm->glk_fileref_does_file_exist (fileref)) + { + g_vm->glk_fileref_destroy (fileref); + return nullptr; + } + + stream = g_vm->glk_stream_open_file (fileref, (FileMode)fmode, 0); + g_vm->glk_fileref_destroy (fileref); + + return stream; +} + + +/* + * os_write_file() + * os_read_file() + * + * Write/read the given buffered data to/from the open Glk stream. + */ +void +os_write_file (void *opaque, const sc_byte *buffer, sc_int length) +{ + strid_t stream = (strid_t) opaque; + assert (opaque && buffer); + + g_vm->glk_put_buffer_stream (stream, (char *) buffer, length); +} + +sc_int +os_read_file (void *opaque, sc_byte *buffer, sc_int length) +{ + strid_t stream = (strid_t) opaque; + assert (opaque && buffer); + + return g_vm->glk_get_buffer_stream (stream, (char *) buffer, length); +} + + +/* + * os_close_file() + * + * Close the opened Glk stream. + */ +void +os_close_file (void *opaque) +{ + strid_t stream = (strid_t) opaque; + assert (opaque); + + g_vm->glk_stream_close (stream, nullptr); +} + + +/*---------------------------------------------------------------------*/ +/* main() and options parsing */ +/*---------------------------------------------------------------------*/ + +/* Loading message flush delay timeout. */ +static const glui32 GSC_LOADING_TIMEOUT = 100; + +/* Enumerated game end options. */ +enum gsc_end_option { GAME_RESTART, GAME_UNDO, GAME_QUIT }; + +/* + * The following value needs to be passed between the startup_code and main + * functions. + */ +static char *gsc_game_message = nullptr; + + +/* + * gsc_callback() + * + * Callback function for reading in game and restore file data; fills a + * buffer with TAF or TAS file data from a Glk stream, and returns the byte + * count. + */ +static sc_int +gsc_callback (void *opaque, sc_byte *buffer, sc_int length) +{ + strid_t stream = (strid_t) opaque; + assert (stream); + + return g_vm->glk_get_buffer_stream (stream, (char *) buffer, length); +} + + +/* + * gsc_get_ending_option() + * + * Offer the option to restart, undo, or quit. Returns the selected game + * end option. Called on game completion. + */ +static enum gsc_end_option +gsc_get_ending_option() +{ + sc_char response; + + /* Ensure back to normal style, and update status. */ + gsc_reset_glk_style (); + gsc_status_notify (); + + /* Prompt for restart, undo, or quit. */ + g_vm->glk_put_string ("\nWould you like to RESTART, UNDO a turn, or QUIT? "); + + /* Loop until 'restart', 'undo' or 'quit'. */ + do + { + event_t event; + + do + { + g_vm->glk_request_char_event (gsc_main_window); + gsc_event_wait (evtype_CharInput, &event); + } + while (event.val1 > UCHAR_MAX); + response = g_vm->glk_char_to_upper (event.val1); + } + while (response != 'R' && response != 'U' && response != 'Q'); + + /* Echo the confirmation response, and a new line. */ + g_vm->glk_set_style (style_Input); + switch (response) + { + case 'R': + g_vm->glk_put_string ("Restart"); + break; + case 'U': + g_vm->glk_put_string ("Undo"); + break; + case 'Q': + g_vm->glk_put_string ("Quit"); + break; + default: + gsc_fatal ("GLK: Invalid response encountered"); + g_vm->glk_exit (); + } + g_vm->glk_set_style (style_Normal); + g_vm->glk_put_char ('\n'); + + /* Return the appropriate value for response. */ + switch (response) + { + case 'R': + return GAME_RESTART; + case 'U': + return GAME_UNDO; + case 'Q': + return GAME_QUIT; + default: + gsc_fatal ("GLK: Invalid response encountered"); + g_vm->glk_exit (); + } + + /* Unreachable; supplied to suppress compiler warning. */ + return GAME_QUIT; +} + + +/* + * gsc_startup_code() + * gsc_main + * + * Together, these functions take the place of the original main(). The + * first one is called from the platform-specific 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. + */ +static int +gsc_startup_code (strid_t game_stream, strid_t restore_stream, + sc_uint trace_flags, sc_bool enable_debugger, + sc_bool stable_random, const sc_char *locale) +{ + winid_t window; + assert (game_stream); + + /* Open a temporary Glk main window. */ + window = g_vm->glk_window_open (0, 0, 0, wintype_TextBuffer, 0); + if (window) + { + /* Clear and initialize the temporary window. */ + g_vm->glk_window_clear (window); + g_vm->glk_set_window (window); + g_vm->glk_set_style (style_Normal); + + /* + * Display a brief loading game message; here we have to use a timeout + * to ensure that the text is flushed to Glk. + */ + g_vm->glk_put_string ("Loading game...\n"); + if (g_vm->glk_gestalt (gestalt_Timer, 0)) + { + event_t event; + + g_vm->glk_request_timer_events (GSC_LOADING_TIMEOUT); + do + { + g_vm->glk_select (&event); + } + while (event.type != evtype_Timer); + g_vm->glk_request_timer_events (0); + } + } + + /* If the Glk libarary does not support unicode, disable it. */ + if (!gsc_has_unicode || !g_vm->glk_gestalt (gestalt_Unicode, 0)) + gsc_unicode_enabled = FALSE; + + /* + * If a locale was requested, set it in the core interpreter now. This + * locale will preempt any auto-detected one found from inspecting the + * game on creation. After game creation, the Glk locale is synchronized + * to the core interpreter's locale. + */ + if (locale) + sc_set_locale (locale); + + /* + * Set tracing flags, then try to create a SCARE game reference from the + * TAF file. Since we need this in our call from g_vm->glk_main, we have to keep + * it in a module static variable. If we can't open the TAF file, then + * we'll set the pointer to nullptr, and complain about it later in main. + * Passing the message string around like this is a nuisance... + */ + sc_set_trace_flags (trace_flags); + gsc_game = sc_game_from_callback (gsc_callback, game_stream); + if (!gsc_game) + { + gsc_game = nullptr; + gsc_game_message = "Unable to load an Adrift game from the" + " requested file."; + } + else + gsc_game_message = nullptr; + g_vm->glk_stream_close (game_stream, nullptr); + + /* + * If the game was created successfully and there is a restore stream, try + * to immediately restore the game from that stream. + */ + if (gsc_game && restore_stream) + { + if (!sc_load_game_from_callback (gsc_game, gsc_callback, restore_stream)) + { + sc_free_game (gsc_game); + gsc_game = nullptr; + gsc_game_message = "Unable to restore this Adrift game from the" + " requested file."; + } + else + gsc_game_message = nullptr; + } + if (restore_stream) + g_vm->glk_stream_close (restore_stream, nullptr); + + /* If successful, set game debugging and synchronize to the core's locale. */ + if (gsc_game) + { + sc_set_game_debugger_enabled (gsc_game, enable_debugger); + gsc_set_locale (sc_get_locale ()); + } + + /* Set portable and predictable random number generation if requested. */ + if (stable_random) + { + sc_set_portable_random (TRUE); + sc_reseed_random_sequence (1); + } + + /* Close the temporary window. */ + if (window) + g_vm->glk_window_close (window, nullptr); + + /* Set title of game */ +#ifdef GARGLK + g_vm->garglk_set_story_name(sc_get_game_name(gsc_game)); +#endif + + /* Game set up, perhaps successfully. */ + return TRUE; +} + +static void +gsc_main() +{ + sc_bool is_running; + + /* Ensure SCARE internal types have the right sizes. */ + if (!(sizeof (sc_byte) == 1 && sizeof (sc_char) == 1 + && sizeof (sc_uint) >= 4 && sizeof (sc_int) >= 4 + && sizeof (sc_uint) <= 8 && sizeof (sc_int) <= 8)) + { + gsc_fatal ("GLK: Types sized incorrectly, recompilation is needed"); + g_vm->glk_exit (); + } + + /* Create the Glk window, and set its stream as the current one. */ + gsc_main_window = g_vm->glk_window_open (0, 0, 0, wintype_TextBuffer, 0); + if (!gsc_main_window) + { + gsc_fatal ("GLK: Can't open main window"); + g_vm->glk_exit (); + } + g_vm->glk_window_clear (gsc_main_window); + g_vm->glk_set_window (gsc_main_window); + g_vm->glk_set_style (style_Normal); + + /* If there's a problem with the game file, complain now. */ + if (!gsc_game) + { + assert (gsc_game_message); + gsc_header_string ("Glk SCARE Error\n\n"); + gsc_normal_string (gsc_game_message); + gsc_normal_char ('\n'); + g_vm->glk_exit (); + } + + /* 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); + gsc_status_window = g_vm->glk_window_open (gsc_main_window, + winmethod_Above | winmethod_Fixed, + 1, wintype_TextGrid, 0); + + /* Repeat the game until no more restarts requested. */ + is_running = TRUE; + while (is_running) + { + /* Run the game until it ends, or the user quits. */ + gsc_status_notify (); + sc_interpret_game (gsc_game); + + /* + * If the game did not complete, the user quit explicitly, so leave the + * game repeat loop. + */ + if (!sc_has_game_completed (gsc_game)) + { + is_running = FALSE; + break; + } + + /* + * If reading from an input log, close it now. We need to request a + * user selection, probably modal, and after that we probably don't + * want the follow-on readlog data being used as game input. + */ + if (gsc_readlog_stream) + { + g_vm->glk_stream_close (gsc_readlog_stream, nullptr); + gsc_readlog_stream = nullptr; + } + + /* + * Get user selection of restart, undo a turn, or quit completed game. + * If undo is unavailable (this should not be possible), degrade to + * restart. + */ + switch (gsc_get_ending_option ()) + { + case GAME_RESTART: + gsc_short_delay (); + sc_restart_game (gsc_game); + break; + + case GAME_UNDO: + if (sc_is_game_undo_available (gsc_game)) + { + sc_undo_game_turn (gsc_game); + gsc_normal_string ("The previous turn has been undone.\n"); + } + else + { + gsc_normal_string ("Sorry, no undo is available.\n"); + gsc_short_delay (); + sc_restart_game (gsc_game); + } + break; + + case GAME_QUIT: + is_running = FALSE; + break; + } + } + + /* All done -- release game resources. */ + sc_free_game (gsc_game); + + /* Close any open transcript, input log, and/or read log. */ + if (gsc_transcript_stream) + { + g_vm->glk_stream_close (gsc_transcript_stream, nullptr); + gsc_transcript_stream = nullptr; + } + if (gsc_inputlog_stream) + { + g_vm->glk_stream_close (gsc_inputlog_stream, nullptr); + gsc_inputlog_stream = nullptr; + } + if (gsc_readlog_stream) + { + g_vm->glk_stream_close (gsc_readlog_stream, nullptr); + gsc_readlog_stream = nullptr; + } +} + + +/*---------------------------------------------------------------------*/ +/* Linkage between Glk entry/exit calls and the real interpreter */ +/*---------------------------------------------------------------------*/ + +/* + * Safety flags, to ensure we always get startup before main, and that + * we only get a call to main once. + */ +static int gsc_startup_called = FALSE, + gsc_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, or to report errors if gsc_game_message is set. + */ +void glk_main() { + assert (gsc_startup_called && !gsc_main_called); + gsc_main_called = TRUE; + + /* Call the generic interpreter main function. */ + gsc_main (); +} + + +/*---------------------------------------------------------------------*/ +/* Glk linkage relevant only to the UNIX platform */ +/*---------------------------------------------------------------------*/ +#ifdef UNUSED + +/* + * Glk arguments for UNIX versions of the Glk interpreter. + */ +glkunix_argumentlist_t glkunix_arguments[] = { + {(char *) "-nc", glkunix_arg_NoValue, + (char *) "-nc No local handling for Glk special commands"}, + {(char *) "-na", glkunix_arg_NoValue, + (char *) "-na Turn off abbreviation expansions"}, + {(char *) "-nu", glkunix_arg_NoValue, + (char *) "-nu Turn off any use of Unicode output"}, +#ifdef LINUX_GRAPHICS + {(char *) "-ng", glkunix_arg_NoValue, + (char *) "-ng Turn off attempts at game graphics"}, +#endif + {(char *) "-r", glkunix_arg_ValueFollows, + (char *) "-r FILE Restore from FILE on starting the game"}, + {(char *) "", glkunix_arg_ValueCanFollow, + (char *) "filename game to run"}, + {nullptr, glkunix_arg_End, nullptr} +}; + + +/* + * glkunix_startup_code() + * + * Startup entry point for UNIX versions of Glk interpreter. Glk will call + * glkunix_startup_code() to pass in arguments. On startup, parse arguments + * and open a Glk stream to the game, then call the generic gsc_startup_code() + * to build a game from the stream. On error, set the message in + * gsc_game_message; the core gsc_main() will report it when it's called. + */ +int +glkunix_startup_code (glkunix_startup_t * data) +{ + int argc = data->argc; + sc_char **argv = data->argv; + int argv_index; + sc_char *restore_from; + const sc_char *locale; + strid_t game_stream, restore_stream; + sc_uint trace_flags; + sc_bool enable_debugger, stable_random; + assert (!gsc_startup_called); + gsc_startup_called = TRUE; + +#ifdef GARGLK + garg_vm->glk_set_program_name("SCARE " SCARE_VERSION); + garg_vm->glk_set_program_info("SCARE " SCARE_VERSION + " by Simon Baldwin and Mark J. Tilford"); +#endif + + /* Handle command line arguments. */ + restore_from = nullptr; + for (argv_index = 1; + argv_index < argc && argv[argv_index][0] == '-'; argv_index++) + { + if (strcmp (argv[argv_index], "-nc") == 0) + { + gsc_commands_enabled = FALSE; + continue; + } + if (strcmp (argv[argv_index], "-na") == 0) + { + gsc_abbreviations_enabled = FALSE; + continue; + } + if (strcmp (argv[argv_index], "-nu") == 0) + { + gsc_unicode_enabled = FALSE; + continue; + } +#ifdef LINUX_GRAPHICS + if (strcmp (argv[argv_index], "-ng") == 0) + { + gsclinux_graphics_enabled = FALSE; + continue; + } +#endif + if (strcmp (argv[argv_index], "-r") == 0) + { + restore_from = argv[++argv_index]; + continue; + } + return FALSE; + } + + /* On invalid usage, set a complaint message and return. */ + if (argv_index != argc - 1) + { + gsc_game = nullptr; + if (argv_index < argc - 1) + gsc_game_message = "More than one game file" + " was given on the command line."; + else + gsc_game_message = "No game file was given on the command line."; + return TRUE; + } + + /* Open a stream to the TAF file, complain if this fails. */ + game_stream = glkunix_stream_open_pathname (argv[argv_index], FALSE, 0); + if (!game_stream) + { + gsc_game = nullptr; + gsc_game_message = "Unable to open the requested game file."; + return TRUE; + } + else + gsc_game_message = nullptr; + + /* + * If a restore requested, open a stream to the TAF (TAS) file, and + * again, complain if this fails. + */ + if (restore_from) + { + restore_stream = glkunix_stream_open_pathname (restore_from, FALSE, 0); + if (!restore_stream) + { + g_vm->glk_stream_close (game_stream, nullptr); + gsc_game = nullptr; + gsc_game_message = "Unable to open the requested restore file."; + return TRUE; + } + else + gsc_game_message = nullptr; + } + else + restore_stream = nullptr; + + /* Set SCARE trace flags and other general setup from the environment. */ + if (getenv ("SC_TRACE_FLAGS")) + trace_flags = strtoul (getenv ("SC_TRACE_FLAGS"), nullptr, 0); + else + trace_flags = 0; + enable_debugger = (getenv ("SC_DEBUGGER_ENABLED") != nullptr); + stable_random = (getenv ("SC_STABLE_RANDOM_ENABLED") != nullptr); + locale = getenv ("SC_LOCALE"); + +#ifdef LINUX_GRAPHICS + /* Note the path to the game file for graphics extraction. */ + gsclinux_game_file = argv[argv_index]; +#endif + + /* Use the generic startup code to complete startup. */ + return gsc_startup_code (game_stream, restore_stream, trace_flags, + enable_debugger, stable_random, locale); +} +#endif /* __unix */ + + +/*---------------------------------------------------------------------*/ +/* Glk linkage relevant only to the Windows platform */ +/*---------------------------------------------------------------------*/ +#ifdef _WIN32 + +#include <windows.h> + +#include "WinGlk.h" +#include "resource.h" + +/* Windows constants and external definitions. */ +static const unsigned int GSCWIN_glk_INIT_VERSION = 0x601; +extern int InitGlk (unsigned int iVersion); + +/* + * WinMain() + * + * Entry point for all Glk applications. + */ +int WINAPI +WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) +{ + /* Attempt to initialize both the Glk library and SCARE. */ + if (!(InitGlk (GSCWIN_glk_INIT_VERSION) && wing_vm->glk_startup_code (lpCmdLine))) + return 0; + + /* Run the application; no return from this routine. */ + g_vm->glk_main (); + g_vm->glk_exit (); + return 0; +} + + +/* + * wing_vm->glk_startup_code() + * + * Startup entry point for Windows versions of Glk interpreter. + */ +int +wing_vm->glk_startup_code (const char *cmdline) +{ + const char *filename, *locale; + frefid_t fileref; + strid_t game_stream; + sc_uint trace_flags; + sc_bool enable_debugger, stable_random; + assert (!gsc_startup_called); + gsc_startup_called = TRUE; + + /* Set up application and window. */ + wing_vm->glk_app_set_name ("Scare"); + wing_vm->glk_set_menu_name ("&Scare"); + wing_vm->glk_window_set_title ("Scare Adrift Interpreter"); + wing_vm->glk_set_about_text ("Windows Scare 1.3.10"); + wing_vm->glk_set_gui (IDI_SCARE); + + /* Open a stream to the game. */ + filename = wing_vm->glk_get_initial_filename (cmdline, + "Select an Adrift game to run", + "Adrift Files (.taf)|*.taf;All Files (*.*)|*.*||"); + if (!filename) + return 0; + + fileref = wing_vm->glk_fileref_create_by_name (fileusage_BinaryMode + | fileusage_Data, + (char *) filename, 0, 0); + if (!fileref) + return 0; + + game_stream = g_vm->glk_stream_open_file (fileref, filemode_Read, 0); + g_vm->glk_fileref_destroy (fileref); + if (!game_stream) + return 0; + + /* Set trace, debugger, and portable random flags. */ + if (getenv ("SC_TRACE_FLAGS")) + trace_flags = strtoul (getenv ("SC_TRACE_FLAGS"), nullptr, 0); + else + trace_flags = 0; + enable_debugger = (getenv ("SC_DEBUGGER_ENABLED") != nullptr); + stable_random = (getenv ("SC_STABLE_RANDOM_ENABLED") != nullptr); + locale = getenv ("SC_LOCALE"); + + /* Use the generic startup code to complete startup. */ + return gsc_startup_code (game_stream, nullptr, trace_flags, + enable_debugger, stable_random, locale); +} +#endif /* _WIN32 */ + +} // End of namespace Adrift +} // End of namespace Glk |