/* 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/config-manager.h" #include "common/textconsole.h" #include "common/translation.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. */ 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 = INTEGER_MAX, GSC_CONF_UNSUBTLE_HINT = INTEGER_MAX - 1, GSC_CONF_CONTINUE_HINTS = INTEGER_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((const 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 }; struct gsc_codepages_t { const glui32 unicode[GSC_TABLE_SIZE]; const sc_char *const ascii[GSC_TABLE_SIZE]; }; /* * Locale contains a name and a pair of codepage structures, a main one and * an alternate. The latter is intended for monospaced output. */ struct gsc_locale_t { const sc_char *const name; const gsc_codepages_t main; const gsc_codepages_t alternate; }; /* * 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, (const 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((const 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) { uint32 *unicode; /* * Allocate a unicode buffer long enough to hold all the characters, * then read in a unicode line. */ unicode = (uint32 *)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() { uint 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. */ struct gsc_font_size_t { sc_bool is_monospaced; sc_int size; }; /* 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 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((const 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. */ struct gsc_command_t { const char *const command; /* Glk subcommand. */ void (* const handler)(const char *argument); /* Subcommand handler. */ const int takes_argument; /* Argument flag. */ }; 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 > BYTE_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); if (g_vm->shouldQuit()) { g_vm->glk_cancel_line_event(gsc_main_window, event); return; } switch (event->type) { case evtype_Arrange: case evtype_Redraw: /* Refresh any sensitive windows on size events. */ gsc_status_redraw(); break; default: 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, (const 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 const char *gsc_game_message; /* * 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) { Common::SeekableReadStream *stream = (Common::SeekableReadStream *)opaque; assert(stream); return stream->read(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 > BYTE_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(Common::SeekableReadStream *game_stream, int restore_slot, 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 (!g_vm->shouldQuit() && event.type != evtype_Timer); g_vm->glk_request_timer_events(0); } if (g_vm->shouldQuit()) return false; } /* 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; } /* * 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_slot != -1) { if (g_vm->loadGameState(restore_slot).getCode() != Common::kNoError) { 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 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; Context context; /* 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(); if (!context._break) sc_interpret_game(context, gsc_game); // End point for any context long jump context.clear(); /* * 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(context, gsc_game); break; case GAME_UNDO: if (sc_is_game_undo_available(gsc_game)) { sc_undo_game_turn(context, 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(context, 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; /* * adrift_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 adrift_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 */ /*---------------------------------------------------------------------*/ #if 0 /* * Glk arguments for UNIX versions of the Glk interpreter. */ glkunix_argumentlist_t glkunix_arguments[] = { { (const char *)"-nc", glkunix_arg_NoValue, (const char *)"-nc No local handling for Glk special commands" }, { (const char *)"-na", glkunix_arg_NoValue, (const char *)"-na Turn off abbreviation expansions" }, { (const char *)"-nu", glkunix_arg_NoValue, (const char *)"-nu Turn off any use of Unicode output" }, #ifdef LINUX_GRAPHICS { (const char *)"-ng", glkunix_arg_NoValue, (const char *)"-ng Turn off attempts at game graphics" }, #endif { (const char *)"-r", glkunix_arg_ValueFollows, (const char *)"-r FILE Restore from FILE on starting the game" }, { (const char *)"", glkunix_arg_ValueCanFollow, (const char *)"filename game to run" }, {nullptr, glkunix_arg_End, nullptr} }; #endif /* * winglk_startup_code() * * Startup entry point for Windows versions of Glk interpreter. */ bool adrift_startup_code(Common::SeekableReadStream *gameFile) { const char *locale; sc_uint trace_flags; sc_bool enable_debugger, stable_random; assert(!gsc_startup_called); gsc_startup_called = TRUE; assert(gameFile); trace_flags = 0; enable_debugger = gDebugLevel > 0; stable_random = gDebugLevel > 0; locale = nullptr; // Check for savegame to load immediate int saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1; // Use the generic startup code to complete startup return gsc_startup_code(gameFile, saveSlot, trace_flags, enable_debugger, stable_random, locale); } } // End of namespace Adrift } // End of namespace Glk