1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/magnetic/magnetic_defs.h"
24 #include "glk/magnetic/magnetic.h"
25 
26 namespace Glk {
27 namespace Magnetic {
28 
29 const gms_command_t Magnetic::GMS_COMMAND_TABLE[14] = {
30 	{ &Magnetic::gms_command_summary,        "summary",        false, false },
31 	{ &Magnetic::gms_command_undo,           "undo",           false, true },
32 	{ &Magnetic::gms_command_script,         "script",         true,  false },
33 	{ &Magnetic::gms_command_inputlog,       "inputlog",       true,  false },
34 	{ &Magnetic::gms_command_readlog,        "readlog",        true,  false },
35 	{ &Magnetic::gms_command_abbreviations,  "abbreviations",  true,  false },
36 	{ &Magnetic::gms_command_graphics,       "graphics",       true,  false },
37 	{ &Magnetic::gms_command_gamma,          "gamma",          true,  false },
38 	{ &Magnetic::gms_command_animations,     "animations",     true,  false },
39 	{ &Magnetic::gms_command_prompts,        "prompts",        true,  false },
40 	{ &Magnetic::gms_command_version,        "version",        false, false },
41 	{ &Magnetic::gms_command_commands,       "commands",       true,  false },
42 	{ &Magnetic::gms_command_help,           "help",           true,  false },
43 
44 	{ nullptr, nullptr, false, false}
45 };
46 
47 const gms_gamma_t Magnetic::GMS_GAMMA_TABLE[38] = {
48 	{ "0.90", { 0,  29,  63,  99, 137, 175, 215, 255 }, true },
49 	{ "0.95", { 0,  33,  68, 105, 141, 179, 217, 255 }, true },
50 	{ "1.00", { 0,  36,  73, 109, 146, 182, 219, 255 }, false },
51 	{ "1.05", { 0,  40,  77, 114, 150, 185, 220, 255 }, true },
52 	{ "1.10", { 0,  43,  82, 118, 153, 188, 222, 255 }, true },
53 	{ "1.15", { 0,  47,  86, 122, 157, 190, 223, 255 }, true },
54 	{ "1.20", { 0,  50,  90, 126, 160, 193, 224, 255 }, true },
55 	{ "1.25", { 0,  54,  94, 129, 163, 195, 225, 255 }, true },
56 	{ "1.30", { 0,  57,  97, 133, 166, 197, 226, 255 }, true },
57 	{ "1.35", { 0,  60, 101, 136, 168, 199, 227, 255 }, true },
58 	{ "1.40", { 0,  64, 104, 139, 171, 201, 228, 255 }, true },
59 	{ "1.45", { 0,  67, 107, 142, 173, 202, 229, 255 }, true },
60 	{ "1.50", { 0,  70, 111, 145, 176, 204, 230, 255 }, true },
61 	{ "1.55", { 0,  73, 114, 148, 178, 205, 231, 255 }, true },
62 	{ "1.60", { 0,  76, 117, 150, 180, 207, 232, 255 }, true },
63 	{ "1.65", { 0,  78, 119, 153, 182, 208, 232, 255 }, true },
64 	{ "1.70", { 0,  81, 122, 155, 183, 209, 233, 255 }, true },
65 	{ "1.75", { 0,  84, 125, 157, 185, 210, 233, 255 }, true },
66 	{ "1.80", { 0,  87, 127, 159, 187, 212, 234, 255 }, true },
67 	{ "1.85", { 0,  89, 130, 161, 188, 213, 235, 255 }, true },
68 	{ "1.90", { 0,  92, 132, 163, 190, 214, 235, 255 }, true },
69 	{ "1.95", { 0,  94, 134, 165, 191, 215, 236, 255 }, true },
70 	{ "2.00", { 0,  96, 136, 167, 193, 216, 236, 255 }, true },
71 	{ "2.05", { 0,  99, 138, 169, 194, 216, 237, 255 }, true },
72 	{ "2.10", { 0, 101, 140, 170, 195, 217, 237, 255 }, true },
73 	{ "2.15", { 0, 103, 142, 172, 197, 218, 237, 255 }, true },
74 	{ "2.20", { 0, 105, 144, 173, 198, 219, 238, 255 }, true },
75 	{ "2.25", { 0, 107, 146, 175, 199, 220, 238, 255 }, true },
76 	{ "2.30", { 0, 109, 148, 176, 200, 220, 238, 255 }, true },
77 	{ "2.35", { 0, 111, 150, 178, 201, 221, 239, 255 }, true },
78 	{ "2.40", { 0, 113, 151, 179, 202, 222, 239, 255 }, true },
79 	{ "2.45", { 0, 115, 153, 180, 203, 222, 239, 255 }, true },
80 	{ "2.50", { 0, 117, 154, 182, 204, 223, 240, 255 }, true },
81 	{ "2.55", { 0, 119, 156, 183, 205, 223, 240, 255 }, true },
82 	{ "2.60", { 0, 121, 158, 184, 206, 224, 240, 255 }, true },
83 	{ "2.65", { 0, 122, 159, 185, 206, 225, 241, 255 }, true },
84 	{ "2.70", { 0, 124, 160, 186, 207, 225, 241, 255 }, true },
85 	{ NULL,   { 0,   0,   0,   0,   0,   0,   0,   0 }, false }
86 };
87 
88 static gms_abbreviation_t GMS_ABBREVIATIONS[] = {
89 	{'c', "close"},    {'g', "again"},  {'i', "inventory"},
90 	{'k', "attack"},   {'l', "look"},   {'p', "open"},
91 	{'q', "quit"},     {'r', "drop"},   {'t', "take"},
92 	{'x', "examine"},  {'y', "yes"},    {'z', "wait"},
93 	{'\0', NULL}
94 };
95 
96 /*---------------------------------------------------------------------*/
97 /*  Module constants                                                   */
98 /*---------------------------------------------------------------------*/
99 
100 #ifndef GARGLK
101 /*
102  * Maximum number of regions to consider in a single repaint pass.  A
103  * couple of hundred seems to strike the right balance between not too
104  * sluggardly picture updates, and responsiveness to input during graphics
105  * rendering, when combined with short timeouts.
106  */
107 static const int GMS_REPAINT_LIMIT = 256;
108 #endif
109 
110 /*
111  * Graphics timeout; we like an update call after this period (ms).  In
112  * practice, this timeout may actually be shorter than the time taken
113  * to reach the limit on repaint regions, but because Glk guarantees that
114  * user interactions (in this case, line events) take precedence over
115  * timeouts, this should be okay; we'll still see a game that responds to
116  * input each time the background repaint function yields.
117  *
118  * Setting this value is tricky.  We'd like it to be the shortest possible
119  * consistent with getting other stuff done, say 10ms.  However, Xglk has
120  * a granularity of 50ms on checking for timeouts, as it uses a 1/20s
121  * timeout on X select.  This means that the shortest timeout we'll ever
122  * get from Xglk will be 50ms, so there's no point in setting this shorter
123  * than that.  With luck, other Glk libraries will be more efficient than
124  * this, and can give us higher timer resolution; we'll set 50ms here, and
125  * hope that no other Glk library is worse.
126  */
127 static const glui32 GMS_GRAPHICS_TIMEOUT = 50;
128 
129 /*
130  * Count of timeouts to wait in between animation paints, and to wait on
131  * repaint request.  Waiting for 2 timeouts of around 50ms, gets us to the
132  * 100ms recommended animation frame rate.  Waiting after a repaint smooths
133  * the display where the frame is being resized, by helping to avoid
134  * graphics output while more resize events are received; around 1/2 second
135  * seems okay.
136  */
137 static const int GMS_GRAPHICS_ANIMATION_WAIT = 2,
138 GMS_GRAPHICS_REPAINT_WAIT = 10;
139 
140 /* Pixel size multiplier for image size scaling. */
141 static const int GMS_GRAPHICS_PIXEL = 2;
142 
143 /* Proportion of the display to use for graphics. */
144 static const glui32 GMS_GRAPHICS_PROPORTION = 60;
145 
146 /*
147  * Border and shading control.  For cases where we can't detect the back-
148  * ground color of the main window, there's a default, white, background.
149  * Bordering is black, with a 1 pixel border, 2 pixel shading, and 8 steps
150  * of shading fade.
151  */
152 static const glui32 GMS_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
153 	GMS_GRAPHICS_BORDER_COLOR = 0x00000000;
154 static const int GMS_GRAPHICS_BORDER = 1,
155 	GMS_GRAPHICS_SHADING = 2,
156 	GMS_GRAPHICS_SHADE_STEPS = 8;
157 
158 /*
159  * Guaranteed unused pixel value.  This value is used to fill the on-screen
160  * buffer on new pictures or repaints, resulting in a full paint of all
161  * pixels since no off-screen, real picture, pixel will match it.
162  */
163 static const int GMS_GRAPHICS_UNUSED_PIXEL = 0xff;
164 
165 /* Default width used for non-windowing Glk status lines. */
166 static const int GMS_DEFAULT_STATUS_WIDTH = 74;
167 
168 /* Success and fail return codes from hint functions. */
169 static const type8 GMS_HINT_SUCCESS = 1;
170 // static const type8 GMS_HINT_ERROR = 0;
171 
172 /* Default window sizes for non-windowing Glk libraries. */
173 static const glui32 GMS_HINT_DEFAULT_WIDTH = 72,
174 GMS_HINT_DEFAULT_HEIGHT = 25;
175 
176 /*
177  * Special hint nodes indicating the root hint node, and a value to signal
178  * quit from hints subsystem.
179  */
180 static const type16 GMS_HINT_ROOT_NODE = 0,
181 GMS_HINTS_DONE = UINT16_MAX_VAL;
182 
183 /* Generic hint topic for the root hints node. */
184 static const char *const GMS_GENERIC_TOPIC = "Hints Menu";
185 
186 /*---------------------------------------------------------------------*/
187 /*  Glk port utility functions                                         */
188 /*---------------------------------------------------------------------*/
189 
gms_fatal(const char * str)190 void Magnetic::gms_fatal(const char *str) {
191 	/*
192 	 * If the failure happens too early for us to have a window, print
193 	 * the message to stderr.
194 	 */
195 	if (!gms_main_window)
196 		error("\n\nINTERNAL ERROR: %s", str);
197 
198 	/* Cancel all possible pending window input events. */
199 	glk_cancel_line_event(gms_main_window, NULL);
200 	glk_cancel_char_event(gms_main_window);
201 	if (gms_hint_menu_window) {
202 		glk_cancel_char_event(gms_hint_menu_window);
203 		glk_window_close(gms_hint_menu_window, NULL);
204 	}
205 	if (gms_hint_text_window) {
206 		glk_cancel_char_event(gms_hint_text_window);
207 		glk_window_close(gms_hint_text_window, NULL);
208 	}
209 
210 	/* Print a message indicating the error. */
211 	glk_set_window(gms_main_window);
212 	glk_set_style(style_Normal);
213 	glk_put_string("\n\nINTERNAL ERROR: ");
214 	glk_put_string(str);
215 
216 	glk_put_string("\n\nPlease record the details of this error, try to"
217 	                     " note down everything you did to cause it, and email"
218 	                     " this information to simon_baldwin@yahoo.com.\n\n");
219 }
220 
gms_malloc(size_t size)221 void *Magnetic::gms_malloc(size_t size) {
222 	void *pointer;
223 
224 	pointer = malloc(size);
225 	if (!pointer) {
226 		gms_fatal("GLK: Out of system memory");
227 		glk_exit();
228 	}
229 
230 	return pointer;
231 }
232 
gms_realloc(void * ptr,size_t size)233 void *Magnetic::gms_realloc(void *ptr, size_t size) {
234 	void *pointer;
235 
236 	pointer = realloc(ptr, size);
237 	if (!pointer) {
238 		gms_fatal("GLK: Out of system memory");
239 		glk_exit();
240 	}
241 
242 	return pointer;
243 }
244 
gms_strncasecmp(const char * s1,const char * s2,size_t n)245 int Magnetic::gms_strncasecmp(const char *s1, const char *s2, size_t n) {
246 	size_t index;
247 
248 	for (index = 0; index < n; index++) {
249 		int diff;
250 
251 		diff = glk_char_to_lower(s1[index]) - glk_char_to_lower(s2[index]);
252 		if (diff < 0 || diff > 0)
253 			return diff < 0 ? -1 : 1;
254 	}
255 
256 	return 0;
257 }
258 
gms_strcasecmp(const char * s1,const char * s2)259 int Magnetic::gms_strcasecmp(const char *s1, const char *s2) {
260 	size_t s1len, s2len;
261 	int result;
262 
263 	s1len = strlen(s1);
264 	s2len = strlen(s2);
265 
266 	result = gms_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
267 	if (result < 0 || result > 0)
268 		return result;
269 	else
270 		return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
271 }
272 
273 /*---------------------------------------------------------------------*/
274 /*  Glk port CRC functions                                             */
275 /*---------------------------------------------------------------------*/
276 
gms_get_buffer_crc(const void * void_buffer,size_t length)277 glui32 Magnetic::gms_get_buffer_crc(const void *void_buffer, size_t length) {
278 	const char *buf = (const char *) void_buffer;
279 	uint32 crc;
280 	size_t index;
281 
282 	/*
283 	 * Start with all ones in the crc, then update using table entries.  Xor
284 	 * with all ones again, finally, before returning.
285 	 */
286 	crc = 0xffffffff;
287 	for (index = 0; index < length; index++)
288 		crc = crc_table[(crc ^ buf[index]) & BYTE_MAX_VAL] ^ (crc >> BITS_PER_BYTE);
289 	return crc ^ 0xffffffff;
290 }
291 
292 /*---------------------------------------------------------------------*/
293 /*  Glk port game identification data and identification functions     */
294 /*---------------------------------------------------------------------*/
295 
gms_gameid_read_uint32(int offset,Common::SeekableReadStream * stream)296 type32 Magnetic::gms_gameid_read_uint32(int offset, Common::SeekableReadStream *stream) {
297 	if (!stream->seek(offset))
298 		return 0;
299 	return stream->readUint32BE();
300 }
301 
gms_gameid_identify_game(const Common::String & text_file)302 void Magnetic::gms_gameid_identify_game(const Common::String &text_file) {
303 	Common::File stream;
304 
305 	if (!stream.open(text_file))
306 		error("Error opening game file");
307 
308 	type32 game_size, game_pc;
309 	gms_game_tableref_t game;
310 
311 	/* Read the game's signature undo size and undo pc values. */
312 	game_size = gms_gameid_read_uint32(0x22, &stream);
313 	game_pc = gms_gameid_read_uint32(0x26, &stream);
314 
315 	/* Search for these values in the table, and set game name if found. */
316 	game = gms_gameid_lookup_game(game_size, game_pc);
317 	gms_gameid_game_name = game ? game->name : NULL;
318 }
319 
320 /*---------------------------------------------------------------------*/
321 /*  Glk port picture functions                                         */
322 /*---------------------------------------------------------------------*/
323 
gms_graphics_open()324 int Magnetic::gms_graphics_open() {
325 	if (!gms_graphics_window) {
326 		gms_graphics_window = glk_window_open(gms_main_window,
327 		                      winmethod_Above
328 		                      | winmethod_Proportional,
329 		                      GMS_GRAPHICS_PROPORTION,
330 		                      wintype_Graphics, 0);
331 	}
332 
333 	return gms_graphics_window != NULL;
334 }
335 
gms_graphics_close()336 void Magnetic::gms_graphics_close() {
337 	if (gms_graphics_window) {
338 		glk_window_close(gms_graphics_window, NULL);
339 		gms_graphics_window = NULL;
340 	}
341 }
342 
gms_graphics_start()343 void Magnetic::gms_graphics_start() {
344 	if (gms_graphics_enabled) {
345 		/* If not running, start the updating "thread". */
346 		if (!gms_graphics_active) {
347 			glk_request_timer_events(GMS_GRAPHICS_TIMEOUT);
348 			gms_graphics_active = true;
349 		}
350 	}
351 }
352 
gms_graphics_stop()353 void Magnetic::gms_graphics_stop() {
354 	/* If running, stop the updating "thread". */
355 	if (gms_graphics_active) {
356 		glk_request_timer_events(0);
357 		gms_graphics_active = false;
358 	}
359 }
360 
gms_graphics_paint()361 void Magnetic::gms_graphics_paint() {
362 	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
363 		/* Set the repaint flag, and start graphics. */
364 		gms_graphics_repaint = true;
365 		gms_graphics_start();
366 	}
367 }
368 
gms_graphics_restart()369 void Magnetic::gms_graphics_restart() {
370 	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
371 		/*
372 		 * If the picture is animated, we'll need to be able to re-get the
373 		 * first animation frame so that the picture can be treated as if
374 		 * it is a new one.  So here, we'll try to re-extract the current
375 		 * picture to do this.  Calling ms_extract() is safe because we
376 		 * don't get here unless graphics are displayed, and graphics aren't
377 		 * displayed until there's a valid picture loaded, and ms_showpic
378 		 * only loads a picture after it's called ms_extract and set the
379 		 * picture id into gms_graphics_picture.
380 		 *
381 		 * The bitmap and other picture stuff can be ignored because it's
382 		 * the precise same stuff as we already have in picture details
383 		 * variables.  If the ms_extract() fails, we'll carry on regardless,
384 		 * which may, or may not, result in the ideal picture display.
385 		 *
386 		 * One or two non-animated pictures return NULL from ms_extract()
387 		 * being re-called, so we'll restrict calls to animations only.
388 		 * And just to be safe, we'll also call only if we're already
389 		 * holding a bitmap (and we should be; how else could the graphics
390 		 * animation flag be set?...).
391 		 */
392 		if (gms_graphics_animated && gms_graphics_bitmap) {
393 			type8 animated;
394 			type16 width, height, palette[GMS_PALETTE_SIZE];
395 
396 			/* Extract the bitmap into dummy variables. */
397 			(void)ms_extract(gms_graphics_picture, &width, &height, palette, &animated);
398 		}
399 
400 		/* Set the new picture flag, and start graphics. */
401 		gms_graphics_new_picture = true;
402 		gms_graphics_start();
403 	}
404 }
405 
gms_graphics_count_colors(type8 bitmap[],type16 width,type16 height,int * color_count,long color_usage[])406 void Magnetic::gms_graphics_count_colors(type8 bitmap[], type16 width, type16 height,
407 		int *color_count, long color_usage[]) {
408 	int x, y, count;
409 	long usage[GMS_PALETTE_SIZE], index_row;
410 	assert(bitmap);
411 
412 	/*
413 	 * Traverse the image, counting each pixel usage.  For the y iterator,
414 	 * maintain an index row as an optimization to avoid multiplications in
415 	 * the loop.
416 	 */
417 	count = 0;
418 	memset(usage, 0, sizeof(usage));
419 	for (y = 0, index_row = 0; y < height; y++, index_row += width) {
420 		for (x = 0; x < width; x++) {
421 			long index;
422 
423 			/* Get the pixel index, and update the count for this color. */
424 			index = index_row + x;
425 			usage[bitmap[index]]++;
426 
427 			/* If color usage is now 1, note new color encountered. */
428 			if (usage[bitmap[index]] == 1)
429 				count++;
430 		}
431 	}
432 
433 	if (color_count)
434 		*color_count = count;
435 
436 	if (color_usage)
437 		memcpy(color_usage, usage, sizeof(usage));
438 }
439 
gms_graphics_game_to_rgb_color(type16 color,gms_gammaref_t gamma,gms_rgbref_t rgb_color)440 void Magnetic::gms_graphics_game_to_rgb_color(type16 color, gms_gammaref_t gamma,
441 		gms_rgbref_t rgb_color) {
442 	assert(gamma && rgb_color);
443 
444 	/*
445 	 * Convert Magnetic Scrolls color, through gamma, into RGB.  This splits
446 	 * the color into components based on the 3-bits used in the game palette,
447 	 * and gamma-corrects and rescales each to the range 0-255, using the given
448 	 * correction.
449 	 */
450 	rgb_color->red   = gamma->table[(color & 0x700) >> 8];
451 	rgb_color->green = gamma->table[(color & 0x070) >> 4];
452 	rgb_color->blue  = gamma->table[(color & 0x007)];
453 }
454 
gms_graphics_split_color(glui32 color,gms_rgbref_t rgb_color)455 void Magnetic::gms_graphics_split_color(glui32 color, gms_rgbref_t rgb_color) {
456 	assert(rgb_color);
457 
458 	rgb_color->red   = (color >> 16) & 0xff;
459 	rgb_color->green = (color >> 8) & 0xff;
460 	rgb_color->blue  = color & 0xff;
461 }
462 
gms_graphics_combine_color(gms_rgbref_t rgb_color)463 glui32 Magnetic::gms_graphics_combine_color(gms_rgbref_t rgb_color) {
464 	assert(rgb_color && _screen->format.bytesPerPixel == 2);
465 	return  _screen->format.RGBToColor(rgb_color->red, rgb_color->green, rgb_color->blue);
466 }
467 
gms_graphics_color_luminance(gms_rgbref_t rgb_color)468 int Magnetic::gms_graphics_color_luminance(gms_rgbref_t rgb_color) {
469 	/* Calculate the luminance and scale back by 1000 to 0-255 before return. */
470 	long luminance = ((long) rgb_color->red   * (long) GMS_LUMINANCE_WEIGHTS.red
471 	             + (long) rgb_color->green * (long) GMS_LUMINANCE_WEIGHTS.green
472 	             + (long) rgb_color->blue  * (long) GMS_LUMINANCE_WEIGHTS.blue);
473 
474 	assert(luminance_weighting > 0);
475 	return (int)(luminance / luminance_weighting);
476 }
477 
gms_graphics_compare_luminance(const void * void_first,const void * void_second)478 int Magnetic::gms_graphics_compare_luminance(const void *void_first,
479 		const void *void_second) {
480 	long first = *(const long *)void_first;
481 	long second = *(const long *)void_second;
482 
483 	return first > second ? 1 : second > first ? -1 : 0;
484 }
485 
gms_graphics_contrast_variance(type16 palette[],long color_usage[],gms_gammaref_t gamma)486 long Magnetic::gms_graphics_contrast_variance(type16 palette[],
487 		long color_usage[], gms_gammaref_t gamma) {
488 	int index, count, has_black, mean;
489 	long sum;
490 	int contrast[GMS_PALETTE_SIZE];
491 	int luminance[GMS_PALETTE_SIZE + 1];  /* Luminance for each color,
492 										   plus one extra for black */
493 
494 	/* Calculate the luminance energy of each palette color at this gamma. */
495 	has_black = false;
496 	for (index = 0, count = 0; index < GMS_PALETTE_SIZE; index++) {
497 		if (color_usage[index] > 0) {
498 			gms_rgb_t rgb_color;
499 
500 			/*
501 			 * Convert the 16-bit base picture color to RGB using the gamma
502 			 * currently under consideration.  Calculate luminance for this
503 			 * color and store in the next available luminance array entry.
504 			 */
505 			gms_graphics_game_to_rgb_color(palette[index], gamma, &rgb_color);
506 			luminance[count++] = gms_graphics_color_luminance(&rgb_color);
507 
508 			/* Note if black is present in the palette. */
509 			has_black |= luminance[count - 1] == 0;
510 		}
511 	}
512 
513 	/*
514 	 * For best results, we want to anchor contrast calculations to black, so
515 	 * if black is not represented in the palette, add it as an extra luminance.
516 	 */
517 	if (!has_black)
518 		luminance[count++] = 0;
519 
520 	/* Sort luminance values so that the darkest color is at index 0. */
521 	qsort(luminance, count,
522 	      sizeof(*luminance), gms_graphics_compare_luminance);
523 
524 	/*
525 	 * Calculate the difference in luminance between adjacent luminances in
526 	 * the sorted array, as contrast, and at the same time sum contrasts to
527 	 * calculate the mean.
528 	 */
529 	sum = 0;
530 	for (index = 0; index < count - 1; index++) {
531 		contrast[index] = luminance[index + 1] - luminance[index];
532 		sum += contrast[index];
533 	}
534 	mean = sum / (count - 1);
535 
536 	/* Calculate and return the variance in contrasts. */
537 	sum = 0;
538 	for (index = 0; index < count - 1; index++)
539 		sum += (contrast[index] - mean) * (contrast[index] - mean);
540 
541 	return sum / (count - 1);
542 }
543 
gms_graphics_equal_contrast_gamma(type16 palette[],long color_usage[])544 gms_gammaref_t Magnetic::gms_graphics_equal_contrast_gamma(type16 palette[], long color_usage[]) {
545 	gms_gammaref_t gamma, result;
546 	long lowest_variance;
547 	assert(palette && color_usage);
548 
549 	result = NULL;
550 	lowest_variance = INT32_MAX_VAL;
551 
552 	/* Search the gamma table for the entry with the lowest contrast variance. */
553 	for (gamma = GMS_GAMMA_TABLE; gamma->level; gamma++) {
554 		long variance;
555 
556 		/* Find the color contrast variance of the palette at this gamma. */
557 		variance = gms_graphics_contrast_variance(palette, color_usage, gamma);
558 
559 		/*
560 		 * Compare the variance to the lowest so far, and if it is lower, note
561 		 * the gamma entry that produced it as being the current best found.
562 		 */
563 		if (variance < lowest_variance) {
564 			result = gamma;
565 			lowest_variance = variance;
566 		}
567 	}
568 
569 	assert(result);
570 	return result;
571 }
572 
gms_graphics_select_gamma(type8 bitmap[],type16 width,type16 height,type16 palette[])573 gms_gammaref_t Magnetic::gms_graphics_select_gamma(type8 bitmap[],
574 		type16 width, type16 height, type16 palette[]) {
575 	long color_usage[GMS_PALETTE_SIZE];
576 	int color_count;
577 	gms_gammaref_t contrast_gamma;
578 	assert(linear_gamma);
579 
580 	/*
581 	 * Check to see if automated correction is turned off; if it is, return
582 	 * the linear gamma.
583 	 */
584 	if (gms_gamma_mode == GAMMA_OFF)
585 		return linear_gamma;
586 
587 	/*
588 	 * Get the color usage and count of total colors represented.  For a
589 	 * degenerate picture with one color or less, return the linear gamma.
590 	 */
591 	gms_graphics_count_colors(bitmap, width, height, &color_count, color_usage);
592 	if (color_count <= 1)
593 		return linear_gamma;
594 
595 	/*
596 	 * Now calculate a gamma setting to give the most equal contrast across the
597 	 * picture colors.  We'll return either half this gamma, or all of it.
598 	 */
599 	contrast_gamma = gms_graphics_equal_contrast_gamma(palette, color_usage);
600 
601 	/*
602 	 * For normal automated correction, return a gamma value half way between
603 	 * the linear gamma and the equal contrast gamma.
604 	 */
605 	if (gms_gamma_mode == GAMMA_NORMAL)
606 		return linear_gamma + (contrast_gamma - linear_gamma) / 2;
607 
608 	/* Correction must be high; return the equal contrast gamma. */
609 	assert(gms_gamma_mode == GAMMA_HIGH);
610 	return contrast_gamma;
611 }
612 
gms_graphics_clear_and_border(winid_t glk_window,int x_offset,int y_offset,int pixel_size,type16 width,type16 height)613 void Magnetic::gms_graphics_clear_and_border(winid_t glk_window,
614 		int x_offset, int y_offset, int pixel_size, type16 width, type16 height) {
615 	uint background;
616 	glui32 fade_color, shading_color;
617 	gms_rgb_t rgb_background, rgb_border, rgb_fade;
618 	int index;
619 	assert(glk_window);
620 
621 	/*
622 	 * Try to detect the background color of the main window, by getting the
623 	 * background for Normal style (Glk offers no way to directly get a window's
624 	 * background color).  If we can get it, we'll match the graphics window
625 	 * background to it.  If we can't, we'll default the color to white.
626 	 */
627 	if (!glk_style_measure(gms_main_window,
628 	                             style_Normal, stylehint_BackColor, &background)) {
629 		/*
630 		 * Unable to get the main window background, so assume, and default
631 		 * graphics to white.
632 		 */
633 		background = GMS_GRAPHICS_DEFAULT_BACKGROUND;
634 	}
635 
636 	/*
637 	 * Set the graphics window background to match the main window background,
638 	 * as best as we can tell, and clear the window.
639 	 */
640 	glk_window_set_background_color(glk_window, background);
641 	glk_window_clear(glk_window);
642 
643 	/*
644 	 * For very small pictures, just border them, but don't try and do any
645 	 * shading.  Failing this check is probably highly unlikely.
646 	 */
647 	if (width < 2 * GMS_GRAPHICS_SHADE_STEPS
648 	        || height < 2 * GMS_GRAPHICS_SHADE_STEPS) {
649 		/* Paint a rectangle bigger than the picture by border pixels. */
650 		glk_window_fill_rect(glk_window,
651 		                           GMS_GRAPHICS_BORDER_COLOR,
652 		                           x_offset - GMS_GRAPHICS_BORDER,
653 		                           y_offset - GMS_GRAPHICS_BORDER,
654 		                           width * pixel_size + GMS_GRAPHICS_BORDER * 2,
655 		                           height * pixel_size + GMS_GRAPHICS_BORDER * 2);
656 		return;
657 	}
658 
659 	/*
660 	 * Paint a rectangle bigger than the picture by border pixels all round,
661 	 * and with additional shading pixels right and below.  Some of these
662 	 * shading pixels are later overwritten by the fading loop below.  The
663 	 * picture will sit over this rectangle.
664 	 */
665 	glk_window_fill_rect(glk_window,
666 	                           GMS_GRAPHICS_BORDER_COLOR,
667 	                           x_offset - GMS_GRAPHICS_BORDER,
668 	                           y_offset - GMS_GRAPHICS_BORDER,
669 	                           width * pixel_size + GMS_GRAPHICS_BORDER * 2
670 	                           + GMS_GRAPHICS_SHADING,
671 	                           height * pixel_size + GMS_GRAPHICS_BORDER * 2
672 	                           + GMS_GRAPHICS_SHADING);
673 
674 	/*
675 	 * Split the main window background color and the border color into
676 	 * components.
677 	 */
678 	gms_graphics_split_color(background, &rgb_background);
679 	gms_graphics_split_color(GMS_GRAPHICS_BORDER_COLOR, &rgb_border);
680 
681 	/*
682 	 * Generate the incremental color to use in fade steps.  Here we're
683 	 * assuming that the border is always darker than the main window
684 	 * background (currently valid, as we're using black).
685 	 */
686 	rgb_fade.red = (rgb_background.red - rgb_border.red)
687 	               / GMS_GRAPHICS_SHADE_STEPS;
688 	rgb_fade.green = (rgb_background.green - rgb_border.green)
689 	                 / GMS_GRAPHICS_SHADE_STEPS;
690 	rgb_fade.blue = (rgb_background.blue - rgb_border.blue)
691 	                / GMS_GRAPHICS_SHADE_STEPS;
692 
693 	/* Combine RGB fade into a single incremental Glk color. */
694 	fade_color = gms_graphics_combine_color(&rgb_fade);
695 
696 	/* Fade in edge, from background to border, shading in stages. */
697 	shading_color = background;
698 	for (index = 0; index < GMS_GRAPHICS_SHADE_STEPS; index++) {
699 		/* Shade the two border areas with this color. */
700 		glk_window_fill_rect(glk_window, shading_color,
701 		                           x_offset + width * pixel_size
702 		                           + GMS_GRAPHICS_BORDER,
703 		                           y_offset + index - GMS_GRAPHICS_BORDER,
704 		                           GMS_GRAPHICS_SHADING, 1);
705 		glk_window_fill_rect(glk_window, shading_color,
706 		                           x_offset + index - GMS_GRAPHICS_BORDER,
707 		                           y_offset + height * pixel_size
708 		                           + GMS_GRAPHICS_BORDER,
709 		                           1, GMS_GRAPHICS_SHADING);
710 
711 		/* Update the shading color for the fade next iteration. */
712 		shading_color -= fade_color;
713 	}
714 }
715 
gms_graphics_convert_palette(type16 ms_palette[],gms_gammaref_t gamma,glui32 glk_palette[])716 void Magnetic::gms_graphics_convert_palette(type16 ms_palette[], gms_gammaref_t gamma,
717 		glui32 glk_palette[]) {
718 	int index;
719 	assert(ms_palette && gamma && glk_palette);
720 
721 	for (index = 0; index < GMS_PALETTE_SIZE; index++) {
722 		gms_rgb_t rgb_color;
723 
724 		/*
725 		 * Convert the 16-bit base picture color through gamma to a 32-bit
726 		 * RGB color, and combine into a Glk color and store in the Glk palette.
727 		 */
728 		gms_graphics_game_to_rgb_color(ms_palette[index], gamma, &rgb_color);
729 		glk_palette[index] = gms_graphics_combine_color(&rgb_color);
730 	}
731 }
732 
gms_graphics_position_picture(winid_t glk_window,int pixel_size,type16 width,type16 height,int * x_offset,int * y_offset)733 void Magnetic::gms_graphics_position_picture(winid_t glk_window,
734 		int pixel_size, type16 width, type16 height, int *x_offset, int *y_offset) {
735 	uint window_width, window_height;
736 	assert(glk_window && x_offset && y_offset);
737 
738 	/* Measure the current graphics window dimensions. */
739 	glk_window_get_size(glk_window, &window_width, &window_height);
740 
741 	/*
742 	 * Calculate and return an x and y offset to use on point plotting, so that
743 	 * the image centers inside the graphical window.
744 	 */
745 	*x_offset = ((int) window_width - width * pixel_size) / 2;
746 	*y_offset = ((int) window_height - height * pixel_size) / 2;
747 }
748 
gms_graphics_apply_animation_frame(type8 bitmap[],type16 frame_width,type16 frame_height,type8 mask[],int frame_x,int frame_y,type8 off_screen[],type16 width,type16 height)749 void Magnetic::gms_graphics_apply_animation_frame(type8 bitmap[],
750 		type16 frame_width, type16 frame_height, type8 mask[], int frame_x, int frame_y,
751 		type8 off_screen[], type16 width, type16 height) {
752 	int mask_width, x, y;
753 	type8 mask_hibit;
754 	long frame_row, buffer_row, mask_row;
755 	assert(bitmap && off_screen);
756 
757 	/*
758 	 * It turns out that the mask isn't quite as described in defs.h, and thanks
759 	 * to Torbjorn Andersson and his Gtk port of Magnetic for illuminating this.
760 	 * The mask is made up of lines of 16-bit words, so the mask width is always
761 	 * even.  Here we'll calculate the real width of a mask, and also set a high
762 	 * bit for later on.
763 	 */
764 	mask_width = (((frame_width - 1) / BITS_PER_BYTE) + 2) & (~1);
765 	mask_hibit = 1 << (BITS_PER_BYTE - 1);
766 
767 	/*
768 	 * Initialize row index components; these are optimizations to avoid the
769 	 * need for multiplications in the frame iteration loop.
770 	 */
771 	frame_row = 0;
772 	buffer_row = frame_y * width;
773 	mask_row = 0;
774 
775 	/*
776 	 * Iterate over each frame row, clipping where y lies outside the main
777 	 * picture area.
778 	 */
779 	for (y = 0; y < frame_height; y++) {
780 		/* Clip if y is outside the main picture area. */
781 		if (y + frame_y < 0 || y + frame_y >= height) {
782 			/* Update optimization variables as if not clipped. */
783 			frame_row += frame_width;
784 			buffer_row += width;
785 			mask_row += mask_width;
786 			continue;
787 		}
788 
789 		/* Iterate over each frame column, clipping again. */
790 		for (x = 0; x < frame_width; x++) {
791 			long frame_index, buffer_index;
792 
793 			/* Clip if x is outside the main picture area. */
794 			if (x + frame_x < 0 || x + frame_x >= width)
795 				continue;
796 
797 			/*
798 			 * If there's a mask, check the bit associated with this x,y, and
799 			 * ignore any transparent pixels.
800 			 */
801 			if (mask) {
802 				type8 mask_byte;
803 
804 				/* Isolate the mask byte, and test the transparency bit. */
805 				mask_byte = mask[mask_row + (x / BITS_PER_BYTE)];
806 				if ((mask_byte & (mask_hibit >> (x % BITS_PER_BYTE))) != 0)
807 					continue;
808 			}
809 
810 			/*
811 			 * Calculate indexes for this pixel into the frame, and into the
812 			 * main off-screen buffer, and transfer the frame pixel into the
813 			 * off-screen buffer.
814 			 */
815 			frame_index = frame_row + x;
816 			buffer_index = buffer_row + x + frame_x;
817 			off_screen[buffer_index] = bitmap[frame_index];
818 		}
819 
820 		/* Update row index components on change of y. */
821 		frame_row += frame_width;
822 		buffer_row += width;
823 		mask_row += mask_width;
824 	}
825 }
826 
gms_graphics_animate(type8 off_screen[],type16 width,type16 height)827 int Magnetic::gms_graphics_animate(type8 off_screen[], type16 width, type16 height) {
828 	struct ms_position *positions;
829 	type16 count;
830 	type8 status;
831 	int frame;
832 	assert(off_screen);
833 
834 	/* Search for more animation frames, and return zero if none. */
835 	status = ms_animate(&positions, &count);
836 	if (status == 0)
837 		return false;
838 
839 	/* Apply each animation frame to the off-screen buffer. */
840 	for (frame = 0; frame < count; frame++) {
841 		type8 *bitmap, *mask;
842 		type16 frame_width, frame_height;
843 
844 		/*
845 		 * Get the bitmap and other details for this frame.  If we can't get
846 		 * this animation frame, skip it and see if any others are available.
847 		 */
848 		bitmap = ms_get_anim_frame(positions[frame].number,
849 		                           &frame_width, &frame_height, &mask);
850 		if (bitmap) {
851 			gms_graphics_apply_animation_frame(bitmap,
852 			                                   frame_width, frame_height, mask,
853 			                                   positions[frame].x,
854 			                                   positions[frame].y,
855 			                                   off_screen, width, height);
856 		}
857 	}
858 
859 	/* Return true since more animation frames remain. */
860 	return true;
861 }
862 
863 #ifndef GARGLK
gms_graphics_is_vertex(type8 off_screen[],type16 width,type16 height,int x,int y)864 int Magnetic::gms_graphics_is_vertex(type8 off_screen[], type16 width, type16 height,
865 		int x, int y) {
866 	type8 pixel;
867 	int above, below, left, right;
868 	long index_row;
869 	assert(off_screen);
870 
871 	/* Use an index row to cut down on multiplications. */
872 	index_row = y * width;
873 
874 	/* Find the color of the reference pixel. */
875 	pixel = off_screen[index_row + x];
876 	assert(pixel < GMS_PALETTE_SIZE);
877 
878 	/*
879 	 * Detect differences between the reference pixel and its upper, lower, left
880 	 * and right neighbors.  Mark as different if the neighbor doesn't exist,
881 	 * that is, at the edge of the picture.
882 	 */
883 	above = (y == 0 || off_screen[index_row - width + x] != pixel);
884 	below = (y == height - 1 || off_screen[index_row + width + x] != pixel);
885 	left  = (x == 0 || off_screen[index_row + x - 1] != pixel);
886 	right = (x == width - 1 || off_screen[index_row + x + 1] != pixel);
887 
888 	/*
889 	 * Return true if this pixel lies at the vertex of a rectangular, fillable,
890 	 * area.  That is, if two adjacent neighbors aren't the same color (or if
891 	 * absent -- at the edge of the picture).
892 	 */
893 	return ((above || below) && (left || right));
894 }
895 
gms_graphics_compare_layering_inverted(const void * void_first,const void * void_second)896 int Magnetic::gms_graphics_compare_layering_inverted(const void *void_first,
897 		const void *void_second) {
898 	gms_layering_t *first = (gms_layering_t *) void_first;
899 	gms_layering_t *second = (gms_layering_t *) void_second;
900 
901 	/*
902 	 * Order by complexity first, then by usage, putting largest first.  Some
903 	 * colors may have no vertices at all when doing animation frames, but
904 	 * rendering optimization relies on the first layer that contains no areas
905 	 * to fill halting the rendering loop.  So it's important here that we order
906 	 * indexes so that colors that render complex shapes come first, non-empty,
907 	 * but simpler shaped colors next, and finally all genuinely empty layers.
908 	 */
909 	return second->complexity > first->complexity ? 1 :
910 	       first->complexity > second->complexity ? -1 :
911 	       second->usage > first->usage ? 1 :
912 	       first->usage > second->usage ? -1 : 0;
913 }
914 
gms_graphics_assign_layers(type8 off_screen[],type8 on_screen[],type16 width,type16 height,int layers[],long layer_usage[])915 void Magnetic::gms_graphics_assign_layers(type8 off_screen[], type8 on_screen[],
916 									   type16 width, type16 height,
917 									   int layers[], long layer_usage[]) {
918 	int index, x, y;
919 	long index_row;
920 	gms_layering_t layering[GMS_PALETTE_SIZE];
921 	assert(off_screen && on_screen && layers && layer_usage);
922 
923 	/* Clear initial complexity and usage counts, and set initial colors. */
924 	for (index = 0; index < GMS_PALETTE_SIZE; index++) {
925 		layering[index].complexity = 0;
926 		layering[index].usage = 0;
927 		layering[index].color = index;
928 	}
929 
930 	/*
931 	 * Traverse the image, counting vertices and pixel usage where the pixels
932 	 * differ between the off-screen and on-screen buffers.  Optimize by
933 	 * maintaining an index row to avoid multiplications.
934 	 */
935 	for (y = 0, index_row = 0; y < height; y++, index_row += width) {
936 		for (x = 0; x < width; x++) {
937 			long idx;
938 
939 			/*
940 			 * Get the index for this pixel, and update complexity and usage
941 			 * if off-screen and on-screen pixels differ.
942 			 */
943 			idx = index_row + x;
944 			if (on_screen[idx] != off_screen[idx]) {
945 				if (gms_graphics_is_vertex(off_screen, width, height, x, y))
946 					layering[off_screen[idx]].complexity++;
947 
948 				layering[off_screen[idx]].usage++;
949 			}
950 		}
951 	}
952 
953 	/*
954 	 * Sort counts to form color indexes.  The primary sort is on the shape
955 	 * complexity, and within this, on color usage.
956 	 */
957 	qsort(layering, GMS_PALETTE_SIZE,
958 	      sizeof(*layering), gms_graphics_compare_layering_inverted);
959 
960 	/*
961 	 * Assign a layer to each palette color, and also return the layer usage
962 	 * for each layer.
963 	 */
964 	for (index = 0; index < GMS_PALETTE_SIZE; index++) {
965 		layers[layering[index].color] = index;
966 		layer_usage[index] = layering[index].usage;
967 	}
968 }
969 
gms_graphics_paint_region(winid_t glk_window,glui32 palette[],int layers[],type8 off_screen[],type8 on_screen[],int x,int y,int x_offset,int y_offset,int pixel_size,type16 width,type16 height)970 void Magnetic::gms_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
971 									  type8 off_screen[], type8 on_screen[],
972 									  int x, int y, int x_offset, int y_offset,
973 									  int pixel_size, type16 width, type16 height) {
974 	type8 pixel;
975 	int layer, x_min, x_max, y_min, y_max, x_index, y_index;
976 	long index_row;
977 	assert(glk_window && palette && layers && off_screen && on_screen);
978 
979 	/* Find the color and layer for the initial pixel. */
980 	pixel = off_screen[y * width + x];
981 	layer = layers[pixel];
982 	assert(pixel < GMS_PALETTE_SIZE);
983 
984 	/*
985 	 * Start by finding the extent to which we can pull the x coordinate and
986 	 * still find either invalidated pixels, or pixels in this layer.
987 	 *
988 	 * Use an index row to remove multiplications from the loops.
989 	 */
990 	index_row = y * width;
991 	for (x_min = x; x_min - 1 >= 0; x_min--) {
992 		long index = index_row + x_min - 1;
993 
994 		if (on_screen[index] == off_screen[index]
995 		        && layers[off_screen[index]] != layer)
996 			break;
997 	}
998 	for (x_max = x; x_max + 1 < width; x_max++) {
999 		long index = index_row + x_max + 1;
1000 
1001 		if (on_screen[index] == off_screen[index]
1002 		        && layers[off_screen[index]] != layer)
1003 			break;
1004 	}
1005 
1006 	/*
1007 	 * Now try to stretch the height of the region, by extending the y
1008 	 * coordinate as much as possible too.  Again, we're looking for pixels
1009 	 * that are invalidated or ones in the same layer.  We need to check
1010 	 * across the full width of the current region.
1011 	 *
1012 	 * As above, an index row removes multiplications from the loops.
1013 	 */
1014 	for (y_min = y, index_row = (y - 1) * width;
1015 	        y_min - 1 >= 0; y_min--, index_row -= width) {
1016 		for (x_index = x_min; x_index <= x_max; x_index++) {
1017 			long index = index_row + x_index;
1018 
1019 			if (on_screen[index] == off_screen[index]
1020 			        && layers[off_screen[index]] != layer)
1021 				goto break_y_min;
1022 		}
1023 	}
1024 break_y_min:
1025 
1026 	for (y_max = y, index_row = (y + 1) * width;
1027 	        y_max + 1 < height; y_max++, index_row += width) {
1028 		for (x_index = x_min; x_index <= x_max; x_index++) {
1029 			long index = index_row + x_index;
1030 
1031 			if (on_screen[index] == off_screen[index]
1032 			        && layers[off_screen[index]] != layer)
1033 				goto break_y_max;
1034 		}
1035 	}
1036 break_y_max:
1037 
1038 	/* Fill the region using Glk's rectangle fill. */
1039 	glk_window_fill_rect(glk_window, palette[pixel],
1040 	                           x_min * pixel_size + x_offset,
1041 	                           y_min * pixel_size + y_offset,
1042 	                           (x_max - x_min + 1) * pixel_size,
1043 	                           (y_max - y_min + 1) * pixel_size);
1044 
1045 	/*
1046 	 * Validate each pixel in the reference layer that was rendered by the
1047 	 * rectangle fill.  We don't validate pixels that are not in this layer
1048 	 * (and are by definition in higher layers, as we've validated all lower
1049 	 * layers), since although we colored them, we did it for optimization
1050 	 * reasons, and they're not yet colored correctly.
1051 	 *
1052 	 * Maintain an index row as an optimization to avoid multiplication.
1053 	 */
1054 	index_row = y_min * width;
1055 	for (y_index = y_min; y_index <= y_max; y_index++) {
1056 		for (x_index = x_min; x_index <= x_max; x_index++) {
1057 			long index;
1058 
1059 			/*
1060 			 * Get the index for x_index,y_index.  If the layers match, update
1061 			 * the on-screen buffer.
1062 			 */
1063 			index = index_row + x_index;
1064 			if (layers[off_screen[index]] == layer) {
1065 				assert(off_screen[index] == pixel);
1066 				on_screen[index] = off_screen[index];
1067 			}
1068 		}
1069 
1070 		/* Update row index component on change of y. */
1071 		index_row += width;
1072 	}
1073 }
1074 #endif
1075 
gms_graphics_paint_everything(winid_t glk_window,glui32 palette[],type8 off_screen[],int x_offset,int y_offset,type16 width,type16 height)1076 void Magnetic::gms_graphics_paint_everything(winid_t glk_window,
1077 		glui32 palette[], type8 off_screen[], int x_offset, int y_offset,
1078 		type16 width, type16 height) {
1079 	type16 x, y;
1080 	glui32 pixel;
1081 
1082 	Graphics::ManagedSurface s(width, height, _screen->format);
1083 
1084 	for (y = 0; y < height; y++) {
1085 		uint16 *lineP = (uint16 *)s.getBasePtr(0, y);
1086 
1087 		for (x = 0; x < width; ++x, ++lineP) {
1088 			pixel = palette[off_screen[y * width + x]];
1089 			*lineP = pixel;
1090 		}
1091 	}
1092 
1093 	glk_image_draw_scaled(glk_window, s, (uint)-1, x_offset, y_offset,
1094 		width * GMS_GRAPHICS_PIXEL, height * GMS_GRAPHICS_PIXEL);
1095 }
1096 
gms_graphics_timeout()1097 void Magnetic::gms_graphics_timeout() {
1098 	static glui32 palette[GMS_PALETTE_SIZE];   /* Precomputed Glk palette */
1099 #ifndef GARGLK
1100 	static int layers[GMS_PALETTE_SIZE];       /* Assigned image layers */
1101 	static long layer_usage[GMS_PALETTE_SIZE]; /* Image layer occupancies */
1102 #endif
1103 
1104 	static int deferred_repaint = false;       /* Local delayed repaint flag */
1105 	static int ignore_counter;                 /* Count of calls ignored */
1106 
1107 	static int x_offset, y_offset;             /* Point plot offsets */
1108 	static int yield_counter;                  /* Yields in rendering */
1109 #ifndef GARGLK
1110 	static int saved_layer;                    /* Saved current layer */
1111 	static int saved_x, saved_y;               /* Saved x,y coord */
1112 	static int total_regions;                  /* Debug statistic */
1113 #endif
1114 
1115 	type8 *on_screen;                          /* On-screen image buffer */
1116 	type8 *off_screen;                         /* Off-screen image buffer */
1117 	long picture_size;                         /* Picture size in pixels */
1118 //  int layer;                                 /* Image layer iterator */
1119 //  int x, y;                                  /* Image iterators */
1120 //  int regions;                               /* Count of regions painted */
1121 
1122 	/* Ignore the call if the current graphics state is inactive. */
1123 	if (!gms_graphics_active)
1124 		return;
1125 	assert(gms_graphics_window);
1126 
1127 	/*
1128 	 * On detecting a repaint request, note the flag in a local static variable,
1129 	 * then set up a graphics delay to wait until, hopefully, the resize, if
1130 	 * that's what caused it, is complete, and return.  This makes resizing the
1131 	 * window a lot smoother, since it prevents unnecessary region paints where
1132 	 * we are receiving consecutive Glk arrange or redraw events.
1133 	 */
1134 	if (gms_graphics_repaint) {
1135 		deferred_repaint = true;
1136 		gms_graphics_repaint = false;
1137 		ignore_counter = GMS_GRAPHICS_REPAINT_WAIT - 1;
1138 		return;
1139 	}
1140 
1141 	/*
1142 	 * If asked to ignore a given number of calls, decrement the ignore counter
1143 	 * and return having done nothing more.  This lets us delay graphics
1144 	 * operations by a number of timeouts, providing animation timing and
1145 	 * partial protection from resize event "storms".
1146 	 *
1147 	 * Note -- to wait for N timeouts, set the count of timeouts to be ignored
1148 	 * to N-1.
1149 	 */
1150 	assert(ignore_counter >= 0);
1151 	if (ignore_counter > 0) {
1152 		ignore_counter--;
1153 		return;
1154 	}
1155 
1156 	/* Calculate the picture size, and synchronize screen buffer pointers. */
1157 	picture_size = gms_graphics_width * gms_graphics_height;
1158 	off_screen = gms_graphics_off_screen;
1159 	on_screen = gms_graphics_on_screen;
1160 
1161 	/*
1162 	 * If we received a new picture, set up the local static variables for that
1163 	 * picture -- decide on gamma correction, convert the color palette, and
1164 	 * initialize the off_screen buffer to be the base picture.
1165 	 */
1166 	if (gms_graphics_new_picture) {
1167 		/* Initialize the off_screen buffer to be a copy of the base picture. */
1168 		free(off_screen);
1169 		off_screen = (type8 *)gms_malloc(picture_size * sizeof(*off_screen));
1170 		memcpy(off_screen, gms_graphics_bitmap,
1171 		       picture_size * sizeof(*off_screen));
1172 
1173 		/* Note the buffer for freeing on cleanup. */
1174 		gms_graphics_off_screen = off_screen;
1175 
1176 		/*
1177 		 * If the picture is animated, apply the first animation frames now.
1178 		 * This is important, since they form an intrinsic part of the first
1179 		 * displayed image (in type2 animation cases, perhaps _all_ of the
1180 		 * first displayed image).
1181 		 */
1182 		if (gms_graphics_animated) {
1183 			gms_graphics_animate(off_screen,
1184 			                     gms_graphics_width, gms_graphics_height);
1185 		}
1186 
1187 		/*
1188 		 * Select a suitable gamma for the picture, taking care to use the
1189 		 * off-screen buffer.
1190 		 */
1191 		gms_graphics_current_gamma =
1192 		    gms_graphics_select_gamma(off_screen,
1193 		                              gms_graphics_width,
1194 		                              gms_graphics_height,
1195 		                              gms_graphics_palette);
1196 
1197 		/*
1198 		 * Pre-convert all the picture palette colors into their corresponding
1199 		 * Glk colors.
1200 		 */
1201 		gms_graphics_convert_palette(gms_graphics_palette,
1202 		                             gms_graphics_current_gamma, palette);
1203 
1204 		/* Save the color count for possible queries later. */
1205 		gms_graphics_count_colors(off_screen,
1206 		                          gms_graphics_width, gms_graphics_height,
1207 		                          &gms_graphics_color_count, NULL);
1208 	}
1209 
1210 	/*
1211 	 * For a new picture, or a repaint of a prior one, calculate new values for
1212 	 * the x and y offsets used to draw image points, and set the on-screen
1213 	 * buffer to an unused pixel value, in effect invalidating all on-screen
1214 	 * data.  Also, reset the saved image scan coordinates so that we scan for
1215 	 * unpainted pixels from top left starting at layer zero, and clear the
1216 	 * graphics window.
1217 	 */
1218 	if (gms_graphics_new_picture || deferred_repaint) {
1219 		/*
1220 		 * Calculate the x and y offset to center the picture in the graphics
1221 		 * window.
1222 		 */
1223 		gms_graphics_position_picture(gms_graphics_window,
1224 		                              GMS_GRAPHICS_PIXEL,
1225 		                              gms_graphics_width, gms_graphics_height,
1226 		                              &x_offset, &y_offset);
1227 
1228 		/*
1229 		 * Reset all on-screen pixels to an unused value, guaranteed not to
1230 		 * match any in a real picture.  This forces all pixels to be repainted
1231 		 * on a buffer/on-screen comparison.
1232 		 */
1233 		free(on_screen);
1234 		on_screen = (type8 *)gms_malloc(picture_size * sizeof(*on_screen));
1235 		memset(on_screen, GMS_GRAPHICS_UNUSED_PIXEL,
1236 		       picture_size * sizeof(*on_screen));
1237 
1238 		/* Note the buffer for freeing on cleanup. */
1239 		gms_graphics_on_screen = on_screen;
1240 
1241 		/*
1242 		 * Assign new layers to the current image.  This sorts colors by usage
1243 		 * and puts the most used colors in the lower layers.  It also hands us
1244 		 * a count of pixels in each layer, useful for knowing when to stop
1245 		 * scanning for layers in the rendering loop.
1246 		 */
1247 #ifndef GARGLK
1248 		gms_graphics_assign_layers(off_screen, on_screen,
1249 		                           gms_graphics_width, gms_graphics_height,
1250 		                           layers, layer_usage);
1251 
1252 		saved_layer = 0;
1253 		saved_x = 0;
1254 		saved_y = 0;
1255 		total_regions = 0;
1256 #endif
1257 
1258 		/* Clear the graphics window. */
1259 		gms_graphics_clear_and_border(gms_graphics_window,
1260 		                              x_offset, y_offset,
1261 		                              GMS_GRAPHICS_PIXEL,
1262 		                              gms_graphics_width, gms_graphics_height);
1263 
1264 		/* Start a fresh picture rendering pass. */
1265 		yield_counter = 0;
1266 
1267 		/* Clear the new picture and deferred repaint flags. */
1268 		gms_graphics_new_picture = false;
1269 		deferred_repaint = false;
1270 	}
1271 
1272 #ifndef GARGLK
1273 	/*
1274 	 * Make a portion of an image pass, from lower to higher image layers,
1275 	 * scanning for invalidated pixels that are in the current image layer we
1276 	 * are painting.  Each invalidated pixel gives rise to a region paint,
1277 	 * which equates to one Glk rectangle fill.
1278 	 *
1279 	 * When the limit on regions is reached, save the current image pass layer
1280 	 * and coordinates, and yield control to the main game playing code by
1281 	 * returning.  On the next call, pick up where we left off.
1282 	 *
1283 	 * As an optimization, we can leave the loop on the first empty layer we
1284 	 * encounter.  Since layers are ordered by complexity and color usage, all
1285 	 * layers higher than the first unused one will also be empty, so we don't
1286 	 * need to scan them.
1287 	 */
1288 	regions = 0;
1289 	for (layer = saved_layer;
1290 	        layer < GMS_PALETTE_SIZE && layer_usage[layer] > 0; layer++) {
1291 		long index_row;
1292 
1293 		/*
1294 		 * As an optimization to avoid multiplications in the loop, maintain a
1295 		 * separate index row.
1296 		 */
1297 		index_row = saved_y * gms_graphics_width;
1298 		for (y = saved_y; y < gms_graphics_height; y++) {
1299 			for (x = saved_x; x < gms_graphics_width; x++) {
1300 				long index;
1301 
1302 				/* Get the index for this pixel. */
1303 				index = index_row + x;
1304 				assert(index < picture_size * sizeof(*off_screen));
1305 
1306 				/*
1307 				 * Ignore pixels not in the current layer, and pixels not
1308 				 * currently invalid (that is, ones whose on-screen represen-
1309 				 * tation matches the off-screen buffer).
1310 				 */
1311 				if (layers[off_screen[index]] == layer
1312 				        && on_screen[index] != off_screen[index]) {
1313 					/*
1314 					 * Rather than painting just one pixel, here we try to
1315 					 * paint the maximal region we can for the layer of the
1316 					 * given pixel.
1317 					 */
1318 					gms_graphics_paint_region(gms_graphics_window,
1319 					                          palette, layers,
1320 					                          off_screen, on_screen,
1321 					                          x, y, x_offset, y_offset,
1322 					                          GMS_GRAPHICS_PIXEL,
1323 					                          gms_graphics_width,
1324 					                          gms_graphics_height);
1325 
1326 					/*
1327 					 * Increment count of regions handled, and yield, by
1328 					 * returning, if the limit on paint regions is reached.
1329 					 * Before returning, save the current layer and scan
1330 					 * coordinates, so we can pick up here on the next call.
1331 					 */
1332 					regions++;
1333 					if (regions >= GMS_REPAINT_LIMIT) {
1334 						yield_counter++;
1335 						saved_layer = layer;
1336 						saved_x = x;
1337 						saved_y = y;
1338 						total_regions += regions;
1339 						return;
1340 					}
1341 				}
1342 			}
1343 
1344 			/* Reset the saved x coordinate on y increment. */
1345 			saved_x = 0;
1346 
1347 			/* Update the index row on change of y. */
1348 			index_row += gms_graphics_width;
1349 		}
1350 
1351 		/* Reset the saved y coordinate on layer change. */
1352 		saved_y = 0;
1353 	}
1354 
1355 	/*
1356 	 * If we reach this point, then we didn't get to the limit on regions
1357 	 * painted on this pass.  In that case, we've finished rendering the
1358 	 * image.
1359 	 */
1360 	assert(regions < GMS_REPAINT_LIMIT);
1361 	total_regions += regions;
1362 
1363 #else
1364 	gms_graphics_paint_everything
1365 	(gms_graphics_window,
1366 	 palette, off_screen,
1367 	 x_offset, y_offset,
1368 	 gms_graphics_width,
1369 	 gms_graphics_height);
1370 #endif
1371 
1372 	/*
1373 	 * If animated, and if animations are enabled, handle further animation
1374 	 * frames, if any.
1375 	 */
1376 	if (gms_animation_enabled && gms_graphics_animated) {
1377 		int more_animation;
1378 
1379 		/*
1380 		 * Reset the off-screen buffer to a copy of the base picture.  This is
1381 		 * the correct state for applying animation frames.
1382 		 */
1383 		memcpy(off_screen, gms_graphics_bitmap,
1384 		       picture_size * sizeof(*off_screen));
1385 
1386 		/*
1387 		 * Apply any further animations.  If none, then stop the graphics
1388 		 * "thread" and return.  There's no more to be done until something
1389 		 * restarts us.
1390 		 */
1391 		more_animation = gms_graphics_animate(off_screen,
1392 		                                      gms_graphics_width,
1393 		                                      gms_graphics_height);
1394 		if (!more_animation) {
1395 			/*
1396 			 * There's one extra wrinkle here.  The base picture we've just put
1397 			 * into the off-screen buffer isn't really complete (and for type2
1398 			 * animations, might be pure garbage), so if we happen to get a
1399 			 * repaint after an animation has ended, the off-screen data we'll
1400 			 * be painting could well look wrong.
1401 			 *
1402 			 * So... here we want to set the off-screen buffer to contain the
1403 			 * final animation frame.  Fortunately, we still have it in the
1404 			 * on-screen buffer.
1405 			 */
1406 			memcpy(off_screen, on_screen, picture_size * sizeof(*off_screen));
1407 			gms_graphics_stop();
1408 			return;
1409 		}
1410 
1411 		/*
1412 		 * Re-assign layers based on animation changes to the off-screen
1413 		 * buffer.
1414 		 */
1415 #ifndef GARGLK
1416 		gms_graphics_assign_layers(off_screen, on_screen,
1417 		                           gms_graphics_width, gms_graphics_height,
1418 		                           layers, layer_usage);
1419 #endif
1420 
1421 		/*
1422 		 * Set up an animation wait, adjusted here by the number of times we
1423 		 * had to yield while rendering, as we're now that late with animations,
1424 		 * and capped at zero, as we can't do anything to compensate for being
1425 		 * too late.  In practice, we're running too close to the edge to have
1426 		 * much of an effect here, but nevertheless...
1427 		 */
1428 		ignore_counter = GMS_GRAPHICS_ANIMATION_WAIT - 1;
1429 		if (yield_counter > ignore_counter)
1430 			ignore_counter = 0;
1431 		else
1432 			ignore_counter -= yield_counter;
1433 
1434 		/* Start a fresh picture rendering pass. */
1435 		yield_counter = 0;
1436 #ifndef GARGLK
1437 		saved_layer = 0;
1438 		saved_x = 0;
1439 		saved_y = 0;
1440 		total_regions = 0;
1441 #endif
1442 	} else {
1443 		/*
1444 		 * Not an animated picture, so just stop graphics, as again, there's
1445 		 * no more to be done until something restarts us.
1446 		 */
1447 		gms_graphics_stop();
1448 	}
1449 }
1450 
ms_showpic(type32 picture,type8 mode)1451 void Magnetic::ms_showpic(type32 picture, type8 mode) {
1452 	type8 *bitmap, animated;
1453 	type16 width, height, palette[GMS_PALETTE_SIZE];
1454 	long picture_bytes;
1455 	glui32 crc;
1456 
1457 	/* See if the mode indicates no graphics. */
1458 	if (mode == 0) {
1459 		/* Note that the interpreter turned graphics off. */
1460 		gms_graphics_interpreter = false;
1461 
1462 		/*
1463 		 * If we are currently displaying the graphics window, stop any update
1464 		 * "thread" and turn off graphics.
1465 		 */
1466 		if (gms_graphics_enabled && gms_graphics_are_displayed()) {
1467 			gms_graphics_stop();
1468 			gms_graphics_close();
1469 		}
1470 
1471 		/* Nothing more to do now graphics are off. */
1472 		return;
1473 	}
1474 
1475 	/* Note that the interpreter turned graphics on. */
1476 	gms_graphics_interpreter = true;
1477 
1478 	/*
1479 	 * Obtain the image details for the requested picture.  The call returns
1480 	 * NULL if there's a problem with the picture.
1481 	 */
1482 	bitmap = ms_extract(picture, &width, &height, palette, &animated);
1483 	if (!bitmap)
1484 		return;
1485 
1486 	/* Note the last thing passed to ms_extract, in case of graphics restarts. */
1487 	gms_graphics_picture = picture;
1488 
1489 	/* Calculate the picture size, and the CRC for the bitmap data. */
1490 	picture_bytes = width * height * sizeof(*bitmap);
1491 	crc = gms_get_buffer_crc(bitmap, picture_bytes);
1492 
1493 	/*
1494 	 * If there is no change of picture, we might be able to largely ignore the
1495 	 * call.  Check for a change, and if we don't see one, and if graphics are
1496 	 * enabled and being displayed, we can safely ignore the call.
1497 	 */
1498 	if (width == gms_graphics_width
1499 	        && height == gms_graphics_height
1500 	        && crc == pic_current_crc
1501 	        && gms_graphics_enabled && gms_graphics_are_displayed())
1502 		return;
1503 
1504 	/*
1505 	 * We know now that this is either a genuine change of picture, or graphics
1506 	 * were off and have been turned on.  So, record picture details, ensure
1507 	 * graphics is on, set the flags, and start the background graphics update.
1508 	 */
1509 
1510 	/*
1511 	 * Save the picture details for the update code.  Here we take a complete
1512 	 * local copy of the bitmap, since the interpreter core may reuse part of
1513 	 * its memory for animations.
1514 	 */
1515 	free(gms_graphics_bitmap);
1516 	gms_graphics_bitmap = (type8 *)gms_malloc(picture_bytes);
1517 	memcpy(gms_graphics_bitmap, bitmap, picture_bytes);
1518 	gms_graphics_width = width;
1519 	gms_graphics_height = height;
1520 	memcpy(gms_graphics_palette, palette, sizeof(palette));
1521 	gms_graphics_animated = animated;
1522 
1523 	/* Retain the new picture CRC. */
1524 	pic_current_crc = crc;
1525 
1526 	/*
1527 	 * If graphics are enabled, ensure the window is displayed, set the
1528 	 * appropriate flags, and start graphics update.  If they're not enabled,
1529 	 * the picture details will simply stick around in module variables until
1530 	 * they are required.
1531 	 */
1532 	if (gms_graphics_enabled) {
1533 		/*
1534 		 * Ensure graphics on, then set the new picture flag and start the
1535 		 * updating "thread".
1536 		 */
1537 		if (gms_graphics_open()) {
1538 			gms_graphics_new_picture = true;
1539 			gms_graphics_start();
1540 		}
1541 	}
1542 }
1543 
gms_graphics_get_picture_details(int * width,int * height,int * is_animated)1544 int Magnetic::gms_graphics_get_picture_details(int *width, int *height, int *is_animated) {
1545 	if (gms_graphics_picture_is_available()) {
1546 		if (width)
1547 			*width = gms_graphics_width;
1548 		if (height)
1549 			*height = gms_graphics_height;
1550 		if (is_animated)
1551 			*is_animated = gms_graphics_animated;
1552 
1553 		return true;
1554 	}
1555 
1556 	return false;
1557 }
1558 
gms_graphics_get_rendering_details(const char ** gamma,int * color_count,int * is_active)1559 int Magnetic::gms_graphics_get_rendering_details(const char **gamma,
1560 		int *color_count, int *is_active) {
1561 	if (gms_graphics_enabled && gms_graphics_are_displayed()) {
1562 		/*
1563 		 * Return the string representing the gamma correction.  If racing
1564 		 * with timeouts, we might return the gamma for the last picture.
1565 		 */
1566 		if (gamma) {
1567 			assert(gms_graphics_current_gamma);
1568 			*gamma = gms_graphics_current_gamma->level;
1569 		}
1570 
1571 		/*
1572 		 * Return the color count noted by timeouts on the first timeout
1573 		 * following a new picture.  Again, we might return the one for
1574 		 * the prior picture.
1575 		 */
1576 		if (color_count)
1577 			*color_count = gms_graphics_color_count;
1578 
1579 		/* Return graphics active flag. */
1580 		if (is_active)
1581 			*is_active = gms_graphics_active;
1582 
1583 		return true;
1584 	}
1585 
1586 	return false;
1587 }
1588 
gms_graphics_interpreter_enabled()1589 int Magnetic::gms_graphics_interpreter_enabled() {
1590 	return gms_graphics_interpreter;
1591 }
1592 
gms_graphics_cleanup()1593 void Magnetic::gms_graphics_cleanup() {
1594 	free(gms_graphics_bitmap);
1595 	gms_graphics_bitmap = NULL;
1596 	free(gms_graphics_off_screen);
1597 	gms_graphics_off_screen = NULL;
1598 	free(gms_graphics_on_screen);
1599 	gms_graphics_on_screen = NULL;
1600 
1601 	gms_graphics_animated = false;
1602 	gms_graphics_picture = 0;
1603 }
1604 
1605 /*---------------------------------------------------------------------*/
1606 /*  Glk port status line functions                                     */
1607 /*---------------------------------------------------------------------*/
1608 
ms_statuschar(type8 c)1609 void Magnetic::ms_statuschar(type8 c) {
1610 	static char buffer_[GMS_STATBUFFER_LENGTH];
1611 	static int length = 0;
1612 
1613 	/*
1614 	 * If the status character is newline, transfer locally buffered data to
1615 	 * the common buffer, empty the local buffer; otherwise, if space permits,
1616 	 * buffer the character.
1617 	 */
1618 	if (c == '\n') {
1619 		memcpy(gms_status_buffer, buffer_, length);
1620 		gms_status_length = length;
1621 
1622 		length = 0;
1623 	} else {
1624 		if (length < (int)sizeof(buffer_))
1625 			buffer_[length++] = c;
1626 	}
1627 }
1628 
gms_status_update()1629 void Magnetic::gms_status_update() {
1630 	uint width, height;
1631 	int index;
1632 	assert(gms_status_window);
1633 
1634 	glk_window_get_size(gms_status_window, &width, &height);
1635 	if (height > 0) {
1636 		glk_window_clear(gms_status_window);
1637 		glk_window_move_cursor(gms_status_window, 0, 0);
1638 		glk_set_window(gms_status_window);
1639 
1640 		glk_set_style(style_User1);
1641 		for (index = 0; index < (int)width; index++)
1642 			glk_put_char(' ');
1643 		glk_window_move_cursor(gms_status_window, 1, 0);
1644 
1645 		if (gms_status_length > 0) {
1646 			/*
1647 			 * Output each character from the status line buffer.  If the
1648 			 * character is Tab, position the cursor to eleven characters shy
1649 			 * of the status window right.
1650 			 */
1651 			for (index = 0; index < gms_status_length; index++) {
1652 				if (gms_status_buffer[index] == '\t')
1653 					glk_window_move_cursor(gms_status_window, width - 11, 0);
1654 				else
1655 					glk_put_char(gms_status_buffer[index]);
1656 			}
1657 		} else {
1658 			const char *game_name;
1659 
1660 			/*
1661 			 * We have no status line to display, so print the game's name, or
1662 			 * a standard message if unable to identify the game.  Having no
1663 			 * status line is common with Magnetic Windows games, which don't,
1664 			 * in general, seem to use one.
1665 			 */
1666 			game_name = gms_gameid_get_game_name();
1667 			glk_put_string(game_name ? game_name : "ScummVM Magnetic version 2.3");
1668 		}
1669 
1670 		glk_set_window(gms_main_window);
1671 	}
1672 }
1673 
gms_status_print()1674 void Magnetic::gms_status_print() {
1675 	static char buffer_[GMS_STATBUFFER_LENGTH];
1676 	static int length = 0;
1677 
1678 	int index, column;
1679 
1680 	/*
1681 	 * Do nothing if there is no status line to print, or if the status
1682 	 * line hasn't changed since last printed.
1683 	 */
1684 	if (gms_status_length == 0
1685 	        || (gms_status_length == length
1686 	            && strncmp(buffer_, gms_status_buffer, length)) == 0)
1687 		return;
1688 
1689 	/* Set fixed width font to try to preserve status line formatting. */
1690 	glk_set_style(style_Preformatted);
1691 
1692 	/* Bracket, and output the status line buffer_. */
1693 	glk_put_string("[ ");
1694 	column = 1;
1695 	for (index = 0; index < gms_status_length; index++) {
1696 		/*
1697 		 * If the character is Tab, position the cursor to eleven characters
1698 		 * shy of the right edge.  In the absence of the real window dimensions,
1699 		 * we'll select 74 characters, which gives us a 78 character status
1700 		 * line; pretty standard.
1701 		 */
1702 		if (gms_status_buffer[index] == '\t') {
1703 			while (column <= GMS_DEFAULT_STATUS_WIDTH - 11) {
1704 				glk_put_char(' ');
1705 				column++;
1706 			}
1707 		} else {
1708 			glk_put_char(gms_status_buffer[index]);
1709 			column++;
1710 		}
1711 	}
1712 
1713 	while (column <= GMS_DEFAULT_STATUS_WIDTH) {
1714 		glk_put_char(' ');
1715 		column++;
1716 	}
1717 	glk_put_string(" ]\n");
1718 
1719 	/* Save the details of the printed status buffer_. */
1720 	memcpy(buffer_, gms_status_buffer, gms_status_length);
1721 	length = gms_status_length;
1722 }
1723 
gms_status_notify()1724 void Magnetic::gms_status_notify() {
1725 	if (gms_status_window)
1726 		gms_status_update();
1727 	else
1728 		gms_status_print();
1729 }
1730 
gms_status_redraw()1731 void Magnetic::gms_status_redraw() {
1732 	if (gms_status_window) {
1733 		winid_t parent;
1734 
1735 		/*
1736 		 * Rearrange the status window, without changing its actual arrangement
1737 		 * in any way.  This is a hack to work round incorrect window repainting
1738 		 * in Xglk; it forces a complete repaint of affected windows on Glk
1739 		 * window resize and arrange events, and works in part because Xglk
1740 		 * doesn't check for actual arrangement changes in any way before
1741 		 * invalidating its windows.  The hack should be harmless to Glk
1742 		 * libraries other than Xglk, moreover, we're careful to activate it
1743 		 * only on resize and arrange events.
1744 		 */
1745 		parent = glk_window_get_parent(gms_status_window);
1746 		glk_window_set_arrangement(parent,
1747 		                                 winmethod_Above | winmethod_Fixed, 1, NULL);
1748 
1749 		gms_status_update();
1750 	}
1751 }
1752 
1753 /*---------------------------------------------------------------------*/
1754 /*  Glk port output functions                                          */
1755 /*---------------------------------------------------------------------*/
1756 
gms_output_register_help_request()1757 void Magnetic::gms_output_register_help_request() {
1758 	gms_help_requested = true;
1759 }
1760 
gms_output_silence_help_hints()1761 void Magnetic::gms_output_silence_help_hints() {
1762 	gms_help_hints_silenced = true;
1763 }
1764 
gms_output_provide_help_hint()1765 void Magnetic::gms_output_provide_help_hint() {
1766 	if (gms_help_requested && !gms_help_hints_silenced) {
1767 		glk_set_style(style_Emphasized);
1768 		glk_put_string("[Try 'glk help' for help on special interpreter"
1769 		                     " commands]\n");
1770 
1771 		gms_help_requested = false;
1772 		glk_set_style(style_Normal);
1773 	}
1774 }
1775 
gms_game_prompted()1776 int Magnetic::gms_game_prompted() {
1777 	int result;
1778 
1779 	result = gms_output_prompt;
1780 	gms_output_prompt = false;
1781 
1782 	return result;
1783 }
1784 
gms_detect_game_prompt()1785 void Magnetic::gms_detect_game_prompt() {
1786 	int index;
1787 
1788 	gms_output_prompt = false;
1789 
1790 	/*
1791 	 * Search for a prompt across any last unterminated buffered line; a prompt
1792 	 * is any non-space character on that line.
1793 	 */
1794 	for (index = gms_output_length - 1;
1795 	        index >= 0 && gms_output_buffer[index] != '\n'; index--) {
1796 		if (gms_output_buffer[index] != ' ') {
1797 			gms_output_prompt = true;
1798 			break;
1799 		}
1800 	}
1801 }
1802 
gms_output_delete()1803 void Magnetic::gms_output_delete() {
1804 	free(gms_output_buffer);
1805 	gms_output_buffer = NULL;
1806 	gms_output_allocation = gms_output_length = 0;
1807 }
1808 
gms_output_flush()1809 void Magnetic::gms_output_flush() {
1810 	assert(glk_stream_get_current());
1811 
1812 	if (gms_output_length > 0) {
1813 		/*
1814 		 * See if the game issued a standard prompt, then print the buffer to
1815 		 * the main window.  If providing a help hint, position that before
1816 		 * the game's prompt (if any).
1817 		 */
1818 		gms_detect_game_prompt();
1819 		glk_set_style(style_Normal);
1820 
1821 		if (gms_output_prompt) {
1822 			int index;
1823 
1824 			for (index = gms_output_length - 1;
1825 			        index >= 0 && gms_output_buffer[index] != '\n';)
1826 				index--;
1827 
1828 			glk_put_buffer(gms_output_buffer, index + 1);
1829 			gms_output_provide_help_hint();
1830 			glk_put_buffer(gms_output_buffer + index + 1,
1831 			                     gms_output_length - index - 1);
1832 		} else {
1833 			glk_put_buffer(gms_output_buffer, gms_output_length);
1834 			gms_output_provide_help_hint();
1835 		}
1836 
1837 		gms_output_delete();
1838 	}
1839 }
1840 
ms_putchar(type8 c)1841 void Magnetic::ms_putchar(type8 c) {
1842 	int bytes;
1843 	assert(gms_output_length <= gms_output_allocation);
1844 
1845 	/*
1846 	 * See if the character is a backspace.  Magnetic Scrolls games can send
1847 	 * backspace characters to the display.  We'll need to handle such
1848 	 * characters specially, by taking the last character out of the buffer.
1849 	 */
1850 	if (c == '\b') {
1851 		if (gms_output_length > 0)
1852 			gms_output_length--;
1853 
1854 		return;
1855 	}
1856 
1857 	/* Grow the output buffer if necessary, then add the character. */
1858 	for (bytes = gms_output_allocation; bytes < gms_output_length + 1;)
1859 		bytes = bytes == 0 ? 1 : bytes << 1;
1860 
1861 	if (bytes > gms_output_allocation) {
1862 		gms_output_buffer = (char *)gms_realloc(gms_output_buffer, bytes);
1863 		gms_output_allocation = bytes;
1864 	}
1865 
1866 	gms_output_buffer[gms_output_length++] = c;
1867 }
1868 
gms_styled_string(glui32 style,const char * message)1869 void Magnetic::gms_styled_string(glui32 style, const char *message) {
1870 	assert(message);
1871 
1872 	glk_set_style(style);
1873 	glk_put_string(message);
1874 	glk_set_style(style_Normal);
1875 }
1876 
gms_styled_char(glui32 style,char c)1877 void Magnetic::gms_styled_char(glui32 style, char c) {
1878 	char str[2];
1879 
1880 	str[0] = c;
1881 	str[1] = '\0';
1882 	gms_styled_string(style, str);
1883 }
1884 
gms_standout_string(const char * message)1885 void Magnetic::gms_standout_string(const char *message) {
1886 	gms_styled_string(style_Emphasized, message);
1887 }
1888 
gms_normal_string(const char * message)1889 void Magnetic::gms_normal_string(const char *message) {
1890 	gms_styled_string(style_Normal, message);
1891 }
1892 
gms_normal_char(char c)1893 void Magnetic::gms_normal_char(char c) {
1894 	gms_styled_char(style_Normal, c);
1895 }
1896 
gms_header_string(const char * message)1897 void Magnetic::gms_header_string(const char *message) {
1898 	gms_styled_string(style_Header, message);
1899 }
1900 
gms_banner_string(const char * message)1901 void Magnetic::gms_banner_string(const char *message) {
1902 	gms_styled_string(style_Subheader, message);
1903 }
1904 
ms_flush()1905 void Magnetic::ms_flush() {
1906 }
1907 
1908 /*---------------------------------------------------------------------*/
1909 /*  Glk port hint functions                                            */
1910 /*---------------------------------------------------------------------*/
1911 
gms_get_hint_max_node(const struct ms_hint hints_[],type16 node)1912 type16 Magnetic::gms_get_hint_max_node(const struct ms_hint hints_[], type16 node) {
1913 	const struct ms_hint *hint;
1914 	int index;
1915 	type16 max_node;
1916 	assert(hints_);
1917 
1918 	hint = hints_ + node;
1919 	max_node = node;
1920 
1921 	switch (hint->nodetype) {
1922 	case GMS_HINT_TYPE_TEXT:
1923 		break;
1924 
1925 	case GMS_HINT_TYPE_FOLDER:
1926 		/*
1927 		 * Recursively find the maximum node reference for each link, and keep
1928 		 * the largest value found.
1929 		 */
1930 		for (index = 0; index < hint->elcount; index++) {
1931 			type16 link_max;
1932 
1933 			link_max = gms_get_hint_max_node(hints_, hint->links[index]);
1934 			if (link_max > max_node)
1935 				max_node = link_max;
1936 		}
1937 		break;
1938 
1939 	default:
1940 		gms_fatal("GLK: Invalid hints_ node type encountered");
1941 		glk_exit();
1942 	}
1943 
1944 	/*
1945 	 * Return the largest node reference found, capped to avoid overlapping the
1946 	 * special end-hints_ value.
1947 	 */
1948 	return max_node < GMS_HINTS_DONE ? max_node : GMS_HINTS_DONE - 1;
1949 }
1950 
gms_get_hint_content(const struct ms_hint hints_[],type16 node,int number)1951 const char *Magnetic::gms_get_hint_content(const struct ms_hint hints_[], type16 node, int number) {
1952 	const struct ms_hint *hint;
1953 	int offset, index;
1954 	assert(hints_);
1955 
1956 	hint = hints_ + node;
1957 
1958 	/* Run through content until 'number' strings found. */
1959 	offset = 0;
1960 	for (index = 0; index < number; index++)
1961 		offset += strlen(hint->content + offset) + 1;
1962 
1963 	/* Return the start of the number'th string encountered. */
1964 	return hint->content + offset;
1965 }
1966 
gms_get_hint_topic(const ms_hint hints_[],type16 node)1967 const char *Magnetic::gms_get_hint_topic(const ms_hint hints_[], type16 node) {
1968 	assert(hints_);
1969 
1970 	if (node == GMS_HINT_ROOT_NODE) {
1971 		/* If the node is the root node, return a generic string. */
1972 		return GMS_GENERIC_TOPIC;
1973 	} else {
1974 		type16 parent;
1975 		int index;
1976 		const char *topic;
1977 
1978 		/*
1979 		 * Search the parent for a link to node, and use that as the hint topic;
1980 		 * NULL if none found.
1981 		 */
1982 		parent = hints_[node].parent;
1983 
1984 		topic = NULL;
1985 		for (index = 0; index < hints_[parent].elcount; index++) {
1986 			if (hints_[parent].links[index] == node) {
1987 				topic = gms_get_hint_content(hints_, parent, index);
1988 				break;
1989 			}
1990 		}
1991 
1992 		return topic ? topic : GMS_GENERIC_TOPIC;
1993 	}
1994 }
1995 
gms_hint_open()1996 int Magnetic::gms_hint_open() {
1997 	if (!gms_hint_menu_window) {
1998 		assert(!gms_hint_text_window);
1999 
2000 		/*
2001 		 * Open the hint menu window.  The initial size is two lines, but we'll
2002 		 * change this later to suit the hint.
2003 		 */
2004 		gms_hint_menu_window = glk_window_open(gms_main_window,
2005 		                       winmethod_Above | winmethod_Fixed,
2006 		                       2, wintype_TextGrid, 0);
2007 		if (!gms_hint_menu_window)
2008 			return false;
2009 
2010 		/*
2011 		 * Now open the hints text window.  This is set to be 100% of the size
2012 		 * of the main window, so should cover what remains of it completely.
2013 		 */
2014 		gms_hint_text_window = glk_window_open(gms_main_window,
2015 		                       winmethod_Above
2016 		                       | winmethod_Proportional,
2017 		                       100, wintype_TextBuffer, 0);
2018 		if (!gms_hint_text_window) {
2019 			glk_window_close(gms_hint_menu_window, NULL);
2020 			gms_hint_menu_window = NULL;
2021 			return false;
2022 		}
2023 	}
2024 
2025 	return true;
2026 }
2027 
gms_hint_close()2028 void Magnetic::Magnetic::gms_hint_close() {
2029 	if (gms_hint_menu_window) {
2030 		assert(gms_hint_text_window);
2031 
2032 		glk_window_close(gms_hint_menu_window, NULL);
2033 		gms_hint_menu_window = NULL;
2034 		glk_window_close(gms_hint_text_window, NULL);
2035 		gms_hint_text_window = NULL;
2036 	}
2037 }
2038 
gms_hint_windows_available()2039 int Magnetic::gms_hint_windows_available() {
2040 	return (gms_hint_menu_window && gms_hint_text_window);
2041 }
2042 
gms_hint_menu_print(int line,int column,const char * string_,glui32 width,glui32 height)2043 void Magnetic::gms_hint_menu_print(int line, int column, const char *string_,
2044 		glui32 width, glui32 height) {
2045 	assert(string_);
2046 
2047 	/* Ignore the call if the text position is outside the window. */
2048 	if (!(line > (int)height || column > (int)width)) {
2049 		if (gms_hint_windows_available()) {
2050 			int posn, index;
2051 
2052 			glk_window_move_cursor(gms_hint_menu_window, column, line);
2053 			glk_set_window(gms_hint_menu_window);
2054 
2055 			/* Write until the end of the string_, or the end of the window. */
2056 			for (posn = column, index = 0;
2057 			        posn < (int)width && index < (int)strlen(string_); posn++, index++) {
2058 				glk_put_char(string_[index]);
2059 			}
2060 
2061 			glk_set_window(gms_main_window);
2062 		} else {
2063 			static int current_line = 0;    /* Retained line number */
2064 			static int current_column = 0;  /* Retained col number */
2065 
2066 			int index;
2067 
2068 			/*
2069 			 * Check the line number against the last one output.  If it is less,
2070 			 * assume the start of a new block.  In this case, perform a hokey
2071 			 * type of screen clear.
2072 			 */
2073 			if (line < current_line) {
2074 				for (index = 0; index < (int)height; index++)
2075 					gms_normal_char('\n');
2076 
2077 				current_line = 0;
2078 				current_column = 0;
2079 			}
2080 
2081 			/* Print blank lines until the target line is reached. */
2082 			for (; current_line < line; current_line++) {
2083 				gms_normal_char('\n');
2084 				current_column = 0;
2085 			}
2086 
2087 			/* Now print spaces until the target column is reached. */
2088 			for (; current_column < column; current_column++)
2089 				gms_normal_char(' ');
2090 
2091 			/*
2092 			 * Write characters until the end of the string_, or the end of the
2093 			 * (self-imposed not-really-there) window.
2094 			 */
2095 			for (index = 0;
2096 			        current_column < (int)width && index < (int)strlen(string_);
2097 			        current_column++, index++) {
2098 				gms_normal_char(string_[index]);
2099 			}
2100 		}
2101 	}
2102 }
2103 
gms_hint_menu_header(int line,const char * string_,glui32 width,glui32 height)2104 void Magnetic::gms_hint_menu_header(int line, const char *string_,
2105 		glui32 width, glui32 height) {
2106 	int posn, length;
2107 	assert(string_);
2108 
2109 	/* Output the text in the approximate line center. */
2110 	length = strlen(string_);
2111 	posn = length < (int)width ? (width - length) / 2 : 0;
2112 	gms_hint_menu_print(line, posn, string_, width, height);
2113 }
2114 
gms_hint_menu_justify(int line,const char * left_string,const char * right_string,glui32 width,glui32 height)2115 void Magnetic::gms_hint_menu_justify(int line, const char *left_string,
2116 		const char *right_string, glui32 width, glui32 height) {
2117 	int posn, length;
2118 	assert(left_string && right_string);
2119 
2120 	/* Write left text normally to window left. */
2121 	gms_hint_menu_print(line, 0, left_string, width, height);
2122 
2123 	/* Output the right text flush with the right of the window. */
2124 	length = strlen(right_string);
2125 	posn = length < (int)width ? width - length : 0;
2126 	gms_hint_menu_print(line, posn, right_string, width, height);
2127 }
2128 
gms_hint_text_print(const char * string_)2129 void Magnetic::gms_hint_text_print(const char *string_) {
2130 	assert(string_);
2131 
2132 	if (gms_hint_windows_available()) {
2133 		glk_set_window(gms_hint_text_window);
2134 		glk_put_string(string_);
2135 		glk_set_window(gms_main_window);
2136 	} else
2137 		gms_normal_string(string_);
2138 }
2139 
gms_hint_menutext_start()2140 void Magnetic::gms_hint_menutext_start() {
2141 	/*
2142 	 * Twiddle for non-windowing libraries; 'clear' the main window by writing
2143 	 * a null string at line 1, then a null string at line 0.  This works
2144 	 * because we know the current output line in gms_hint_menu_print() is zero,
2145 	 * since we set it that way with gms_hint_menutext_done(), or if this is
2146 	 * the first call, then that's its initial value.
2147 	 */
2148 	if (!gms_hint_windows_available()) {
2149 		gms_hint_menu_print(1, 0, "",
2150 		                    GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
2151 		gms_hint_menu_print(0, 0, "",
2152 		                    GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
2153 	}
2154 }
2155 
gms_hint_menutext_done()2156 void Magnetic::gms_hint_menutext_done() {
2157 	/*
2158 	 * Twiddle for non-windowing libraries; 'clear' the main window by writing
2159 	 * an empty string to line zero.  For windowing Glk libraries, this function
2160 	 * does nothing.
2161 	 */
2162 	if (!gms_hint_windows_available()) {
2163 		gms_hint_menu_print(0, 0, "",
2164 		                    GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
2165 	}
2166 }
2167 
gms_hint_menutext_char_event(event_t * event)2168 void Magnetic::gms_hint_menutext_char_event(event_t *event) {
2169 	assert(event);
2170 
2171 	if (gms_hint_windows_available()) {
2172 		glk_request_char_event(gms_hint_menu_window);
2173 		glk_request_char_event(gms_hint_text_window);
2174 
2175 		gms_event_wait(evtype_CharInput, event);
2176 		assert(event->window == gms_hint_menu_window
2177 		       || event->window == gms_hint_text_window);
2178 
2179 		glk_cancel_char_event(gms_hint_menu_window);
2180 		glk_cancel_char_event(gms_hint_text_window);
2181 	} else {
2182 		glk_request_char_event(gms_main_window);
2183 		gms_event_wait(evtype_CharInput, event);
2184 	}
2185 }
2186 
gms_hint_arrange_windows(int requested_lines,glui32 * width,glui32 * height)2187 void Magnetic::gms_hint_arrange_windows(int requested_lines, glui32 *width, glui32 *height) {
2188 	if (gms_hint_windows_available()) {
2189 		winid_t parent;
2190 
2191 		/* Resize the hint menu window to fit the current hint. */
2192 		parent = glk_window_get_parent(gms_hint_menu_window);
2193 		glk_window_set_arrangement(parent,
2194 		                                 winmethod_Above | winmethod_Fixed,
2195 		                                 requested_lines, NULL);
2196 
2197 		uint width_temp, height_temp;
2198 
2199 		/* Measure, and return the size of the hint menu window. */
2200 		glk_window_get_size(gms_hint_menu_window, &width_temp, &height_temp);
2201 
2202 		*width = width_temp;
2203 		*height = height_temp;
2204 
2205 		/* Clear both the hint menu and the hint text window. */
2206 		glk_window_clear(gms_hint_menu_window);
2207 		glk_window_clear(gms_hint_text_window);
2208 	} else {
2209 		/*
2210 		 * No hints windows, so default width and height.  The hints output
2211 		 * functions will cope with this.
2212 		 */
2213 		if (width)
2214 			*width = GMS_HINT_DEFAULT_WIDTH;
2215 		if (height)
2216 			*height = GMS_HINT_DEFAULT_HEIGHT;
2217 	}
2218 }
2219 
gms_hint_display_folder(const ms_hint hints_[],const int cursor[],type16 node)2220 void Magnetic::gms_hint_display_folder(const ms_hint hints_[],
2221 		const int cursor[], type16 node) {
2222 	glui32 width, height;
2223 	int line, index;
2224 	assert(hints_ && cursor);
2225 
2226 	/*
2227 	 * Arrange windows to suit the hint folder.  For a folder menu window we
2228 	 * use one line for each element, three for the controls, and two spacers,
2229 	 * making a total of five additional lines.  Width and height receive the
2230 	 * actual menu window dimensions.
2231 	 */
2232 	gms_hint_arrange_windows(hints_[node].elcount + 5, &width, &height);
2233 
2234 	/* Paint in the menu header. */
2235 	line = 0;
2236 	gms_hint_menu_header(line++,
2237 	                     gms_get_hint_topic(hints_, node),
2238 	                     width, height);
2239 	gms_hint_menu_justify(line++,
2240 	                      " N = next subject  ", "  P = previous ",
2241 	                      width, height);
2242 	gms_hint_menu_justify(line++,
2243 	                      " RETURN = read subject  ",
2244 	                      node == GMS_HINT_ROOT_NODE
2245 	                      ? "  Q = resume game " : "  Q = previous menu ",
2246 	                      width, height);
2247 
2248 	/*
2249 	 * Output a blank line, then the menu for the node's folder hint.  The folder
2250 	 * text for the selected hint is preceded by a '>' pointer.
2251 	 */
2252 	line++;
2253 	for (index = 0; index < hints_[node].elcount; index++) {
2254 		gms_hint_menu_print(line, 3,
2255 		                    index == cursor[node] ? ">" : " ",
2256 		                    width, height);
2257 		gms_hint_menu_print(line++, 5,
2258 		                    gms_get_hint_content(hints_, node, index),
2259 		                    width, height);
2260 	}
2261 
2262 	/*
2263 	 * Terminate with a blank line; using a single space here improves cursor
2264 	 * positioning for optimized output libraries (for example, without it,
2265 	 * curses output will leave the cursor at the end of the previous line).
2266 	 */
2267 	gms_hint_menu_print(line, 0, " ", width, height);
2268 }
2269 
gms_hint_display_text(const ms_hint hints_[],const int cursor[],type16 node)2270 void Magnetic::gms_hint_display_text(const ms_hint hints_[],
2271 		const int cursor[], type16 node) {
2272 	glui32 width, height;
2273 	int line, index;
2274 	assert(hints_ && cursor);
2275 
2276 	/*
2277 	 * Arrange windows to suit the hint text.  For a hint menu, we use a simple
2278 	 * two-line set of controls; everything else is in the hints_ text window.
2279 	 * Width and height receive the actual menu window dimensions.
2280 	 */
2281 	gms_hint_arrange_windows(2, &width, &height);
2282 
2283 	/* Paint in a short menu header. */
2284 	line = 0;
2285 	gms_hint_menu_header(line++,
2286 	                     gms_get_hint_topic(hints_, node),
2287 	                     width, height);
2288 	gms_hint_menu_justify(line++,
2289 	                      " RETURN = read hint  ", "  Q = previous menu ",
2290 	                      width, height);
2291 
2292 	/*
2293 	 * Output hints_ to the hints_ text window.  hints_ not yet exposed are
2294 	 * indicated by the cursor for the hint, and are displayed as a dash.
2295 	 */
2296 	gms_hint_text_print("\n");
2297 	for (index = 0; index < hints_[node].elcount; index++) {
2298 		char buf[16];
2299 
2300 		sprintf(buf, "%3d.  ", index + 1);
2301 		gms_hint_text_print(buf);
2302 
2303 		gms_hint_text_print(index < cursor[node]
2304 		                    ? gms_get_hint_content(hints_, node, index) : "-");
2305 		gms_hint_text_print("\n");
2306 	}
2307 }
2308 
gms_hint_display(const ms_hint hints_[],const int cursor[],type16 node)2309 void Magnetic::gms_hint_display(const ms_hint hints_[], const int cursor[], type16 node) {
2310 	assert(hints_ && cursor);
2311 
2312 	switch (hints_[node].nodetype) {
2313 	case GMS_HINT_TYPE_TEXT:
2314 		gms_hint_display_text(hints_, cursor, node);
2315 		break;
2316 
2317 	case GMS_HINT_TYPE_FOLDER:
2318 		gms_hint_display_folder(hints_, cursor, node);
2319 		break;
2320 
2321 	default:
2322 		gms_fatal("GLK: Invalid hints_ node type encountered");
2323 		glk_exit();
2324 	}
2325 }
2326 
gms_hint_handle_folder(const ms_hint hints_[],int cursor[],type16 node,glui32 keycode)2327 type16 Magnetic::gms_hint_handle_folder(const ms_hint hints_[],
2328 		int cursor[], type16 node, glui32 keycode) {
2329 	unsigned char response;
2330 	type16 next_node;
2331 	assert(hints_ && cursor);
2332 
2333 	/* Convert key code into a single response character. */
2334 	switch (keycode) {
2335 	case keycode_Down:
2336 		response = 'N';
2337 		break;
2338 	case keycode_Up:
2339 		response = 'P';
2340 		break;
2341 	case keycode_Right:
2342 	case keycode_Return:
2343 		response = '\n';
2344 		break;
2345 	case keycode_Left:
2346 	case keycode_Escape:
2347 		response = 'Q';
2348 		break;
2349 	default:
2350 		response = keycode <= BYTE_MAX_VAL ? glk_char_to_upper(keycode) : 0;
2351 		break;
2352 	}
2353 
2354 	/*
2355 	 * Now handle the response character.  We'll default the next node to be
2356 	 * this node, but a response case can change it.
2357 	 */
2358 	next_node = node;
2359 	switch (response) {
2360 	case 'N':
2361 		/* Advance the hint cursor, wrapping at the folder end. */
2362 		if (cursor[node] < hints_[node].elcount - 1)
2363 			cursor[node]++;
2364 		else
2365 			cursor[node] = 0;
2366 		break;
2367 
2368 	case 'P':
2369 		/* Regress the hint cursor, wrapping at the folder start. */
2370 		if (cursor[node] > 0)
2371 			cursor[node]--;
2372 		else
2373 			cursor[node] = hints_[node].elcount - 1;
2374 		break;
2375 
2376 	case '\n':
2377 		/* The next node is the hint node at the cursor position. */
2378 		next_node = hints_[node].links[cursor[node]];
2379 		break;
2380 
2381 	case 'Q':
2382 		/* If root, we're done; if not, next node is node's parent. */
2383 		next_node = node == GMS_HINT_ROOT_NODE
2384 		            ? GMS_HINTS_DONE : hints_[node].parent;
2385 		break;
2386 
2387 	default:
2388 		break;
2389 	}
2390 
2391 	return next_node;
2392 }
2393 
gms_hint_handle_text(const ms_hint hints_[],int cursor[],type16 node,glui32 keycode)2394 type16 Magnetic::gms_hint_handle_text(const ms_hint hints_[],
2395 		int cursor[], type16 node, glui32 keycode) {
2396 	unsigned char response;
2397 	type16 next_node;
2398 	assert(hints_ && cursor);
2399 
2400 	/* Convert key code into a single response character. */
2401 	switch (keycode) {
2402 	case keycode_Right:
2403 	case keycode_Return:
2404 		response = '\n';
2405 		break;
2406 	case keycode_Left:
2407 	case keycode_Escape:
2408 		response = 'Q';
2409 		break;
2410 	default:
2411 		response = keycode <= BYTE_MAX_VAL ? glk_char_to_upper(keycode) : 0;
2412 		break;
2413 	}
2414 
2415 	/*
2416 	 * Now handle the response character.  We'll default the next node to be
2417 	 * this node, but a response case can change it.
2418 	 */
2419 	next_node = node;
2420 	switch (response) {
2421 	case '\n':
2422 		/* If not at end of the hint, advance the hint cursor. */
2423 		if (cursor[node] < hints_[node].elcount)
2424 			cursor[node]++;
2425 		break;
2426 
2427 	case 'Q':
2428 		/* Done with this hint node, so next node is its parent. */
2429 		next_node = hints_[node].parent;
2430 		break;
2431 
2432 	default:
2433 		break;
2434 	}
2435 
2436 	return next_node;
2437 }
2438 
gms_hint_handle(const ms_hint hints_[],int cursor[],type16 node,glui32 keycode)2439 type16 Magnetic::gms_hint_handle(const ms_hint hints_[],
2440 		int cursor[], type16 node, glui32 keycode) {
2441 	type16 next_node;
2442 	assert(hints_ && cursor);
2443 
2444 	next_node = GMS_HINT_ROOT_NODE;
2445 	switch (hints_[node].nodetype) {
2446 	case GMS_HINT_TYPE_TEXT:
2447 		next_node = gms_hint_handle_text(hints_, cursor, node, keycode);
2448 		break;
2449 
2450 	case GMS_HINT_TYPE_FOLDER:
2451 		next_node = gms_hint_handle_folder(hints_, cursor, node, keycode);
2452 		break;
2453 
2454 	default:
2455 		gms_fatal("GLK: Invalid hints_ node type encountered");
2456 		glk_exit();
2457 	}
2458 
2459 	return next_node;
2460 }
2461 
ms_showhints(ms_hint * hints_)2462 type8 Magnetic::ms_showhints(ms_hint *hints_) {
2463 	type16 hint_count;
2464 	glui32 crc;
2465 	assert(hints_);
2466 
2467 	/*
2468 	 * Find the number of hints_ in the array.  To do this, we'll visit every
2469 	 * node in a tree search, starting at the root, to locate the maximum node
2470 	 * number found, then add one to that.  It's a pity that the interpreter
2471 	 * doesn't hand us this information directly.
2472 	 */
2473 	hint_count = gms_get_hint_max_node(hints_, GMS_HINT_ROOT_NODE) + 1;
2474 
2475 	/*
2476 	 * Calculate a CRC for the hints_ array data.  If the CRC has changed, or
2477 	 * this is the first call, assign a new cursor array.
2478 	 */
2479 	crc = gms_get_buffer_crc(hints_, hint_count * sizeof(*hints_));
2480 	if (crc != hints_current_crc || !hints_crc_initialized) {
2481 		int bytes;
2482 
2483 		/* Allocate new cursors, and set all to zero initial state. */
2484 		free(gms_hint_cursor);
2485 		bytes = hint_count * sizeof(*gms_hint_cursor);
2486 		gms_hint_cursor = (int *)gms_malloc(bytes);
2487 		memset(gms_hint_cursor, 0, bytes);
2488 
2489 		/*
2490 		 * Retain the hints_ CRC, for later comparisons, and set is_initialized
2491 		 * flag.
2492 		 */
2493 		hints_current_crc = crc;
2494 		hints_crc_initialized = true;
2495 	}
2496 
2497 	/*
2498 	 * Save the hints_ array passed in.  This is done here since even if the data
2499 	 * remains the same (found by the CRC check above), the pointer to it might
2500 	 * have changed.
2501 	 */
2502 	gms_hints = hints_;
2503 
2504 	/*
2505 	 * Try to create the hints_ windows.  If they can't be created, perhaps
2506 	 * because the Glk library doesn't support it, the output functions will
2507 	 * work around this.
2508 	 */
2509 	gms_hint_open();
2510 	gms_hint_menutext_start();
2511 
2512 	/*
2513 	 * Begin hints_ display at the root node, and navigate until the user exits
2514 	 * hints_.
2515 	 */
2516 	gms_current_hint_node = GMS_HINT_ROOT_NODE;
2517 	while (gms_current_hint_node != GMS_HINTS_DONE) {
2518 		event_t event;
2519 
2520 		assert(gms_current_hint_node < hint_count);
2521 		gms_hint_display(gms_hints, gms_hint_cursor, gms_current_hint_node);
2522 
2523 		/* Get and handle a character key event for hint navigation. */
2524 		gms_hint_menutext_char_event(&event);
2525 		assert(event.type == evtype_CharInput);
2526 		gms_current_hint_node = gms_hint_handle(gms_hints,
2527 		                                        gms_hint_cursor,
2528 		                                        gms_current_hint_node,
2529 		                                        event.val1);
2530 	}
2531 
2532 	/* Done with hint windows. */
2533 	gms_hint_menutext_done();
2534 	gms_hint_close();
2535 
2536 	return GMS_HINT_SUCCESS;
2537 }
2538 
gms_hint_redraw()2539 void Magnetic::gms_hint_redraw() {
2540 	if (gms_hint_windows_available()) {
2541 		assert(gms_hints && gms_hint_cursor);
2542 		gms_hint_display(gms_hints, gms_hint_cursor, gms_current_hint_node);
2543 	}
2544 }
2545 
gms_hints_cleanup()2546 void Magnetic::gms_hints_cleanup() {
2547 	free(gms_hint_cursor);
2548 	gms_hint_cursor = NULL;
2549 
2550 	gms_hints = NULL;
2551 	gms_current_hint_node = 0;
2552 }
2553 
ms_playmusic(type8 * midi_data,type32 length,type16 tempo)2554 void Magnetic::ms_playmusic(type8 *midi_data, type32 length, type16 tempo) {
2555 }
2556 
2557 /*---------------------------------------------------------------------*/
2558 /*  Glk command escape functions                                       */
2559 /*---------------------------------------------------------------------*/
2560 
gms_command_undo(const char * argument)2561 void Magnetic::gms_command_undo(const char *argument) {
2562 	assert(argument);
2563 }
2564 
gms_command_script(const char * argument)2565 void Magnetic::gms_command_script(const char *argument) {
2566 	assert(argument);
2567 
2568 	if (gms_strcasecmp(argument, "on") == 0) {
2569 		frefid_t fileref;
2570 
2571 		if (gms_transcript_stream) {
2572 			gms_normal_string("Glk transcript is already on.\n");
2573 			return;
2574 		}
2575 
2576 		fileref = glk_fileref_create_by_prompt(fileusage_Transcript
2577 		          | fileusage_TextMode,
2578 		          filemode_WriteAppend, 0);
2579 		if (!fileref) {
2580 			gms_standout_string("Glk transcript failed.\n");
2581 			return;
2582 		}
2583 
2584 		gms_transcript_stream = glk_stream_open_file(fileref,
2585 		                        filemode_WriteAppend, 0);
2586 		glk_fileref_destroy(fileref);
2587 		if (!gms_transcript_stream) {
2588 			gms_standout_string("Glk transcript failed.\n");
2589 			return;
2590 		}
2591 
2592 		glk_window_set_echo_stream(gms_main_window, gms_transcript_stream);
2593 
2594 		gms_normal_string("Glk transcript is now on.\n");
2595 	}
2596 
2597 	else if (gms_strcasecmp(argument, "off") == 0) {
2598 		if (!gms_transcript_stream) {
2599 			gms_normal_string("Glk transcript is already off.\n");
2600 			return;
2601 		}
2602 
2603 		glk_stream_close(gms_transcript_stream, NULL);
2604 		gms_transcript_stream = NULL;
2605 
2606 		glk_window_set_echo_stream(gms_main_window, NULL);
2607 
2608 		gms_normal_string("Glk transcript is now off.\n");
2609 	}
2610 
2611 	else if (strlen(argument) == 0) {
2612 		gms_normal_string("Glk transcript is ");
2613 		gms_normal_string(gms_transcript_stream ? "on" : "off");
2614 		gms_normal_string(".\n");
2615 	}
2616 
2617 	else {
2618 		gms_normal_string("Glk transcript can be ");
2619 		gms_standout_string("on");
2620 		gms_normal_string(", or ");
2621 		gms_standout_string("off");
2622 		gms_normal_string(".\n");
2623 	}
2624 }
2625 
gms_command_inputlog(const char * argument)2626 void Magnetic::gms_command_inputlog(const char *argument) {
2627 	assert(argument);
2628 
2629 	if (gms_strcasecmp(argument, "on") == 0) {
2630 		frefid_t fileref;
2631 
2632 		if (gms_inputlog_stream) {
2633 			gms_normal_string("Glk input logging is already on.\n");
2634 			return;
2635 		}
2636 
2637 		fileref = glk_fileref_create_by_prompt(fileusage_InputRecord
2638 		          | fileusage_BinaryMode,
2639 		          filemode_WriteAppend, 0);
2640 		if (!fileref) {
2641 			gms_standout_string("Glk input logging failed.\n");
2642 			return;
2643 		}
2644 
2645 		gms_inputlog_stream = glk_stream_open_file(fileref,
2646 		                      filemode_WriteAppend, 0);
2647 		glk_fileref_destroy(fileref);
2648 		if (!gms_inputlog_stream) {
2649 			gms_standout_string("Glk input logging failed.\n");
2650 			return;
2651 		}
2652 
2653 		gms_normal_string("Glk input logging is now on.\n");
2654 	}
2655 
2656 	else if (gms_strcasecmp(argument, "off") == 0) {
2657 		if (!gms_inputlog_stream) {
2658 			gms_normal_string("Glk input logging is already off.\n");
2659 			return;
2660 		}
2661 
2662 		glk_stream_close(gms_inputlog_stream, NULL);
2663 		gms_inputlog_stream = NULL;
2664 
2665 		gms_normal_string("Glk input log is now off.\n");
2666 	}
2667 
2668 	else if (strlen(argument) == 0) {
2669 		gms_normal_string("Glk input logging is ");
2670 		gms_normal_string(gms_inputlog_stream ? "on" : "off");
2671 		gms_normal_string(".\n");
2672 	}
2673 
2674 	else {
2675 		gms_normal_string("Glk input logging can be ");
2676 		gms_standout_string("on");
2677 		gms_normal_string(", or ");
2678 		gms_standout_string("off");
2679 		gms_normal_string(".\n");
2680 	}
2681 }
2682 
gms_command_readlog(const char * argument)2683 void Magnetic::gms_command_readlog(const char *argument) {
2684 	assert(argument);
2685 
2686 	if (gms_strcasecmp(argument, "on") == 0) {
2687 		frefid_t fileref;
2688 
2689 		if (gms_readlog_stream) {
2690 			gms_normal_string("Glk read log is already on.\n");
2691 			return;
2692 		}
2693 
2694 		fileref = glk_fileref_create_by_prompt(fileusage_InputRecord
2695 		          | fileusage_BinaryMode,
2696 		          filemode_Read, 0);
2697 		if (!fileref) {
2698 			gms_standout_string("Glk read log failed.\n");
2699 			return;
2700 		}
2701 
2702 		if (!glk_fileref_does_file_exist(fileref)) {
2703 			glk_fileref_destroy(fileref);
2704 			gms_standout_string("Glk read log failed.\n");
2705 			return;
2706 		}
2707 
2708 		gms_readlog_stream = glk_stream_open_file(fileref, filemode_Read, 0);
2709 		glk_fileref_destroy(fileref);
2710 		if (!gms_readlog_stream) {
2711 			gms_standout_string("Glk read log failed.\n");
2712 			return;
2713 		}
2714 
2715 		gms_normal_string("Glk read log is now on.\n");
2716 	}
2717 
2718 	else if (gms_strcasecmp(argument, "off") == 0) {
2719 		if (!gms_readlog_stream) {
2720 			gms_normal_string("Glk read log is already off.\n");
2721 			return;
2722 		}
2723 
2724 		glk_stream_close(gms_readlog_stream, NULL);
2725 		gms_readlog_stream = NULL;
2726 
2727 		gms_normal_string("Glk read log is now off.\n");
2728 	}
2729 
2730 	else if (strlen(argument) == 0) {
2731 		gms_normal_string("Glk read log is ");
2732 		gms_normal_string(gms_readlog_stream ? "on" : "off");
2733 		gms_normal_string(".\n");
2734 	}
2735 
2736 	else {
2737 		gms_normal_string("Glk read log can be ");
2738 		gms_standout_string("on");
2739 		gms_normal_string(", or ");
2740 		gms_standout_string("off");
2741 		gms_normal_string(".\n");
2742 	}
2743 }
2744 
gms_command_abbreviations(const char * argument)2745 void Magnetic::gms_command_abbreviations(const char *argument) {
2746 	assert(argument);
2747 
2748 	if (gms_strcasecmp(argument, "on") == 0) {
2749 		if (gms_abbreviations_enabled) {
2750 			gms_normal_string("Glk abbreviation expansions are already on.\n");
2751 			return;
2752 		}
2753 
2754 		gms_abbreviations_enabled = true;
2755 		gms_normal_string("Glk abbreviation expansions are now on.\n");
2756 	}
2757 
2758 	else if (gms_strcasecmp(argument, "off") == 0) {
2759 		if (!gms_abbreviations_enabled) {
2760 			gms_normal_string("Glk abbreviation expansions are already off.\n");
2761 			return;
2762 		}
2763 
2764 		gms_abbreviations_enabled = false;
2765 		gms_normal_string("Glk abbreviation expansions are now off.\n");
2766 	}
2767 
2768 	else if (strlen(argument) == 0) {
2769 		gms_normal_string("Glk abbreviation expansions are ");
2770 		gms_normal_string(gms_abbreviations_enabled ? "on" : "off");
2771 		gms_normal_string(".\n");
2772 	}
2773 
2774 	else {
2775 		gms_normal_string("Glk abbreviation expansions can be ");
2776 		gms_standout_string("on");
2777 		gms_normal_string(", or ");
2778 		gms_standout_string("off");
2779 		gms_normal_string(".\n");
2780 	}
2781 }
2782 
gms_command_graphics(const char * argument)2783 void Magnetic::gms_command_graphics(const char *argument) {
2784 	assert(argument);
2785 
2786 	if (!gms_graphics_possible) {
2787 		gms_normal_string("Glk graphics are not available.\n");
2788 		return;
2789 	}
2790 
2791 	if (gms_strcasecmp(argument, "on") == 0) {
2792 		if (gms_graphics_enabled) {
2793 			gms_normal_string("Glk graphics are already on.\n");
2794 			return;
2795 		}
2796 
2797 		gms_graphics_enabled = true;
2798 
2799 		/* If a picture is loaded, call the restart function to repaint it. */
2800 		if (gms_graphics_picture_is_available()) {
2801 			if (!gms_graphics_open()) {
2802 				gms_normal_string("Glk graphics error.\n");
2803 				return;
2804 			}
2805 			gms_graphics_restart();
2806 		}
2807 
2808 		gms_normal_string("Glk graphics are now on.\n");
2809 	}
2810 
2811 	else if (gms_strcasecmp(argument, "off") == 0) {
2812 		if (!gms_graphics_enabled) {
2813 			gms_normal_string("Glk graphics are already off.\n");
2814 			return;
2815 		}
2816 
2817 		/*
2818 		 * Set graphics to disabled, and stop any graphics processing.  Close
2819 		 * the graphics window.
2820 		 */
2821 		gms_graphics_enabled = false;
2822 		gms_graphics_stop();
2823 		gms_graphics_close();
2824 
2825 		gms_normal_string("Glk graphics are now off.\n");
2826 	}
2827 
2828 	else if (strlen(argument) == 0) {
2829 		gms_normal_string("Glk graphics are available,");
2830 		gms_normal_string(gms_graphics_enabled
2831 		                  ? " and enabled.\n" : " but disabled.\n");
2832 
2833 		if (gms_graphics_picture_is_available()) {
2834 			int width, height, is_animated;
2835 
2836 			if (gms_graphics_get_picture_details(&width, &height, &is_animated)) {
2837 				char buf[16];
2838 
2839 				gms_normal_string("There is ");
2840 				gms_normal_string(is_animated ? "an animated" : "a");
2841 				gms_normal_string(" picture loaded, ");
2842 
2843 				sprintf(buf, "%d", width);
2844 				gms_normal_string(buf);
2845 				gms_normal_string(" by ");
2846 
2847 				sprintf(buf, "%d", height);
2848 				gms_normal_string(buf);
2849 
2850 				gms_normal_string(" pixels.\n");
2851 			}
2852 		}
2853 
2854 		if (!gms_graphics_interpreter_enabled())
2855 			gms_normal_string("Interpreter graphics are disabled.\n");
2856 
2857 		if (gms_graphics_enabled && gms_graphics_are_displayed()) {
2858 			int color_count, is_active;
2859 			const char *gamma;
2860 
2861 			if (gms_graphics_get_rendering_details(&gamma, &color_count,
2862 			                                       &is_active)) {
2863 				char buf[16];
2864 
2865 				gms_normal_string("Graphics are ");
2866 				gms_normal_string(is_active ? "active, " : "displayed, ");
2867 
2868 				sprintf(buf, "%d", color_count);
2869 				gms_normal_string(buf);
2870 				gms_normal_string(" colours");
2871 
2872 				if (gms_gamma_mode == GAMMA_OFF)
2873 					gms_normal_string(", without gamma correction");
2874 				else {
2875 					gms_normal_string(", with gamma ");
2876 					gms_normal_string(gamma);
2877 					gms_normal_string(" correction");
2878 				}
2879 				gms_normal_string(".\n");
2880 			} else
2881 				gms_normal_string("Graphics are being displayed.\n");
2882 		}
2883 
2884 		if (gms_graphics_enabled && !gms_graphics_are_displayed())
2885 			gms_normal_string("Graphics are not being displayed.\n");
2886 	}
2887 
2888 	else {
2889 		gms_normal_string("Glk graphics can be ");
2890 		gms_standout_string("on");
2891 		gms_normal_string(", or ");
2892 		gms_standout_string("off");
2893 		gms_normal_string(".\n");
2894 	}
2895 }
2896 
gms_command_gamma(const char * argument)2897 void Magnetic::gms_command_gamma(const char *argument) {
2898 	assert(argument);
2899 
2900 	if (!gms_graphics_possible) {
2901 		gms_normal_string("Glk automatic gamma correction is not available.\n");
2902 		return;
2903 	}
2904 
2905 	if (gms_strcasecmp(argument, "high") == 0) {
2906 		if (gms_gamma_mode == GAMMA_HIGH) {
2907 			gms_normal_string("Glk automatic gamma correction mode is"
2908 			                  " already 'high'.\n");
2909 			return;
2910 		}
2911 
2912 		gms_gamma_mode = GAMMA_HIGH;
2913 		gms_graphics_restart();
2914 
2915 		gms_normal_string("Glk automatic gamma correction mode is"
2916 		                  " now 'high'.\n");
2917 	}
2918 
2919 	else if (gms_strcasecmp(argument, "normal") == 0
2920 	         || gms_strcasecmp(argument, "on") == 0) {
2921 		if (gms_gamma_mode == GAMMA_NORMAL) {
2922 			gms_normal_string("Glk automatic gamma correction mode is"
2923 			                  " already 'normal'.\n");
2924 			return;
2925 		}
2926 
2927 		gms_gamma_mode = GAMMA_NORMAL;
2928 		gms_graphics_restart();
2929 
2930 		gms_normal_string("Glk automatic gamma correction mode is"
2931 		                  " now 'normal'.\n");
2932 	}
2933 
2934 	else if (gms_strcasecmp(argument, "none") == 0
2935 	         || gms_strcasecmp(argument, "off") == 0) {
2936 		if (gms_gamma_mode == GAMMA_OFF) {
2937 			gms_normal_string("Glk automatic gamma correction mode is"
2938 			                  " already 'off'.\n");
2939 			return;
2940 		}
2941 
2942 		gms_gamma_mode = GAMMA_OFF;
2943 		gms_graphics_restart();
2944 
2945 		gms_normal_string("Glk automatic gamma correction mode is"
2946 		                  " now 'off'.\n");
2947 	}
2948 
2949 	else if (strlen(argument) == 0) {
2950 		gms_normal_string("Glk automatic gamma correction mode is '");
2951 		switch (gms_gamma_mode) {
2952 		case GAMMA_OFF:
2953 		default:
2954 			gms_normal_string("off");
2955 			break;
2956 		case GAMMA_NORMAL:
2957 			gms_normal_string("normal");
2958 			break;
2959 		case GAMMA_HIGH:
2960 			gms_normal_string("high");
2961 			break;
2962 		}
2963 		gms_normal_string("'.\n");
2964 	}
2965 
2966 	else {
2967 		gms_normal_string("Glk automatic gamma correction mode can be ");
2968 		gms_standout_string("high");
2969 		gms_normal_string(", ");
2970 		gms_standout_string("normal");
2971 		gms_normal_string(", or ");
2972 		gms_standout_string("off");
2973 		gms_normal_string(".\n");
2974 	}
2975 }
2976 
gms_command_animations(const char * argument)2977 void Magnetic::gms_command_animations(const char *argument) {
2978 	assert(argument);
2979 
2980 	if (!gms_graphics_possible) {
2981 		gms_normal_string("Glk graphics animations are not available.\n");
2982 		return;
2983 	}
2984 
2985 	if (gms_strcasecmp(argument, "on") == 0) {
2986 		int is_animated;
2987 
2988 		if (gms_animation_enabled) {
2989 			gms_normal_string("Glk graphics animations are already on.\n");
2990 			return;
2991 		}
2992 
2993 		/*
2994 		 * Set animation to on, and restart graphics if the current picture
2995 		 * is animated; if it isn't, we can leave it displayed as is, since
2996 		 * changing animation mode doesn't affect this picture.
2997 		 */
2998 		gms_animation_enabled = true;
2999 		if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
3000 			if (is_animated)
3001 				gms_graphics_restart();
3002 		}
3003 
3004 		gms_normal_string("Glk graphics animations are now on.\n");
3005 	}
3006 
3007 	else if (gms_strcasecmp(argument, "off") == 0) {
3008 		int is_animated;
3009 
3010 		if (!gms_animation_enabled) {
3011 			gms_normal_string("Glk graphics animations are already off.\n");
3012 			return;
3013 		}
3014 
3015 		gms_animation_enabled = false;
3016 		if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
3017 			if (is_animated)
3018 				gms_graphics_restart();
3019 		}
3020 
3021 		gms_normal_string("Glk graphics animations are now off.\n");
3022 	}
3023 
3024 	else if (strlen(argument) == 0) {
3025 		gms_normal_string("Glk graphics animations are ");
3026 		gms_normal_string(gms_animation_enabled ? "on" : "off");
3027 		gms_normal_string(".\n");
3028 	}
3029 
3030 	else {
3031 		gms_normal_string("Glk graphics animations can be ");
3032 		gms_standout_string("on");
3033 		gms_normal_string(", or ");
3034 		gms_standout_string("off");
3035 		gms_normal_string(".\n");
3036 	}
3037 }
3038 
gms_command_prompts(const char * argument)3039 void Magnetic::gms_command_prompts(const char *argument) {
3040 	assert(argument);
3041 
3042 	if (gms_strcasecmp(argument, "on") == 0) {
3043 		if (gms_prompt_enabled) {
3044 			gms_normal_string("Glk extra prompts are already on.\n");
3045 			return;
3046 		}
3047 
3048 		gms_prompt_enabled = true;
3049 		gms_normal_string("Glk extra prompts are now on.\n");
3050 
3051 		/* Check for a game prompt to clear the flag. */
3052 		gms_game_prompted();
3053 	}
3054 
3055 	else if (gms_strcasecmp(argument, "off") == 0) {
3056 		if (!gms_prompt_enabled) {
3057 			gms_normal_string("Glk extra prompts are already off.\n");
3058 			return;
3059 		}
3060 
3061 		gms_prompt_enabled = false;
3062 		gms_normal_string("Glk extra prompts are now off.\n");
3063 	}
3064 
3065 	else if (strlen(argument) == 0) {
3066 		gms_normal_string("Glk extra prompts are ");
3067 		gms_normal_string(gms_prompt_enabled ? "on" : "off");
3068 		gms_normal_string(".\n");
3069 	}
3070 
3071 	else {
3072 		gms_normal_string("Glk extra prompts can be ");
3073 		gms_standout_string("on");
3074 		gms_normal_string(", or ");
3075 		gms_standout_string("off");
3076 		gms_normal_string(".\n");
3077 	}
3078 }
3079 
gms_command_print_version_number(glui32 version_)3080 void Magnetic::gms_command_print_version_number(glui32 version_) {
3081 	Common::String str = Common::String::format("%lu.%lu.%lu",
3082 	        (unsigned long)version_ >> 16,
3083 	        (unsigned long)(version_ >> 8) & 0xff,
3084 	        (unsigned long)version_ & 0xff);
3085 	gms_normal_string(str.c_str());
3086 }
3087 
gms_command_version(const char * argument)3088 void Magnetic::gms_command_version(const char *argument) {
3089 	glui32 version_;
3090 	assert(argument);
3091 
3092 	gms_normal_string("This is version_ ");
3093 	gms_command_print_version_number(GMS_PORT_VERSION);
3094 	gms_normal_string(" of the Glk Magnetic port.\n");
3095 
3096 	version_ = glk_gestalt(gestalt_Version, 0);
3097 	gms_normal_string("The Glk library version_ is ");
3098 	gms_command_print_version_number(version_);
3099 	gms_normal_string(".\n");
3100 }
3101 
gms_command_commands(const char * argument)3102 void Magnetic::gms_command_commands(const char *argument) {
3103 	assert(argument);
3104 
3105 	if (gms_strcasecmp(argument, "on") == 0) {
3106 		gms_normal_string("Glk commands are already on.\n");
3107 	}
3108 
3109 	else if (gms_strcasecmp(argument, "off") == 0) {
3110 		gms_commands_enabled = false;
3111 		gms_normal_string("Glk commands are now off.\n");
3112 	}
3113 
3114 	else if (strlen(argument) == 0) {
3115 		gms_normal_string("Glk commands are ");
3116 		gms_normal_string(gms_commands_enabled ? "on" : "off");
3117 		gms_normal_string(".\n");
3118 	}
3119 
3120 	else {
3121 		gms_normal_string("Glk commands can be ");
3122 		gms_standout_string("on");
3123 		gms_normal_string(", or ");
3124 		gms_standout_string("off");
3125 		gms_normal_string(".\n");
3126 	}
3127 }
3128 
gms_command_summary(const char * argument)3129 void Magnetic::gms_command_summary(const char *argument) {
3130 	const gms_command_t *entry;
3131 	assert(argument);
3132 
3133 	/*
3134 	 * Call handlers that have status to report with an empty argument,
3135 	 * prompting each to print its current setting.
3136 	 */
3137 	for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
3138 		if (entry->handler == &Magnetic::gms_command_summary
3139 		        || entry->handler == &Magnetic::gms_command_undo
3140 		        || entry->handler == &Magnetic::gms_command_help)
3141 			continue;
3142 
3143 		(this->*entry->handler)("");
3144 	}
3145 }
3146 
gms_command_help(const char * command)3147 void Magnetic::gms_command_help(const char *command) {
3148 	const gms_command_t *entry, *matched;
3149 	assert(command);
3150 
3151 	if (strlen(command) == 0) {
3152 		gms_normal_string("Glk commands are");
3153 		for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
3154 			const gms_command_t *next;
3155 
3156 			next = entry + 1;
3157 			gms_normal_string(next->command ? " " : " and ");
3158 			gms_standout_string(entry->command);
3159 			gms_normal_string(next->command ? "," : ".\n\n");
3160 		}
3161 
3162 		gms_normal_string("Glk commands may be abbreviated, as long as"
3163 		                  " the abbreviation is unambiguous.  Use ");
3164 		gms_standout_string("glk help");
3165 		gms_normal_string(" followed by a Glk command name for help on that"
3166 		                  " command.\n");
3167 		return;
3168 	}
3169 
3170 	matched = NULL;
3171 	for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
3172 		if (gms_strncasecmp(command, entry->command, strlen(command)) == 0) {
3173 			if (matched) {
3174 				gms_normal_string("The Glk command ");
3175 				gms_standout_string(command);
3176 				gms_normal_string(" is ambiguous.  Try ");
3177 				gms_standout_string("glk help");
3178 				gms_normal_string(" for more information.\n");
3179 				return;
3180 			}
3181 			matched = entry;
3182 		}
3183 	}
3184 	if (!matched) {
3185 		gms_normal_string("The Glk command ");
3186 		gms_standout_string(command);
3187 		gms_normal_string(" is not valid.  Try ");
3188 		gms_standout_string("glk help");
3189 		gms_normal_string(" for more information.\n");
3190 		return;
3191 	}
3192 
3193 	if (matched->handler == &Magnetic::gms_command_summary) {
3194 		gms_normal_string("Prints a summary of all the current Glk Magnetic"
3195 		                  " settings.\n");
3196 	}
3197 
3198 	else if (matched->handler == &Magnetic::gms_command_undo) {
3199 		gms_normal_string("Undoes a single game turn.\n\nEquivalent to the"
3200 		                  " standalone game 'undo' command.\n");
3201 	}
3202 
3203 	else if (matched->handler == &Magnetic::gms_command_script) {
3204 		gms_normal_string("Logs the game's output to a file.\n\nUse ");
3205 		gms_standout_string("glk script on");
3206 		gms_normal_string(" to begin logging game output, and ");
3207 		gms_standout_string("glk script off");
3208 		gms_normal_string(" to end it.  Glk Magnetic will ask you for a file"
3209 		                  " when you turn scripts on.\n");
3210 	}
3211 
3212 	else if (matched->handler == &Magnetic::gms_command_inputlog) {
3213 		gms_normal_string("Records the commands you type into a game.\n\nUse ");
3214 		gms_standout_string("glk inputlog on");
3215 		gms_normal_string(", to begin recording your commands, and ");
3216 		gms_standout_string("glk inputlog off");
3217 		gms_normal_string(" to turn off input logs.  You can play back"
3218 		                  " recorded commands into a game with the ");
3219 		gms_standout_string("glk readlog");
3220 		gms_normal_string(" command.\n");
3221 	}
3222 
3223 	else if (matched->handler == &Magnetic::gms_command_readlog) {
3224 		gms_normal_string("Plays back commands recorded with ");
3225 		gms_standout_string("glk inputlog on");
3226 		gms_normal_string(".\n\nUse ");
3227 		gms_standout_string("glk readlog on");
3228 		gms_normal_string(".  Command play back stops at the end of the"
3229 		                  " file.  You can also play back commands from a"
3230 		                  " text file created using any standard editor.\n");
3231 	}
3232 
3233 	else if (matched->handler == &Magnetic::gms_command_abbreviations) {
3234 		gms_normal_string("Controls abbreviation expansion.\n\nGlk Magnetic"
3235 		                  " automatically expands several standard single"
3236 		                  " letter abbreviations for you; for example, \"x\""
3237 		                  " becomes \"examine\".  Use ");
3238 		gms_standout_string("glk abbreviations on");
3239 		gms_normal_string(" to turn this feature on, and ");
3240 		gms_standout_string("glk abbreviations off");
3241 		gms_normal_string(" to turn it off.  While the feature is on, you"
3242 		                  " can bypass abbreviation expansion for an"
3243 		                  " individual game command by prefixing it with a"
3244 		                  " single quote.\n");
3245 	}
3246 
3247 	else if (matched->handler == &Magnetic::gms_command_graphics) {
3248 		gms_normal_string("Turns interpreter graphics on and off.\n\nUse ");
3249 		gms_standout_string("glk graphics on");
3250 		gms_normal_string(" to enable interpreter graphics, and ");
3251 		gms_standout_string("glk graphics off");
3252 		gms_normal_string(" to turn graphics off and close the graphics window."
3253 		                  "  This control works slightly differently to the"
3254 		                  " 'graphics' command in Magnetic Windows and Magnetic"
3255 		                  " Scrolls games themselves; the game's 'graphics'"
3256 		                  " command may disable new images, but leave old ones"
3257 		                  " displayed.  For graphics to be displayed, they"
3258 		                  " must be turned on in both the game and the"
3259 		                  " interpreter.\n");
3260 	}
3261 
3262 	else if (matched->handler == &Magnetic::gms_command_gamma) {
3263 		gms_normal_string("Sets the level of automatic gamma correction applied"
3264 		                  " to game graphics.\n\nUse ");
3265 		gms_standout_string("glk gamma normal");
3266 		gms_normal_string(" to set moderate automatic colour contrast"
3267 		                  " correction, ");
3268 		gms_standout_string("glk gamma high");
3269 		gms_normal_string(" to set high automatic colour contrast correction,"
3270 		                  " or ");
3271 		gms_standout_string("glk gamma off");
3272 		gms_normal_string(" to turn off all automatic gamma correction.\n");
3273 	}
3274 
3275 	else if (matched->handler == &Magnetic::gms_command_animations) {
3276 		gms_normal_string("Turns graphic animations on and off.\n\nUse ");
3277 		gms_standout_string("glk animation on");
3278 		gms_normal_string(" to enable animations, or ");
3279 		gms_standout_string("glk animation off");
3280 		gms_normal_string(" to turn animations off.  Not all game graphics are"
3281 		                  " animated, so this control works only on graphics"
3282 		                  " that are animated.  When animation is off, Glk"
3283 		                  " Magnetic displays only the static portions of a"
3284 		                  " game's pictures.\n");
3285 	}
3286 
3287 	else if (matched->handler == &Magnetic::gms_command_prompts) {
3288 		gms_normal_string("Controls extra input prompting.\n\n"
3289 		                  "Glk Magnetic can issue a replacement '>' input"
3290 		                  " prompt if it detects that the game hasn't prompted"
3291 		                  " after, say, an empty input line.  Use ");
3292 		gms_standout_string("glk prompts on");
3293 		gms_normal_string(" to turn this feature on, and ");
3294 		gms_standout_string("glk prompts off");
3295 		gms_normal_string(" to turn it off.\n");
3296 	}
3297 
3298 	else if (matched->handler == &Magnetic::gms_command_version) {
3299 		gms_normal_string("Prints the version numbers of the Glk library"
3300 		                  " and the Glk Magnetic port.\n");
3301 	}
3302 
3303 	else if (matched->handler == &Magnetic::gms_command_commands) {
3304 		gms_normal_string("Turn off Glk commands.\n\nUse ");
3305 		gms_standout_string("glk commands off");
3306 		gms_normal_string(" to disable all Glk commands, including this one."
3307 		                  "  Once turned off, there is no way to turn Glk"
3308 		                  " commands back on while inside the game.\n");
3309 	}
3310 
3311 	else if (matched->handler == &Magnetic::gms_command_help)
3312 		gms_command_help("");
3313 
3314 	else
3315 		gms_normal_string("There is no help available on that Glk command."
3316 		                  "  Sorry.\n");
3317 }
3318 
gms_command_escape(const char * string_,int * undo_command)3319 int Magnetic::gms_command_escape(const char *string_, int *undo_command) {
3320 	int posn;
3321 	char *string_copy, *command, *argument;
3322 	assert(string_ && undo_command);
3323 
3324 	/*
3325 	 * Return false if the string doesn't begin with the Glk command escape
3326 	 * introducer.
3327 	 */
3328 	posn = strspn(string_, "\t ");
3329 	if (gms_strncasecmp(string_ + posn, "glk", strlen("glk")) != 0)
3330 		return false;
3331 
3332 	/* Take a copy of the string_, without any leading space or introducer. */
3333 	string_copy = (char *)gms_malloc(strlen(string_ + posn) + 1 - strlen("glk"));
3334 	strcpy(string_copy, string_ + posn + strlen("glk"));
3335 
3336 	/*
3337 	 * Find the subcommand; the first word in the string copy.  Find its end,
3338 	 * and ensure it terminates with a NUL.
3339 	 */
3340 	posn = strspn(string_copy, "\t ");
3341 	command = string_copy + posn;
3342 	posn += strcspn(string_copy + posn, "\t ");
3343 	if (string_copy[posn] != '\0')
3344 		string_copy[posn++] = '\0';
3345 
3346 	/*
3347 	 * Now find any argument data for the command, ensuring it too terminates
3348 	 * with a NUL.
3349 	 */
3350 	posn += strspn(string_copy + posn, "\t ");
3351 	argument = string_copy + posn;
3352 	posn += strcspn(string_copy + posn, "\t ");
3353 	string_copy[posn] = '\0';
3354 
3355 	/*
3356 	 * Try to handle the command and argument as a Glk subcommand.  If it
3357 	 * doesn't run unambiguously, print command usage.  Treat an empty command
3358 	 * as "help".
3359 	 */
3360 	if (strlen(command) > 0) {
3361 		const gms_command_t *entry, *matched;
3362 		int matches;
3363 
3364 		/*
3365 		 * Search for the first unambiguous table command string_ matching
3366 		 * the command passed in.
3367 		 */
3368 		matches = 0;
3369 		matched = NULL;
3370 		for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
3371 			if (gms_strncasecmp(command, entry->command, strlen(command)) == 0) {
3372 				matches++;
3373 				matched = entry;
3374 			}
3375 		}
3376 
3377 		/* If the match was unambiguous, call the command handler. */
3378 		if (matches == 1) {
3379 			if (!matched->undo_return)
3380 				gms_normal_char('\n');
3381 			(this->*(matched->handler))(argument);
3382 
3383 			if (!matched->takes_argument && strlen(argument) > 0) {
3384 				gms_normal_string("[The ");
3385 				gms_standout_string(matched->command);
3386 				gms_normal_string(" command ignores arguments.]\n");
3387 			}
3388 
3389 			*undo_command = matched->undo_return;
3390 		}
3391 
3392 		/* No match, or the command was ambiguous. */
3393 		else {
3394 			gms_normal_string("\nThe Glk command ");
3395 			gms_standout_string(command);
3396 			gms_normal_string(" is ");
3397 			gms_normal_string(matches == 0 ? "not valid" : "ambiguous");
3398 			gms_normal_string(".  Try ");
3399 			gms_standout_string("glk help");
3400 			gms_normal_string(" for more information.\n");
3401 		}
3402 	} else {
3403 		gms_normal_char('\n');
3404 		gms_command_help("");
3405 	}
3406 
3407 	/* The string_ contained a Glk command; return true. */
3408 	free(string_copy);
3409 	return true;
3410 }
3411 
gms_command_undo_special(const char * string_)3412 int Magnetic::gms_command_undo_special(const char *string_) {
3413 	int posn, end;
3414 	assert(string_);
3415 
3416 	/* Find the start and end of the first string_ word. */
3417 	posn = strspn(string_, "\t ");
3418 	end = posn + strcspn(string_ + posn, "\t ");
3419 
3420 	/* See if string_ contains an "undo" request, with nothing following. */
3421 	if (end - posn == (int)strlen("undo")
3422 	        && gms_strncasecmp(string_ + posn, "undo", end - posn) == 0) {
3423 		posn = end + strspn(string_ + end, "\t ");
3424 		if (string_[posn] == '\0')
3425 			return true;
3426 	}
3427 
3428 	return false;
3429 }
3430 
3431 /*---------------------------------------------------------------------*/
3432 /*  Glk port input functions                                           */
3433 /*---------------------------------------------------------------------*/
3434 
gms_expand_abbreviations(char * buffer_,int size)3435 void Magnetic::gms_expand_abbreviations(char *buffer_, int size) {
3436 	char *command, abbreviation;
3437 	const char *expansion;
3438 	gms_abbreviationref_t entry;
3439 	assert(buffer_);
3440 
3441 	/* Ignore anything that isn't a single letter command. */
3442 	command = buffer_ + strspn(buffer_, "\t ");
3443 	if (!(strlen(command) == 1
3444 	        || (strlen(command) > 1 && Common::isSpace(command[1]))))
3445 		return;
3446 
3447 	/* Scan the abbreviations table for a match. */
3448 	abbreviation = glk_char_to_lower((unsigned char) command[0]);
3449 	expansion = NULL;
3450 	for (entry = GMS_ABBREVIATIONS; entry->expansion; entry++) {
3451 		if (entry->abbreviation == abbreviation) {
3452 			expansion = entry->expansion;
3453 			break;
3454 		}
3455 	}
3456 
3457 	/*
3458 	 * If a match found, check for a fit, then replace the character with the
3459 	 * expansion string.
3460 	 */
3461 	if (expansion) {
3462 		if ((int)(strlen(buffer_) + strlen(expansion)) - 1 >= size)
3463 			return;
3464 
3465 		memmove(command + strlen(expansion) - 1, command, strlen(command) + 1);
3466 		memcpy(command, expansion, strlen(expansion));
3467 
3468 #if 0
3469 		gms_standout_string("[");
3470 		gms_standout_char(abbreviation);
3471 		gms_standout_string(" -> ");
3472 		gms_standout_string(expansion);
3473 		gms_standout_string("]\n");
3474 #endif
3475 	}
3476 }
3477 
gms_buffer_input()3478 void Magnetic::gms_buffer_input() {
3479 	event_t event;
3480 
3481 	/*
3482 	 * Update the current status line display, and flush any pending buffered
3483 	 * output.
3484 	 */
3485 	gms_status_notify();
3486 	gms_output_flush();
3487 
3488 	/*
3489 	 * Magnetic Windows games tend not to issue a prompt after reading an empty
3490 	 * line of input.  This can make for a very blank looking screen.
3491 	 *
3492 	 * To slightly improve things, if it looks like we didn't get a prompt from
3493 	 * the game, do our own.
3494 	 */
3495 	if (gms_prompt_enabled && !gms_game_prompted()) {
3496 		gms_normal_char('\n');
3497 		gms_normal_string(GMS_INPUT_PROMPT);
3498 	}
3499 
3500 	/*
3501 	 * If we have an input log to read from, use that until it is exhausted.  On
3502 	 * end of file, close the stream and resume input from line requests.
3503 	 */
3504 	if (gms_readlog_stream) {
3505 		glui32 chars;
3506 
3507 		/* Get the next line from the log stream. */
3508 		chars = glk_get_line_stream(gms_readlog_stream,
3509 		                                  gms_input_buffer, sizeof(gms_input_buffer));
3510 		if (chars > 0) {
3511 			/* Echo the line just read in input style. */
3512 			glk_set_style(style_Input);
3513 			glk_put_buffer(gms_input_buffer, chars);
3514 			glk_set_style(style_Normal);
3515 
3516 			/* Note how many characters buffered, and return. */
3517 			gms_input_length = chars;
3518 			return;
3519 		}
3520 
3521 		/*
3522 		 * We're at the end of the log stream.  Close it, and then continue
3523 		 * on to request a line from Glk.
3524 		 */
3525 		glk_stream_close(gms_readlog_stream, NULL);
3526 		gms_readlog_stream = NULL;
3527 	}
3528 
3529 	/*
3530 	 * No input log being read, or we just hit the end of file on one.  Revert
3531 	 * to normal line input; start by getting a new line from Glk.
3532 	 */
3533 	glk_request_line_event(gms_main_window,
3534 	                             gms_input_buffer, sizeof(gms_input_buffer) - 1, 0);
3535 	gms_event_wait(evtype_LineInput, &event);
3536 	if (shouldQuit()) {
3537 		glk_cancel_line_event(gms_main_window, &event);
3538 		return;
3539 	}
3540 
3541 	/* Terminate the input line with a NUL. */
3542 	assert(event.val1 <= sizeof(gms_input_buffer) - 1);
3543 	gms_input_buffer[event.val1] = '\0';
3544 
3545 	/* Special handling for "undo" commands. */
3546 	if (gms_command_undo_special(gms_input_buffer)) {
3547 		/* Write the "undo" to any input log. */
3548 		if (gms_inputlog_stream) {
3549 			glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
3550 			glk_put_char_stream(gms_inputlog_stream, '\n');
3551 		}
3552 
3553 		/* Overwrite buffer with an empty line if we saw "undo". */
3554 		gms_input_buffer[0] = '\n';
3555 		gms_input_length = 1;
3556 
3557 		gms_undo_notification = true;
3558 		return;
3559 	}
3560 
3561 	/*
3562 	 * If neither abbreviations nor local commands are enabled, use the data
3563 	 * read above without further massaging.
3564 	 */
3565 	if (gms_abbreviations_enabled || gms_commands_enabled) {
3566 		char *command;
3567 
3568 		/*
3569 		 * If the first non-space input character is a quote, bypass all
3570 		 * abbreviation expansion and local command recognition, and use the
3571 		 * unadulterated input, less introductory quote.
3572 		 */
3573 		command = gms_input_buffer + strspn(gms_input_buffer, "\t ");
3574 		if (command[0] == '\'') {
3575 			/* Delete the quote with memmove(). */
3576 			memmove(command, command + 1, strlen(command));
3577 		} else {
3578 			/* Check for, and expand, any abbreviated commands. */
3579 			if (gms_abbreviations_enabled) {
3580 				gms_expand_abbreviations(gms_input_buffer,
3581 				                         sizeof(gms_input_buffer));
3582 			}
3583 
3584 			/*
3585 			 * Check for standalone "help", then for Glk port special commands;
3586 			 * suppress the interpreter's use of this input for Glk commands
3587 			 * by overwriting the line with a single newline character.
3588 			 */
3589 			if (gms_commands_enabled) {
3590 				int posn;
3591 
3592 				posn = strspn(gms_input_buffer, "\t ");
3593 				if (gms_strncasecmp(gms_input_buffer + posn,
3594 				                    "help", strlen("help")) == 0) {
3595 					if (strspn(gms_input_buffer + posn + strlen("help"), "\t ")
3596 					        == strlen(gms_input_buffer + posn + strlen("help"))) {
3597 						gms_output_register_help_request();
3598 					}
3599 				}
3600 
3601 				if (gms_command_escape(gms_input_buffer,
3602 				                       &gms_undo_notification)) {
3603 					gms_output_silence_help_hints();
3604 					gms_input_buffer[0] = '\n';
3605 					gms_input_length = 1;
3606 					return;
3607 				}
3608 			}
3609 		}
3610 	}
3611 
3612 	/*
3613 	 * If there is an input log active, log this input string to it.  Note that
3614 	 * by logging here we get any abbreviation expansions but we won't log glk
3615 	 * special commands, nor any input read from a current open input log.
3616 	 */
3617 	if (gms_inputlog_stream) {
3618 		glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
3619 		glk_put_char_stream(gms_inputlog_stream, '\n');
3620 	}
3621 
3622 	/*
3623 	 * Now append a newline to the buffer, since Glk line input doesn't provide
3624 	 * one, and in any case, abbreviation expansion may have edited the buffer
3625 	 * contents (and in particular, changed the length).
3626 	 */
3627 	gms_input_buffer[strlen(gms_input_buffer) + 1] = '\0';
3628 	gms_input_buffer[strlen(gms_input_buffer)] = '\n';
3629 
3630 	/* Note how many characters are buffered after all of the above. */
3631 	gms_input_length = strlen(gms_input_buffer);
3632 }
3633 
ms_getchar(type8 trans)3634 type8 Magnetic::ms_getchar(type8 trans) {
3635 	/* See if we are at the end of the input buffer. */
3636 	if (gms_input_cursor == gms_input_length) {
3637 		/*
3638 		 * Try to read in more data, and rewind buffer cursor.  As well as
3639 		 * reading input, this may set an undo notification.
3640 		 */
3641 		gms_buffer_input();
3642 		gms_input_cursor = 0;
3643 
3644 		if (shouldQuit())
3645 			return '\0';
3646 
3647 		if (gms_undo_notification) {
3648 			/*
3649 			 * Clear the undo notification, and discard buffered input (usually
3650 			 * just the '\n' placed there when the undo command was recognized).
3651 			 */
3652 			gms_undo_notification = false;
3653 			gms_input_length = 0;
3654 
3655 			/*
3656 			 * Return the special 0, or a blank line if no undo is allowed at
3657 			 * this point.
3658 			 */
3659 			return trans ? 0 : '\n';
3660 		}
3661 	}
3662 
3663 	/* Return the next character from the input buffer. */
3664 	assert(gms_input_cursor < gms_input_length);
3665 	return gms_input_buffer[gms_input_cursor++];
3666 }
3667 
3668 /*---------------------------------------------------------------------*/
3669 /*  Glk port event functions                                           */
3670 /*---------------------------------------------------------------------*/
3671 
gms_event_wait(glui32 wait_type,event_t * event)3672 void Magnetic::gms_event_wait(glui32 wait_type, event_t *event) {
3673 	assert(event);
3674 
3675 	do {
3676 		glk_select(event);
3677 
3678 		switch (event->type) {
3679 		case evtype_Arrange:
3680 		case evtype_Redraw:
3681 			/* Refresh any sensitive windows on size events. */
3682 			gms_status_redraw();
3683 			gms_hint_redraw();
3684 			gms_graphics_paint();
3685 			break;
3686 
3687 		case evtype_Timer:
3688 			/* Do background graphics updates on timeout. */
3689 			gms_graphics_timeout();
3690 			break;
3691 
3692 		case evtype_Quit:
3693 			return;
3694 
3695 		default:
3696 			break;
3697 		}
3698 	} while (event->type != (EvType)wait_type);
3699 }
3700 
3701 /*---------------------------------------------------------------------*/
3702 /*  Functions intercepted by link-time wrappers                        */
3703 /*---------------------------------------------------------------------*/
3704 
__wrap_toupper(int ch)3705 int Magnetic::__wrap_toupper(int ch) {
3706 	unsigned char uch;
3707 
3708 	uch = glk_char_to_upper((unsigned char) ch);
3709 	return (int) uch;
3710 }
3711 
__wrap_tolower(int ch)3712 int Magnetic::__wrap_tolower(int ch) {
3713 	unsigned char lch;
3714 
3715 	lch = glk_char_to_lower((unsigned char) ch);
3716 	return (int) lch;
3717 }
3718 
3719 /*---------------------------------------------------------------------*/
3720 /*  main() and options parsing                                         */
3721 /*---------------------------------------------------------------------*/
3722 
gms_establish_filenames(const char * name,char ** text,char ** graphics,char ** hints_)3723 void Magnetic::gms_establish_filenames(const char *name, char **text, char **graphics, char **hints_) {
3724 	char *base, *text_file, *graphics_file, *hints_file;
3725 	Common::File stream;
3726 	assert(name && text && graphics && hints_);
3727 
3728 	/* Take a destroyable copy of the input filename. */
3729 	base = (char *)gms_malloc(strlen(name) + 1);
3730 	strcpy(base, name);
3731 
3732 	/* If base has an extension .MAG, .GFX, or .HNT, remove it. */
3733 	if (strlen(base) > strlen(".XXX")) {
3734 		if (gms_strcasecmp(base + strlen(base) - strlen(".MAG"), ".MAG") == 0
3735 		        || gms_strcasecmp(base + strlen(base) - strlen(".GFX"), ".GFX") == 0
3736 		        || gms_strcasecmp(base + strlen(base) - strlen(".HNT"), ".HNT") == 0)
3737 			base[strlen(base) - strlen(".XXX")] = '\0';
3738 	}
3739 
3740 	/* Allocate space for the return text file. */
3741 	text_file = (char *)gms_malloc(strlen(base) + strlen(".MAG") + 1);
3742 
3743 	/* Form a candidate text file, by adding a .MAG extension. */
3744 	strcpy(text_file, base);
3745 	strcat(text_file, ".MAG");
3746 
3747 	if (!stream.open(text_file)) {
3748 		/* Retry, using a .mag extension instead. */
3749 		strcpy(text_file, base);
3750 		strcat(text_file, ".mag");
3751 
3752 		if (!stream.open(text_file)) {
3753 			/*
3754 			 * No access to a usable game text file.  Return immediately,
3755 			 * without looking for any associated graphics or hints_ files.
3756 			 */
3757 			*text = NULL;
3758 			*graphics = NULL;
3759 			*hints_ = NULL;
3760 
3761 			free(text_file);
3762 			free(base);
3763 			return;
3764 		}
3765 	}
3766 	stream.close();
3767 
3768 	/* Now allocate space for the return graphics file. */
3769 	graphics_file = (char *)gms_malloc(strlen(base) + strlen(".GFX") + 1);
3770 
3771 	/* As above, form a candidate graphics file, using a .GFX extension. */
3772 	strcpy(graphics_file, base);
3773 	strcat(graphics_file, ".GFX");
3774 
3775 	if (!stream.open(graphics_file)) {
3776 		/* Retry, using a .gfx extension instead. */
3777 		strcpy(graphics_file, base);
3778 		strcat(graphics_file, ".gfx");
3779 
3780 		if (!stream.open(graphics_file)) {
3781 			/*
3782 			 * No access to any graphics file.  In this case, free memory and
3783 			 * reset graphics file to NULL.
3784 			 */
3785 			free(graphics_file);
3786 			graphics_file = NULL;
3787 		}
3788 	}
3789 	stream.close();
3790 
3791 	/* Now allocate space for the return hints_ file. */
3792 	hints_file = (char *)gms_malloc(strlen(base) + strlen(".HNT") + 1);
3793 
3794 	/* As above, form a candidate graphics file, using a .HNT extension. */
3795 	strcpy(hints_file, base);
3796 	strcat(hints_file, ".HNT");
3797 
3798 	if (!stream.open(hints_file)) {
3799 		/* Retry, using a .hnt extension instead. */
3800 		strcpy(hints_file, base);
3801 		strcat(hints_file, ".hnt");
3802 
3803 		if (!stream.open(hints_file)) {
3804 			/*
3805 			 * No access to any hints_ file.  In this case, free memory and
3806 			 * reset hints_ file to NULL.
3807 			 */
3808 			free(hints_file);
3809 			hints_file = NULL;
3810 		}
3811 	}
3812 	stream.close();
3813 
3814 	/* Return the text file, and graphics and hints_, which may be NULL. */
3815 	*text = text_file;
3816 	*graphics = graphics_file;
3817 	*hints_ = hints_file;
3818 
3819 	free(base);
3820 }
3821 
gms_main()3822 void Magnetic::gms_main() {
3823 	char *text_file = NULL, *graphics_file = NULL, *hints_file = NULL;
3824 	int ms_init_status, is_running;
3825 
3826 	/* Create the main Glk window, and set its stream as current. */
3827 	gms_main_window = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
3828 	if (!gms_main_window) {
3829 		gms_fatal("GLK: Can't open main window");
3830 		glk_exit();
3831 		return;
3832 	}
3833 	glk_window_clear(gms_main_window);
3834 	glk_set_window(gms_main_window);
3835 	glk_set_style(style_Normal);
3836 
3837 	/*
3838 	 * Given the basic game name, try to come up with usable text, graphics,
3839 	 * and hints filenames.  The graphics and hints files may be null, but the
3840 	 * text file may not.
3841 	 */
3842 	Common::String gameFile = getFilename();
3843 	gms_establish_filenames(gameFile.c_str(), &text_file, &graphics_file, &hints_file);
3844 
3845 	/* Set the possibility of pictures depending on graphics file. */
3846 	if (graphics_file) {
3847 		/*
3848 		 * Check Glk library capabilities, and note pictures are impossible if
3849 		 * the library can't offer both graphics and timers.  We need timers to
3850 		 * create the background "thread" for picture updates.
3851 		 */
3852 		gms_graphics_possible = glk_gestalt(gestalt_Graphics, 0)
3853 		                        && glk_gestalt(gestalt_Timer, 0);
3854 	} else
3855 		gms_graphics_possible = false;
3856 
3857 
3858 	/*
3859 	 * If pictures are impossible, clear pictures enabled flag.  That is, act
3860 	 * as if -np was given on the command line, even though it may not have
3861 	 * been.  If pictures are impossible, they can never be enabled.
3862 	 */
3863 	if (!gms_graphics_possible)
3864 		gms_graphics_enabled = false;
3865 
3866 	/* Try to create a one-line status window.  We can live without it. */
3867 	glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
3868 	gms_status_window = glk_window_open(gms_main_window,
3869 	                    winmethod_Above | winmethod_Fixed,
3870 	                    1, wintype_TextGrid, 0);
3871 
3872 	/*
3873 	 * Load the game.  If no graphics are possible, then passing the NULL to
3874 	 * ms_init() runs a game without graphics.
3875 	 */
3876 	if (gms_graphics_possible) {
3877 		assert(graphics_file);
3878 		ms_init_status = ms_init(text_file, graphics_file, hints_file, NULL);
3879 	} else
3880 		ms_init_status = ms_init(text_file, NULL, hints_file, NULL);
3881 
3882 	/* Look for a complete failure to load the game. */
3883 	if (ms_init_status == 0) {
3884 		if (gms_status_window)
3885 			glk_window_close(gms_status_window, NULL);
3886 		gms_header_string("Glk Magnetic Error\n\n");
3887 		gms_normal_string("Can't load game '");
3888 		gms_normal_string(gameFile.c_str());
3889 		gms_normal_char('\'');
3890 
3891 		gms_normal_char('\n');
3892 
3893 		/*
3894 		 * Free the text file path, any graphics/hints file path, and
3895 		 * interpreter allocated memory.
3896 		 */
3897 		free(text_file);
3898 		free(graphics_file);
3899 		free(hints_file);
3900 		ms_freemem();
3901 		glk_exit();
3902 	}
3903 
3904 	/* Try to identify the game from its text file header. */
3905 	gms_gameid_identify_game(text_file);
3906 
3907 	/* Look for failure to load just game graphics. */
3908 	if (gms_graphics_possible && ms_init_status == 1) {
3909 		/*
3910 		 * Output a warning if graphics failed, but the main game text
3911 		 * initialized okay.
3912 		 */
3913 		gms_standout_string("Error: Unable to open graphics file\n"
3914 		                    "Continuing without pictures...\n\n");
3915 
3916 		gms_graphics_possible = false;
3917 	}
3918 
3919 	/* Run the game opcodes -- ms_rungame() returns false on game end. */
3920 	do {
3921 		is_running = ms_rungame() && !shouldQuit();
3922 		glk_tick();
3923 	} while (is_running);
3924 
3925 	/* Handle any updated status and pending buffered output. */
3926 	gms_status_notify();
3927 	gms_output_flush();
3928 
3929 	/* Turn off any background graphics "thread". */
3930 	gms_graphics_stop();
3931 
3932 	/* Free interpreter allocated memory. */
3933 	ms_freemem();
3934 
3935 	/*
3936 	 * Free any temporary memory that may have been used by graphics and hints.
3937 	 */
3938 	gms_graphics_cleanup();
3939 	gms_hints_cleanup();
3940 
3941 	/* Close any open transcript, input log, and/or read log. */
3942 	if (gms_transcript_stream) {
3943 		glk_stream_close(gms_transcript_stream, NULL);
3944 		gms_transcript_stream = NULL;
3945 	}
3946 	if (gms_inputlog_stream) {
3947 		glk_stream_close(gms_inputlog_stream, NULL);
3948 		gms_inputlog_stream = NULL;
3949 	}
3950 	if (gms_readlog_stream) {
3951 		glk_stream_close(gms_readlog_stream, NULL);
3952 		gms_readlog_stream = NULL;
3953 	}
3954 
3955 	/* Free the text file path, and any graphics/hints file path. */
3956 	free(text_file);
3957 	free(graphics_file);
3958 	free(hints_file);
3959 }
3960 
3961 /*---------------------------------------------------------------------*/
3962 /*  Linkage between Glk entry/exit calls and the Magnetic interpreter  */
3963 /*---------------------------------------------------------------------*/
3964 
glk_main()3965 void Magnetic::glk_main() {
3966 	assert(gms_startup_called && !gms_main_called);
3967 	gms_main_called = true;
3968 
3969 	/* Call the interpreter main function. */
3970 	gms_main();
3971 }
3972 
write(const char * fmt,...)3973 void Magnetic::write(const char *fmt, ...) {
3974 	va_list ap;
3975 	va_start(ap, fmt);
3976 	Common::String s = Common::String::format(fmt, ap);
3977 	va_end(ap);
3978 	glk_put_buffer(s.c_str(), s.size());
3979 }
3980 
writeChar(char c)3981 void Magnetic::writeChar(char c) {
3982 	glk_put_char(c);
3983 }
3984 
script_write(type8 c)3985 void Magnetic::script_write(type8 c) {
3986 	if (log_on == 2) {
3987 		if (_log1) {
3988 			_log1->writeByte(c);
3989 		}
3990 	}
3991 }
3992 
transcript_write(type8 c)3993 void Magnetic::transcript_write(type8 c) {
3994 	if (_log2) {
3995 		_log2->writeByte(c);
3996 	}
3997 }
3998 
3999 } // End of namespace Magnetic
4000 } // End of namespace Glk
4001