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/level9/os_glk.h"
24 #include "glk/level9/level9_main.h"
25 #include "glk/level9/level9.h"
26 #include "common/config-manager.h"
27 #include "common/textconsole.h"
28 
29 namespace Glk {
30 namespace Level9 {
31 
32 /*---------------------------------------------------------------------*/
33 /*  Module variables, miscellaneous other stuff                        */
34 /*---------------------------------------------------------------------*/
35 
36 /* Glk Level 9 port version number. */
37 static const glui32 GLN_PORT_VERSION = 0x00020201;
38 
39 /*
40  * We use a maximum of three Glk windows, one for status, one for pictures,
41  * and one for everything else.  The status and pictures windows may be
42  * nullptr, depending on user selections and the capabilities of the Glk
43  * library.
44  */
45 static winid_t gln_main_window, gln_status_window, gln_graphics_window;
46 
47 /*
48  * Transcript stream and input log.  These are nullptr if there is no current
49  * collection of these strings.
50  */
51 static strid_t gln_transcript_stream, gln_inputlog_stream;
52 
53 /* Input read log stream, for reading back an input log. */
54 static strid_t gln_readlog_stream;
55 
56 /* Note about whether graphics is possible, or not. */
57 bool gln_graphics_possible;
58 
59 /* Options that may be turned off by command line flags. */
60 bool gln_graphics_enabled, gln_intercept_enabled, gln_prompt_enabled;
61 bool gln_loopcheck_enabled, gln_abbreviations_enabled, gln_commands_enabled;
62 
63 /* Reason for stopping the game, used to detect restarts and ^C exits. */
64 enum StopReason {
65 	STOP_NONE, STOP_FORCE, STOP_RESTART, STOP_EXIT
66 };
67 static StopReason gln_stop_reason;
68 
69 /* Level 9 standard input prompt string. */
70 static const char *const GLN_INPUT_PROMPT = "> ";
71 
72 /*
73  * Typedef equivalents for interpreter types (uncapitalized to avoid appearing
74  * as macros), and some internal interpreter symbols symbols used for our own
75  * deviant purposes.
76  */
77 typedef L9BOOL gln_bool;
78 typedef L9BYTE gln_byte;
79 typedef L9UINT16 gln_uint16;
80 typedef L9UINT32 gln_uint32;
81 
82 extern void save();
83 extern void restore();
84 extern gln_bool Cheating;
85 extern gln_uint32 FileSize;
86 
87 /* Forward declarations of event wait and other miscellaneous functions. */
88 static void gln_event_wait(glui32 wait_type, event_t *event);
89 static void gln_event_wait_2(glui32 wait_type_1,
90 							 glui32 wait_type_2, event_t *event);
91 
92 static void gln_watchdog_tick();
93 static void gln_standout_string(const char *message);
94 
95 static int gln_confirm(const char *prompt);
96 
97 /* Picture variables */
98 /* Graphics file directory, and type of graphics found in it. */
99 static char *gln_graphics_bitmap_directory = nullptr;
100 static BitmapType gln_graphics_bitmap_type = NO_BITMAPS;
101 
102 /* The current picture id being displayed. */
103 enum { GLN_PALETTE_SIZE = 32 };
104 static gln_byte *gln_graphics_bitmap = nullptr;
105 static gln_uint16 gln_graphics_width = 0,
106 gln_graphics_height = 0;
107 static Colour gln_graphics_palette[GLN_PALETTE_SIZE]; /* = { 0, ... }; */
108 static int gln_graphics_picture = -1;
109 
110 /*
111  * Flags set on new picture, and on resize or arrange events, and a flag
112  * to indicate whether background repaint is stopped or active.
113  */
114 static int gln_graphics_new_picture = FALSE,
115 gln_graphics_repaint = FALSE,
116 gln_graphics_active = FALSE;
117 
118 /*
119  * State to monitor the state of interpreter graphics.  The values of the
120  * enumerations match the modes supplied by os_graphics().
121  */
122 enum GraphicsState {
123 	GLN_GRAPHICS_OFF = 0,
124 	GLN_GRAPHICS_LINE_MODE = 1,
125 	GLN_GRAPHICS_BITMAP_MODE = 2
126 };
127 static GraphicsState gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
128 
129 
130 /*
131  * Pointer to the two graphics buffers, one the off-screen representation
132  * of pixels, and the other tracking on-screen data.  These are temporary
133  * graphics malloc'ed memory, and should be free'd on exit.
134  */
135 static gln_byte *gln_graphics_off_screen = nullptr,
136 *gln_graphics_on_screen = nullptr;
137 
138 /*
139  * The number of colors used in the palette by the current picture.  Because
140  * of the way it's queried, we risk a race, with admittedly a very low
141  * probability, with the updater.  So, it's initialized instead to the
142  * largest possible value.  The real value in use is inserted on the first
143  * picture update timeout call for a new picture.
144  */
145 static int gln_graphics_color_count = GLN_PALETTE_SIZE;
146 
147 
148 /*---------------------------------------------------------------------*/
149 /*  Glk port utility functions                                         */
150 /*---------------------------------------------------------------------*/
151 
gln_initialize()152 void gln_initialize() {
153 	gln_main_window = nullptr;
154 	gln_status_window = nullptr;
155 	gln_graphics_window = nullptr;
156 	gln_transcript_stream = nullptr;
157 	gln_inputlog_stream = nullptr;
158 	gln_readlog_stream = nullptr;
159 	gln_graphics_possible = TRUE;
160 	gln_graphics_enabled = TRUE;
161 	gln_intercept_enabled = TRUE;
162 	gln_prompt_enabled = TRUE;
163 	gln_loopcheck_enabled = TRUE;
164 	gln_abbreviations_enabled = TRUE;
165 	gln_commands_enabled = TRUE;
166 	gln_stop_reason = STOP_NONE;
167 
168 	gln_graphics_bitmap_directory = nullptr;
169 	gln_graphics_bitmap_type = NO_BITMAPS;
170 	gln_graphics_bitmap = nullptr;
171 	gln_graphics_width = 0;
172 	gln_graphics_height = 0;
173 	gln_graphics_picture = -1;
174 	gln_graphics_new_picture = FALSE;
175 	gln_graphics_repaint = FALSE;
176 	gln_graphics_active = FALSE;
177 	gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
178 	gln_graphics_off_screen = nullptr;
179 	gln_graphics_on_screen = nullptr;
180 	gln_graphics_color_count = GLN_PALETTE_SIZE;
181 }
182 
183 /*
184  * gln_fatal()
185  *
186  * Fatal error handler.  The function returns, expecting the caller to
187  * abort() or otherwise handle the error.
188  */
gln_fatal(const char * string)189 static void gln_fatal(const char *string) {
190 	/*
191 	 * If the failure happens too early for us to have a window, print
192 	 * the message to stderr.
193 	 */
194 	if (!gln_main_window) {
195 		warning("INTERNAL ERROR: %s", string);
196 		return;
197 	}
198 
199 	/* Cancel all possible pending window input events. */
200 	g_vm->glk_cancel_line_event(gln_main_window, nullptr);
201 	g_vm->glk_cancel_char_event(gln_main_window);
202 
203 	/* Print a message indicating the error. */
204 	g_vm->glk_set_window(gln_main_window);
205 	g_vm->glk_set_style(style_Normal);
206 	g_vm->glk_put_string("\n\nINTERNAL ERROR: ");
207 	g_vm->glk_put_string(string);
208 
209 	g_vm->glk_put_string("\n\nPlease record the details of this error, try to"
210 	                     " note down everything you did to cause it, and email"
211 	                     " this information to simon_baldwin@yahoo.com.\n\n");
212 }
213 
214 
215 /*
216  * gln_malloc()
217  * gln_realloc()
218  *
219  * Non-failing malloc and realloc; call gln_fatal and exit if memory
220  * allocation fails.
221  */
gln_malloc(size_t size)222 static void *gln_malloc(size_t size) {
223 	void *pointer;
224 
225 	pointer = malloc(size);
226 	if (!pointer) {
227 		gln_fatal("GLK: Out of system memory");
228 		g_vm->glk_exit();
229 	}
230 
231 	return pointer;
232 }
233 
gln_realloc(void * ptr,size_t size)234 static void *gln_realloc(void *ptr, size_t size) {
235 	void *pointer;
236 
237 	pointer = realloc(ptr, size);
238 	if (!pointer) {
239 		gln_fatal("GLK: Out of system memory");
240 		g_vm->glk_exit();
241 	}
242 
243 	return pointer;
244 }
245 
246 
247 /*
248  * gln_strncasecmp()
249  * gln_strcasecmp()
250  *
251  * Strncasecmp and strcasecmp are not ANSI functions, so here are local
252  * definitions to do the same jobs.
253  *
254  * They're global here so that the core interpreter can use them; otherwise
255  * it tries to use the non-ANSI str[n]icmp() functions.
256  */
gln_strncasecmp(const char * s1,const char * s2,size_t n)257 int gln_strncasecmp(const char *s1, const char *s2, size_t n) {
258 	size_t index;
259 
260 	for (index = 0; index < n; index++) {
261 		int diff;
262 
263 		diff = g_vm->glk_char_to_lower(s1[index]) - g_vm->glk_char_to_lower(s2[index]);
264 		if (diff < 0 || diff > 0)
265 			return diff < 0 ? -1 : 1;
266 	}
267 
268 	return 0;
269 }
270 
gln_strcasecmp(const char * s1,const char * s2)271 int gln_strcasecmp(const char *s1, const char *s2) {
272 	size_t s1len, s2len;
273 	int result;
274 
275 	s1len = strlen(s1);
276 	s2len = strlen(s2);
277 
278 	result = gln_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
279 	if (result < 0 || result > 0)
280 		return result;
281 	else
282 		return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
283 }
284 
285 /*---------------------------------------------------------------------*/
286 /*  Glk port bitmap picture functions                                  */
287 /*---------------------------------------------------------------------*/
288 
289 /* R,G,B color triple definition. */
290 struct gln_rgb_t {
291 	int red, green, blue;
292 };
293 typedef gln_rgb_t *gln_rgbref_t;
294 
295 #ifndef GARGLK
296 /*
297  * Maximum number of regions to consider in a single repaint pass.  A
298  * couple of hundred seems to strike the right balance between not too
299  * sluggardly picture updates, and responsiveness to input during graphics
300  * rendering, when combined with short timeouts.
301  */
302 static const int GLN_REPAINT_LIMIT = 256;
303 #endif
304 
305 /*
306  * Graphics timeout; we like an update call after this period (ms).  In
307  * practice, this timeout may actually be shorter than the time taken
308  * to reach the limit on repaint regions, but because Glk guarantees that
309  * user interactions (in this case, line events) take precedence over
310  * timeouts, this should be okay; we'll still see a game that responds to
311  * input each time the background repaint function yields.
312  *
313  * Setting this value is tricky.  We'd like it to be the shortest possible
314  * consistent with getting other stuff done, say 10ms.  However, Xglk has
315  * a granularity of 50ms on checking for timeouts, as it uses a 1/20s
316  * timeout on X select.  This means that the shortest timeout we'll ever
317  * get from Xglk will be 50ms, so there's no point in setting this shorter
318  * than that.  With luck, other Glk libraries will be more efficient than
319  * this, and can give us higher timer resolution; we'll set 50ms here, and
320  * hope that no other Glk library is worse.
321  */
322 static const glui32 GLN_GRAPHICS_TIMEOUT = 50;
323 
324 /*
325  * Count of timeouts to wait on.  Waiting after a repaint smooths the
326  * display where the frame is being resized, by helping to avoid graphics
327  * output while more resize events are received; around 1/2 second seems
328  * okay.
329  */
330 static const int GLN_GRAPHICS_REPAINT_WAIT = 10;
331 
332 #ifdef GFX_SCALE_BY_FACTOR
333 /* Pixel size multiplier for image size scaling. */
334 static const int GLN_GRAPHICS_PIXEL = 1;
335 #endif
336 
337 /* Proportion of the display to use for graphics. */
338 static const glui32 GLN_GRAPHICS_PROPORTION = 50;
339 
340 /*
341  * Special title picture number, requiring its own handling, and count of
342  * timeouts to wait on after fully rendering the title picture (~2 seconds).
343  */
344 static const int GLN_GRAPHICS_TITLE_PICTURE = 0,
345 				 GLN_GRAPHICS_TITLE_WAIT = 40;
346 
347 /*
348  * Border and shading control.  For cases where we can't detect the back-
349  * ground color of the main window, there's a default, white, background.
350  * Bordering is black, with a 1 pixel border, 2 pixel shading, and 8 steps
351  * of shading fade.
352  */
353 static const glui32 GLN_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
354 					GLN_GRAPHICS_BORDER_COLOR = 0x00000000;
355 static const int GLN_GRAPHICS_BORDER = 1,
356 				 GLN_GRAPHICS_SHADING = 2,
357 				 GLN_GRAPHICS_SHADE_STEPS = 8;
358 
359 /*
360  * Guaranteed unused pixel value.  This value is used to fill the on-screen
361  * buffer on new pictures or repaints, resulting in a full paint of all
362  * pixels since no off-screen, real picture, pixel will match it.
363  */
364 static const int GLN_GRAPHICS_UNUSED_PIXEL = 0xff;
365 
366 
367 /*
368  * gln_graphics_open()
369  *
370  * If it's not open, open the graphics window.  Returns TRUE if graphics
371  * was successfully started, or already on.
372  */
gln_graphics_open()373 static int gln_graphics_open() {
374 	if (!gln_graphics_window) {
375 		gln_graphics_window = g_vm->glk_window_open(gln_main_window,
376 		                      winmethod_Above
377 		                      | winmethod_Proportional,
378 		                      GLN_GRAPHICS_PROPORTION,
379 		                      wintype_Graphics, 0);
380 	}
381 
382 	return gln_graphics_window != nullptr;
383 }
384 
385 
386 /*
387  * gln_graphics_close()
388  *
389  * If open, close the graphics window and set back to nullptr.
390  */
gln_graphics_close()391 static void gln_graphics_close() {
392 	if (gln_graphics_window) {
393 		g_vm->glk_window_close(gln_graphics_window, nullptr);
394 		gln_graphics_window = nullptr;
395 	}
396 }
397 
398 
399 /*
400  * gln_graphics_start()
401  *
402  * If graphics enabled, start any background picture update processing.
403  */
gln_graphics_start()404 static void gln_graphics_start() {
405 	if (gln_graphics_enabled) {
406 		/* If not running, start the updating "thread". */
407 		if (!gln_graphics_active) {
408 			g_vm->glk_request_timer_events(GLN_GRAPHICS_TIMEOUT);
409 			gln_graphics_active = TRUE;
410 		}
411 	}
412 }
413 
414 
415 /*
416  * gln_graphics_stop()
417  *
418  * Stop any background picture update processing.
419  */
gln_graphics_stop()420 static void gln_graphics_stop() {
421 	/* If running, stop the updating "thread". */
422 	if (gln_graphics_active) {
423 		g_vm->glk_request_timer_events(0);
424 		gln_graphics_active = FALSE;
425 	}
426 }
427 
428 
429 /*
430  * gln_graphics_are_displayed()
431  *
432  * Return TRUE if graphics are currently being displayed, FALSE otherwise.
433  */
gln_graphics_are_displayed()434 static int gln_graphics_are_displayed() {
435 	return gln_graphics_window != nullptr;
436 }
437 
438 
439 /*
440  * gln_graphics_paint()
441  *
442  * Set up a complete repaint of the current picture in the graphics window.
443  * This function should be called on the appropriate Glk window resize and
444  * arrange events.
445  */
gln_graphics_paint()446 static void gln_graphics_paint() {
447 	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
448 		/* Set the repaint flag, and start graphics. */
449 		gln_graphics_repaint = TRUE;
450 		gln_graphics_start();
451 	}
452 }
453 
454 
455 /*
456  * gln_graphics_restart()
457  *
458  * Restart graphics as if the current picture is a new picture.  This
459  * function should be called whenever graphics is re-enabled after being
460  * disabled.
461  */
gln_graphics_restart()462 static void gln_graphics_restart() {
463 	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
464 		/* Set the new picture flag, and start graphics. */
465 		gln_graphics_new_picture = TRUE;
466 		gln_graphics_start();
467 	}
468 }
469 
470 
471 /*
472  * gln_graphics_count_colors()
473  *
474  * Analyze an image, and return an overall count of how many colors out of
475  * the palette are used.
476  */
gln_graphics_count_colors(gln_byte bitmap[],gln_uint16 width,gln_uint16 height)477 static int gln_graphics_count_colors(gln_byte bitmap[], gln_uint16 width, gln_uint16 height) {
478 	int x, y, count;
479 	long usage[GLN_PALETTE_SIZE], index_row;
480 	assert(bitmap);
481 
482 	/*
483 	 * Traverse the image, counting each pixel usage.  For the y iterator,
484 	 * maintain an index row as an optimization to avoid multiplications in
485 	 * the loop.
486 	 */
487 	count = 0;
488 	memset(usage, 0, sizeof(usage));
489 	for (y = 0, index_row = 0; y < height; y++, index_row += width) {
490 		for (x = 0; x < width; x++) {
491 			long index;
492 
493 			/* Get the pixel index, and update the count for this color. */
494 			index = index_row + x;
495 			usage[bitmap[index]]++;
496 
497 			/* If color usage is now 1, note new color encountered. */
498 			if (usage[bitmap[index]] == 1)
499 				count++;
500 		}
501 	}
502 
503 	return count;
504 }
505 
506 
507 /*
508  * gln_graphics_split_color()
509  * gln_graphics_combine_color()
510  *
511  * General graphics helper functions, to convert between RGB and Glk glui32
512  * color representations.
513  */
gln_graphics_split_color(glui32 color,gln_rgbref_t rgb_color)514 static void gln_graphics_split_color(glui32 color, gln_rgbref_t rgb_color) {
515 	assert(rgb_color);
516 
517 	rgb_color->red   = (color >> 16) & 0xff;
518 	rgb_color->green = (color >> 8) & 0xff;
519 	rgb_color->blue  = color & 0xff;
520 }
521 
gln_graphics_combine_color(gln_rgbref_t rgb_color)522 static glui32 gln_graphics_combine_color(gln_rgbref_t rgb_color) {
523 	glui32 color;
524 	assert(rgb_color);
525 
526 	color = (rgb_color->red << 16) | (rgb_color->green << 8) | rgb_color->blue;
527 	return color;
528 }
529 
530 
531 /*
532  * gln_graphics_clear_and_border()
533  *
534  * Clear the graphics window, and border and shade the area where the
535  * picture is going to be rendered.  This attempts a small raised effect
536  * for the picture, in keeping with modern trends.
537  */
gln_graphics_clear_and_border(winid_t glk_window,int x_offset,int y_offset,int pixel_size,gln_uint16 width,gln_uint16 height)538 static void gln_graphics_clear_and_border(winid_t glk_window, int x_offset, int y_offset,
539 		int pixel_size, gln_uint16 width, gln_uint16 height) {
540 	uint background;
541 	glui32 fade_color, shading_color;
542 	gln_rgb_t rgb_background, rgb_border, rgb_fade;
543 	int index;
544 	assert(glk_window);
545 
546 	/*
547 	 * Try to detect the background color of the main window, by getting the
548 	 * background for Normal style (Glk offers no way to directly get a window's
549 	 * background color).  If we can get it, we'll match the graphics window
550 	 * background to it.  If we can't, we'll default the color to white.
551 	 */
552 	if (!g_vm->glk_style_measure(gln_main_window,
553 	                             style_Normal, stylehint_BackColor, &background)) {
554 		/*
555 		 * Unable to get the main window background, so assume, and default
556 		 * graphics to white.
557 		 */
558 		background = GLN_GRAPHICS_DEFAULT_BACKGROUND;
559 	}
560 
561 	/*
562 	 * Set the graphics window background to match the main window background,
563 	 * as best as we can tell, and clear the window.
564 	 */
565 	g_vm->glk_window_set_background_color(glk_window, background);
566 	g_vm->glk_window_clear(glk_window);
567 #ifndef GARGLK
568 	/*
569 	 * For very small pictures, just border them, but don't try and
570 	 * do any shading.  Failing this check is probably highly unlikely.
571 	 */
572 	if (width < 2 * GLN_GRAPHICS_SHADE_STEPS
573 			|| height < 2 * GLN_GRAPHICS_SHADE_STEPS) {
574 		/* Paint a rectangle bigger than the picture by border pixels. */
575 		g_vm->glk_window_fill_rect(glk_window,
576 		                           GLN_GRAPHICS_BORDER_COLOR,
577 		                           x_offset - GLN_GRAPHICS_BORDER,
578 		                           y_offset - GLN_GRAPHICS_BORDER,
579 		                           width * pixel_size + GLN_GRAPHICS_BORDER * 2,
580 		                           height * pixel_size + GLN_GRAPHICS_BORDER * 2);
581 		return;
582 	}
583 #endif
584 	/*
585 	 * Paint a rectangle bigger than the picture by border pixels all round,
586 	 * and with additional shading pixels right and below.  Some of these
587 	 * shading pixels are later overwritten by the fading loop below.  The
588 	 * picture will sit over this rectangle.
589 	 */
590 	g_vm->glk_window_fill_rect(glk_window,
591 	                           GLN_GRAPHICS_BORDER_COLOR,
592 	                           x_offset - GLN_GRAPHICS_BORDER,
593 	                           y_offset - GLN_GRAPHICS_BORDER,
594 	                           width * pixel_size + GLN_GRAPHICS_BORDER * 2
595 	                           + GLN_GRAPHICS_SHADING,
596 	                           height * pixel_size + GLN_GRAPHICS_BORDER * 2
597 	                           + GLN_GRAPHICS_SHADING);
598 
599 	/*
600 	 * Split the main window background color and the border color into
601 	 * components.
602 	 */
603 	gln_graphics_split_color(background, &rgb_background);
604 	gln_graphics_split_color(GLN_GRAPHICS_BORDER_COLOR, &rgb_border);
605 
606 	/*
607 	 * Generate the incremental color to use in fade steps.  Here we're
608 	 * assuming that the border is always darker than the main window
609 	 * background (currently valid, as we're using black).
610 	 */
611 	rgb_fade.red = (rgb_background.red - rgb_border.red)
612 	               / GLN_GRAPHICS_SHADE_STEPS;
613 	rgb_fade.green = (rgb_background.green - rgb_border.green)
614 	                 / GLN_GRAPHICS_SHADE_STEPS;
615 	rgb_fade.blue = (rgb_background.blue - rgb_border.blue)
616 	                / GLN_GRAPHICS_SHADE_STEPS;
617 
618 	/* Combine RGB fade into a single incremental Glk color. */
619 	fade_color = gln_graphics_combine_color(&rgb_fade);
620 
621 	/* Fade in edge, from background to border, shading in stages. */
622 	shading_color = background;
623 	for (index = 0; index < GLN_GRAPHICS_SHADE_STEPS; index++) {
624 		/* Shade the two border areas with this color. */
625 		g_vm->glk_window_fill_rect(glk_window, shading_color,
626 		                           x_offset + width * pixel_size
627 		                           + GLN_GRAPHICS_BORDER,
628 		                           y_offset + index - GLN_GRAPHICS_BORDER,
629 		                           GLN_GRAPHICS_SHADING, 1);
630 		g_vm->glk_window_fill_rect(glk_window, shading_color,
631 		                           x_offset + index - GLN_GRAPHICS_BORDER,
632 		                           y_offset + height * pixel_size
633 		                           + GLN_GRAPHICS_BORDER,
634 		                           1, GLN_GRAPHICS_SHADING);
635 
636 		/* Update the shading color for the fade next iteration. */
637 		shading_color -= fade_color;
638 	}
639 }
640 
641 
642 /*
643  * gln_graphics_convert_palette()
644  *
645  * Convert a Level 9 bitmap color palette to a Glk one.
646  */
gln_graphics_convert_palette(Colour ln_palette[],glui32 glk_palette[])647 static void gln_graphics_convert_palette(Colour ln_palette[], glui32 glk_palette[]) {
648 	int index;
649 	assert(ln_palette && glk_palette);
650 
651 	for (index = 0; index < GLN_PALETTE_SIZE; index++) {
652 		Colour colour;
653 		gln_rgb_t gln_color;
654 
655 		/* Convert color from Level 9 to internal RGB, then to Glk color. */
656 		colour = ln_palette[index];
657 		gln_color.red   = colour.red;
658 		gln_color.green = colour.green;
659 		gln_color.blue  = colour.blue;
660 		glk_palette[index] = gln_graphics_combine_color(&gln_color);
661 	}
662 }
663 
664 #ifdef GFX_SCALE_BY_FACTOR
665 /*
666  * gln_graphics_position_picture()
667  *
668  * Given a picture width and height, return the x and y offsets to center
669  * this picture in the current graphics window.
670  */
gln_graphics_position_picture(winid_t glk_window,int pixel_size,gln_uint16 width,gln_uint16 height,int * x_offset,int * y_offset)671 static void gln_graphics_position_picture(winid_t glk_window, int pixel_size,
672 		gln_uint16 width, gln_uint16 height, int *x_offset, int *y_offset) {
673 	uint window_width, window_height;
674 	assert(glk_window && x_offset && y_offset);
675 
676 	/* Measure the current graphics window dimensions. */
677 	g_vm->glk_window_get_size(glk_window, &window_width, &window_height);
678 
679 	/*
680 	 * Calculate and return an x and y offset to use on point plotting, so that
681 	 * the image centers inside the graphical window.
682 	 */
683 	*x_offset = ((int) window_width - width * pixel_size) / 2;
684 	*y_offset = ((int) window_height - height * pixel_size) / 2;
685 }
686 #endif
687 
688 /*
689  * gms_graphics_compare_layering_inverted()
690  * gln_graphics_assign_layers()
691  *
692  * Given two sets of image bitmaps, and a palette, this function will
693  * assign layers palette colors.
694  *
695  * Layers are assigned by first counting the number of vertices in the
696  * color plane, to get a measure of the complexity of shapes displayed in
697  * this color, and also the raw number of times each palette color is
698  * used.  This is then sorted, so that layers are assigned to colors, with
699  * the lowest layer being the color with the most complex shapes, and
700  * within this (or where the count of vertices is zero) the most used color.
701  *
702  * The function compares pixels in the two image bitmaps given, these
703  * being the off-screen and on-screen buffers, and generates counts only
704  * where these bitmaps differ.  This ensures that only pixels not yet
705  * painted are included in layering.
706  *
707  * As well as assigning layers, this function returns a set of layer usage
708  * flags, to help the rendering loop to terminate as early as possible.
709  *
710  * By painting lower layers first, the paint can take in larger areas if
711  * it's permitted to include not-yet-validated higher levels.  This helps
712  * minimize the amount of Glk areas fills needed to render a picture.
713  */
714 struct gln_layering_t {
715 	long complexity;  /* Count of vertices for this color. */
716 	long usage;       /* Color usage count. */
717 	int color;        /* Color index into palette. */
718 };
719 
720 #ifndef GARGLK
721 
722 /*
723  * gln_graphics_is_vertex()
724  *
725  * Given a point, return TRUE if that point is the vertex of a fillable
726  * region.  This is a helper function for layering pictures.  When assign-
727  * ing layers, we want to weight the colors that have the most complex
728  * shapes, or the largest count of isolated areas, heavier than simpler
729  * areas.
730  *
731  * By painting the colors with the largest number of isolated areas or
732  * the most complex shapes first, we help to minimize the number of fill
733  * regions needed to render the complete picture.
734  */
gln_graphics_is_vertex(gln_byte off_screen[],gln_uint16 width,gln_uint16 height,int x,int y)735 static int gln_graphics_is_vertex(gln_byte off_screen[], gln_uint16 width, gln_uint16 height,
736 	int x, int y) {
737 	gln_byte pixel;
738 	int above, below, left, right;
739 	long index_row;
740 	assert(off_screen);
741 
742 	/* Use an index row to cut down on multiplications. */
743 	index_row = y * width;
744 
745 	/* Find the color of the reference pixel. */
746 	pixel = off_screen[index_row + x];
747 	assert(pixel < GLN_PALETTE_SIZE);
748 
749 	/*
750 	 * Detect differences between the reference pixel and its upper, lower, left
751 	 * and right neighbors.  Mark as different if the neighbor doesn't exist,
752 	 * that is, at the edge of the picture.
753 	 */
754 	above = (y == 0 || off_screen[index_row - width + x] != pixel);
755 	below = (y == height - 1 || off_screen[index_row + width + x] != pixel);
756 	left = (x == 0 || off_screen[index_row + x - 1] != pixel);
757 	right = (x == width - 1 || off_screen[index_row + x + 1] != pixel);
758 
759 	/*
760 	 * Return TRUE if this pixel lies at the vertex of a rectangular, fillable,
761 	 * area.  That is, if two adjacent neighbors aren't the same color (or if
762 	 * absent -- at the edge of the picture).
763 	 */
764 	return ((above || below) && (left || right));
765 }
766 
gln_graphics_compare_layering_inverted(const void * void_first,const void * void_second)767 static int gln_graphics_compare_layering_inverted(const void *void_first,
768 		const void *void_second) {
769 	const gln_layering_t *first = (const gln_layering_t *)void_first;
770 	const gln_layering_t *second = (const gln_layering_t *)void_second;
771 
772 	/*
773 	 * Order by complexity first, then by usage, putting largest first.  Some
774 	 * colors may have no vertices at all when doing animation frames, but
775 	 * rendering optimization relies on the first layer that contains no areas
776 	 * to fill halting the rendering loop.  So it's important here that we order
777 	 * indexes so that colors that render complex shapes come first, non-empty,
778 	 * but simpler shaped colors next, and finally all genuinely empty layers.
779 	 */
780 	return second->complexity > first->complexity ? 1 :
781 	       first->complexity > second->complexity ? -1 :
782 	       second->usage > first->usage ? 1 :
783 	       first->usage > second->usage ? -1 : 0;
784 }
785 
gln_graphics_assign_layers(gln_byte off_screen[],gln_byte on_screen[],gln_uint16 width,gln_uint16 height,int layers[],long layer_usage[])786 static void gln_graphics_assign_layers(gln_byte off_screen[], gln_byte on_screen[],
787 		gln_uint16 width, gln_uint16 height, int layers[], long layer_usage[]) {
788 	int index, x, y;
789 	long index_row;
790 	gln_layering_t layering[GLN_PALETTE_SIZE];
791 	assert(off_screen && on_screen && layers && layer_usage);
792 
793 	/* Clear initial complexity and usage counts, and set initial colors. */
794 	for (index = 0; index < GLN_PALETTE_SIZE; index++) {
795 		layering[index].complexity = 0;
796 		layering[index].usage = 0;
797 		layering[index].color = index;
798 	}
799 
800 	/*
801 	 * Traverse the image, counting vertices and pixel usage where the pixels
802 	 * differ between the off-screen and on-screen buffers.  Optimize by
803 	 * maintaining an index row to avoid multiplications.
804 	 */
805 	for (y = 0, index_row = 0; y < height; y++, index_row += width) {
806 		for (x = 0; x < width; x++) {
807 			long idx;
808 
809 			/*
810 			 * Get the idx for this pixel, and update complexity and usage
811 			 * if off-screen and on-screen pixels differ.
812 			 */
813 			idx = index_row + x;
814 			if (on_screen[idx] != off_screen[idx]) {
815 				if (gln_graphics_is_vertex(off_screen, width, height, x, y))
816 					layering[off_screen[idx]].complexity++;
817 
818 				layering[off_screen[idx]].usage++;
819 			}
820 		}
821 	}
822 
823 	/*
824 	 * Sort counts to form color indexes.  The primary sort is on the shape
825 	 * complexity, and within this, on color usage.
826 	 */
827 	qsort(layering, GLN_PALETTE_SIZE,
828 	      sizeof(*layering), gln_graphics_compare_layering_inverted);
829 
830 	/*
831 	 * Assign a layer to each palette color, and also return the layer usage
832 	 * for each layer.
833 	 */
834 	for (index = 0; index < GLN_PALETTE_SIZE; index++) {
835 		layers[layering[index].color] = index;
836 		layer_usage[index] = layering[index].usage;
837 	}
838 }
839 
840 /*
841  * gln_graphics_paint_region()
842  *
843  * This is a partially optimized point plot.  Given a point in the graphics
844  * bitmap, it tries to extend the point to a color region, and fill a number
845  * of pixels in a single Glk rectangle fill.  The goal here is to reduce the
846  * number of Glk rectangle fills, which tend to be extremely inefficient
847  * operations for generalized point plotting.
848  *
849  * The extension works in image layers; each palette color is assigned* a
850  * layer, and we paint each layer individually, starting at the lowest.  So,
851  * the region is free to fill any invalidated pixel in a higher layer, and
852  * all pixels, invalidated or already validated, in the same layer.  In
853  * practice, it is good enough to look for either invalidated pixels or pixels
854  * in the same layer, and construct a region as large as possible from these,
855  * then on marking points as validated, mark only those in the same layer as
856  * the initial point.
857  *
858  * The optimization here is not the best possible, but is reasonable.  What
859  * we do is to try and stretch the region horizontally first, then vertically.
860  * In practice, we might find larger areas by stretching vertically and then
861  * horizontally, or by stretching both dimensions at the same time.  In
862  * mitigation, the number of colors in a picture is small (16), and the
863  * aspect ratio of pictures makes them generally wider than they are tall.
864  *
865  * Once we've found the region, we render it with a single Glk rectangle fill,
866  * and mark all the pixels in this region that match the layer of the initial
867  * given point as validated.
868  */
gln_graphics_paint_region(winid_t glk_window,glui32 palette[],int layers[],gln_byte off_screen[],gln_byte on_screen[],int x,int y,int x_offset,int y_offset,int pixel_size,gln_uint16 width,gln_uint16 height)869 static void gln_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
870 		gln_byte off_screen[], gln_byte on_screen[], int x, int y, int x_offset, int y_offset,
871 		int pixel_size, gln_uint16 width, gln_uint16 height) {
872 	gln_byte pixel;
873 	int layer, x_min, x_max, y_min, y_max, x_index, y_index;
874 	long index_row;
875 	assert(glk_window && palette && layers && off_screen && on_screen);
876 
877 	/* Find the color and layer for the initial pixel. */
878 	pixel = off_screen[y * width + x];
879 	layer = layers[pixel];
880 	assert(pixel < GLN_PALETTE_SIZE);
881 
882 	/*
883 	 * Start by finding the extent to which we can pull the x coordinate and
884 	 * still find either invalidated pixels, or pixels in this layer.
885 	 *
886 	 * Use an index row to remove multiplications from the loops.
887 	 */
888 	index_row = y * width;
889 	for (x_min = x; x_min - 1 >= 0; x_min--) {
890 		long index = index_row + x_min - 1;
891 
892 		if (on_screen[index] == off_screen[index]
893 		        && layers[off_screen[index]] != layer)
894 			break;
895 	}
896 	for (x_max = x; x_max + 1 < width; x_max++) {
897 		long index = index_row + x_max + 1;
898 
899 		if (on_screen[index] == off_screen[index]
900 		        && layers[off_screen[index]] != layer)
901 			break;
902 	}
903 
904 	/*
905 	 * Now try to stretch the height of the region, by extending the y
906 	 * coordinate as much as possible too.  Again, we're looking for pixels
907 	 * that are invalidated or ones in the same layer.  We need to check
908 	 * across the full width of the current region.
909 	 *
910 	 * As above, an index row removes multiplications from the loops.
911 	 */
912 	for (y_min = y, index_row = (y - 1) * width;
913 	        y_min - 1 >= 0; y_min--, index_row -= width) {
914 		for (x_index = x_min; x_index <= x_max; x_index++) {
915 			long index = index_row + x_index;
916 
917 			if (on_screen[index] == off_screen[index]
918 			        && layers[off_screen[index]] != layer)
919 				goto break_y_min;
920 		}
921 	}
922 break_y_min:
923 
924 	for (y_max = y, index_row = (y + 1) * width;
925 	        y_max + 1 < height; y_max++, index_row += width) {
926 		for (x_index = x_min; x_index <= x_max; x_index++) {
927 			long index = index_row + x_index;
928 
929 			if (on_screen[index] == off_screen[index]
930 			        && layers[off_screen[index]] != layer)
931 				goto break_y_max;
932 		}
933 	}
934 break_y_max:
935 
936 	/* Fill the region using Glk's rectangle fill. */
937 	g_vm->glk_window_fill_rect(glk_window, palette[pixel],
938 	                           x_min * pixel_size + x_offset,
939 	                           y_min * pixel_size + y_offset,
940 	                           (x_max - x_min + 1) * pixel_size,
941 	                           (y_max - y_min + 1) * pixel_size);
942 
943 	/*
944 	 * Validate each pixel in the reference layer that was rendered by the
945 	 * rectangle fill.  We don't validate pixels that are not in this layer
946 	 * (and are by definition in higher layers, as we've validated all lower
947 	 * layers), since although we colored them, we did it for optimization
948 	 * reasons, and they're not yet colored correctly.
949 	 *
950 	 * Maintain an index row as an optimization to avoid multiplication.
951 	 */
952 	index_row = y_min * width;
953 	for (y_index = y_min; y_index <= y_max; y_index++) {
954 		for (x_index = x_min; x_index <= x_max; x_index++) {
955 			long index;
956 
957 			/*
958 			 * Get the index for x_index,y_index.  If the layers match, update
959 			 * the on-screen buffer.
960 			 */
961 			index = index_row + x_index;
962 			if (layers[off_screen[index]] == layer) {
963 				assert(off_screen[index] == pixel);
964 				on_screen[index] = off_screen[index];
965 			}
966 		}
967 
968 		/* Update row index component on change of y. */
969 		index_row += width;
970 	}
971 }
972 #endif
973 
gln_graphics_paint_everything(winid_t glk_window,Colour palette[],gln_byte off_screen[],int x_offset,int y_offset,gln_uint16 width,gln_uint16 height)974 static void gln_graphics_paint_everything(winid_t glk_window, Colour palette[],
975 		gln_byte off_screen[], int x_offset, int y_offset, gln_uint16 width, gln_uint16 height) {
976 	Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 0);
977 	Graphics::ManagedSurface s(width, height, format);
978 
979 	for (int y = 0; y < height; ++y) {
980 		uint32 *lineP = (uint32 *)s.getBasePtr(0, y);
981 		for (int x = 0; x < width; ++x, ++lineP) {
982 			byte pixel = off_screen[y * width + x];
983 			assert(pixel < GLN_PALETTE_SIZE);
984 			const Colour &col = palette[pixel];
985 
986 			*lineP = format.RGBToColor(col.red, col.green, col.blue);
987 		}
988 	}
989 
990 	#ifdef GFX_SCALE_BY_FACTOR
991 	g_vm->glk_image_draw_scaled(glk_window, s, (uint)-1, x_offset, y_offset,
992 		width * GLN_GRAPHICS_PIXEL, height * GLN_GRAPHICS_PIXEL);
993 	#else
994 	uint winWidth, winHeight;
995 	g_vm->glk_window_get_size(glk_window, &winWidth, &winHeight);
996 	g_vm->glk_image_draw_scaled(glk_window, s, (uint)-1, 0, 0, winWidth, winHeight);
997 	#endif
998 }
999 
1000 /*
1001  * gln_graphics_timeout()
1002  *
1003  * This is a background function, called on Glk timeouts.  Its job is to
1004  * repaint some of the current graphics image.  On successive calls, it
1005  * does a part of the repaint, then yields to other processing.  This is
1006  * useful since the Glk primitive to plot points in graphical windows is
1007  * extremely slow; this way, the repaint doesn't block game play.
1008  *
1009  * The function should be called on Glk timeout events.  When the repaint
1010  * is complete, the function will turn off Glk timers.
1011  *
1012  * The function uses double-buffering to track how much of the graphics
1013  * buffer has been rendered.  This helps to minimize the amount of point
1014  * plots required, as only the differences between the two buffers need
1015  * to be rendered.
1016  */
gln_graphics_timeout()1017 static void gln_graphics_timeout() {
1018 	static glui32 palette[GLN_PALETTE_SIZE];   /* Precomputed Glk palette */
1019 
1020 	static int deferred_repaint = FALSE;       /* Local delayed repaint flag */
1021 	static int ignore_counter;                 /* Count of calls ignored */
1022 	static int x_offset, y_offset;             /* Point plot offsets */
1023 
1024 #ifndef GARGLK
1025 	static int yield_counter;                  /* Yields in rendering */
1026 	static int saved_layer;                    /* Saved current layer */
1027 	static int saved_x, saved_y;               /* Saved x,y coord */
1028 
1029 	static int total_regions;                  /* Debug statistic */
1030 #endif
1031 	gln_byte *on_screen;                       /* On-screen image buffer */
1032 	gln_byte *off_screen;                      /* Off-screen image buffer */
1033 	long picture_size;                         /* Picture size in pixels */
1034 
1035 	/* Ignore the call if the current graphics state is inactive. */
1036 	if (!gln_graphics_active)
1037 		return;
1038 	assert(gln_graphics_window);
1039 
1040 	/*
1041 	 * On detecting a repaint request, note the flag in a local static variable,
1042 	 * then set up a graphics delay to wait until, hopefully, the resize, if
1043 	 * that's what caused it, is complete, and return.  This makes resizing the
1044 	 * window a lot smoother, since it prevents unnecessary region paints where
1045 	 * we are receiving consecutive Glk arrange or redraw events.
1046 	 */
1047 	if (gln_graphics_repaint) {
1048 		deferred_repaint = TRUE;
1049 		gln_graphics_repaint = FALSE;
1050 		ignore_counter = GLN_GRAPHICS_REPAINT_WAIT - 1;
1051 		return;
1052 	}
1053 
1054 	/*
1055 	 * If asked to ignore a given number of calls, decrement the ignore counter
1056 	 * and return having done nothing more.  This lets us delay graphics
1057 	 * operations by a number of timeouts, providing partial protection from
1058 	 * resize event "storms".
1059 	 *
1060 	 * Note -- to wait for N timeouts, set the count of timeouts to be ignored
1061 	 * to N-1.
1062 	 */
1063 	assert(ignore_counter >= 0);
1064 	if (ignore_counter > 0) {
1065 		ignore_counter--;
1066 		return;
1067 	}
1068 
1069 	/* Calculate the picture size, and synchronize screen buffer pointers. */
1070 	picture_size = gln_graphics_width * gln_graphics_height;
1071 	off_screen = gln_graphics_off_screen;
1072 	on_screen = gln_graphics_on_screen;
1073 
1074 	/*
1075 	 * If we received a new picture, set up the local static variables for that
1076 	 * picture -- convert the color palette, and initialize the off_screen
1077 	 * buffer to be the base picture.
1078 	 */
1079 	if (gln_graphics_new_picture) {
1080 		/* Initialize the off_screen buffer to be a copy of the base picture. */
1081 		free(off_screen);
1082 		off_screen = (gln_byte *)gln_malloc(picture_size * sizeof(*off_screen));
1083 		memcpy(off_screen, gln_graphics_bitmap,
1084 		       picture_size * sizeof(*off_screen));
1085 
1086 		/* Note the buffer for freeing on cleanup. */
1087 		gln_graphics_off_screen = off_screen;
1088 
1089 		/*
1090 		 * Pre-convert all the picture palette colors into their corresponding
1091 		 * Glk colors.
1092 		 */
1093 		gln_graphics_convert_palette(gln_graphics_palette, palette);
1094 
1095 		/* Save the color count for possible queries later. */
1096 		gln_graphics_color_count =
1097 		    gln_graphics_count_colors(off_screen,
1098 		                              gln_graphics_width, gln_graphics_height);
1099 	}
1100 
1101 	/*
1102 	 * For a new picture, or a repaint of a prior one, calculate new values for
1103 	 * the x and y offsets used to draw image points, and set the on-screen
1104 	 * buffer to an unused pixel value, in effect invalidating all on-screen
1105 	 * data.  Also, reset the saved image scan coordinates so that we scan for
1106 	 * unpainted pixels from top left starting at layer zero, and clear the
1107 	 * graphics window.
1108 	 */
1109 	if (gln_graphics_new_picture || deferred_repaint) {
1110 		#ifdef GFX_SCALE_BY_FACTOR
1111 		/*
1112 		 * Calculate the x and y offset to center the picture in the graphics
1113 		 * window.
1114 		 */
1115 		gln_graphics_position_picture(gln_graphics_window,
1116 		                              GLN_GRAPHICS_PIXEL,
1117 		                              gln_graphics_width, gln_graphics_height,
1118 		                              &x_offset, &y_offset);
1119 		#else
1120 		x_offset = y_offset = 0;
1121 		#endif
1122 
1123 		/*
1124 		 * Reset all on-screen pixels to an unused value, guaranteed not to
1125 		 * match any in a real picture.  This forces all pixels to be repainted
1126 		 * on a buffer/on-screen comparison.
1127 		 */
1128 		free(on_screen);
1129 		on_screen = (gln_byte *)gln_malloc(picture_size * sizeof(*on_screen));
1130 		memset(on_screen, GLN_GRAPHICS_UNUSED_PIXEL,
1131 		       picture_size * sizeof(*on_screen));
1132 
1133 		/* Note the buffer for freeing on cleanup. */
1134 		gln_graphics_on_screen = on_screen;
1135 
1136 		/*
1137 		 * Assign new layers to the current image.  This sorts colors by usage
1138 		 * and puts the most used colors in the lower layers.  It also hands us
1139 		 * a count of pixels in each layer, useful for knowing when to stop
1140 		 * scanning for layers in the rendering loop.
1141 		 */
1142 #ifndef GARGLK
1143 		gln_graphics_assign_layers(off_screen, on_screen,
1144 		                           gln_graphics_width, gln_graphics_height,
1145 		                           layers, layer_usage);
1146 #endif
1147 
1148 		/* Clear the graphics window. */
1149 		gln_graphics_clear_and_border(gln_graphics_window,
1150 		                              x_offset, y_offset,
1151 		#ifdef GFX_SCALE_BY_FACTOR
1152 		                              GLN_GRAPHICS_PIXEL,
1153 		#else
1154 										1,
1155 		#endif
1156 		                              gln_graphics_width, gln_graphics_height);
1157 #ifndef GARGLK
1158 		/* Start a fresh picture rendering pass. */
1159 		yield_counter = 0;
1160 		saved_layer = 0;
1161 		saved_x = 0;
1162 		saved_y = 0;
1163 		total_regions = 0;
1164 #endif
1165 
1166 		/* Clear the new picture and deferred repaint flags. */
1167 		gln_graphics_new_picture = FALSE;
1168 		deferred_repaint = FALSE;
1169 	}
1170 
1171 #ifndef GARGLK
1172 	int layer;                                 /* Image layer iterator */
1173 	int x, y;                                  /* Image iterators */
1174 	int regions;                               /* Count of regions painted */
1175 	static int layers[GLN_PALETTE_SIZE];       /* Assigned image layers */
1176 	static long layer_usage[GLN_PALETTE_SIZE]; /* Image layer occupancies */
1177 
1178 	/*
1179 	 * Make a portion of an image pass, from lower to higher image layers,
1180 	 * scanning for invalidated pixels that are in the current image layer we
1181 	 * are painting.  Each invalidated pixel gives rise to a region paint,
1182 	 * which equates to one Glk rectangle fill.
1183 	 *
1184 	 * When the limit on regions is reached, save the current image pass layer
1185 	 * and coordinates, and yield control to the main game playing code by
1186 	 * returning.  On the next call, pick up where we left off.
1187 	 *
1188 	 * As an optimization, we can leave the loop on the first empty layer we
1189 	 * encounter.  Since layers are ordered by complexity and color usage, all
1190 	 * layers higher than the first unused one will also be empty, so we don't
1191 	 * need to scan them.
1192 	 */
1193 	regions = 0;
1194 	for (layer = saved_layer;
1195 	        layer < GLN_PALETTE_SIZE && layer_usage[layer] > 0; layer++) {
1196 		long index_row;
1197 
1198 		/*
1199 		 * As an optimization to avoid multiplications in the loop, maintain a
1200 		 * separate index row.
1201 		 */
1202 		index_row = saved_y * gln_graphics_width;
1203 		for (y = saved_y; y < gln_graphics_height; y++) {
1204 			for (x = saved_x; x < gln_graphics_width; x++) {
1205 				long index;
1206 
1207 				/* Get the index for this pixel. */
1208 				index = index_row + x;
1209 				assert(index < picture_size * sizeof(*off_screen));
1210 
1211 				/*
1212 				 * Ignore pixels not in the current layer, and pixels not
1213 				 * currently invalid (that is, ones whose on-screen represen-
1214 				 * tation matches the off-screen buffer).
1215 				 */
1216 				if (layers[off_screen[index]] == layer
1217 				        && on_screen[index] != off_screen[index]) {
1218 					/*
1219 					 * Rather than painting just one pixel, here we try to
1220 					 * paint the maximal region we can for the layer of the
1221 					 * given pixel.
1222 					 */
1223 					gln_graphics_paint_region(gln_graphics_window,
1224 					                          palette, layers,
1225 					                          off_screen, on_screen,
1226 					                          x, y, x_offset, y_offset,
1227 					                          GLN_GRAPHICS_PIXEL,
1228 					                          gln_graphics_width,
1229 					                          gln_graphics_height);
1230 
1231 					/*
1232 					 * Increment count of regions handled, and yield, by
1233 					 * returning, if the limit on paint regions is reached.
1234 					 * Before returning, save the current layer and scan
1235 					 * coordinates, so we can pick up here on the next call.
1236 					 */
1237 					regions++;
1238 					if (regions >= GLN_REPAINT_LIMIT) {
1239 						yield_counter++;
1240 						saved_layer = layer;
1241 						saved_x = x;
1242 						saved_y = y;
1243 						total_regions += regions;
1244 						return;
1245 					}
1246 				}
1247 			}
1248 
1249 			/* Reset the saved x coordinate on y increment. */
1250 			saved_x = 0;
1251 
1252 			/* Update the index row on change of y. */
1253 			index_row += gln_graphics_width;
1254 		}
1255 
1256 		/* Reset the saved y coordinate on layer change. */
1257 		saved_y = 0;
1258 	}
1259 
1260 	/*
1261 	 * If we reach this point, then we didn't get to the limit on regions
1262 	 * painted on this pass.  In that case, we've finished rendering the
1263 	 * image.
1264 	 */
1265 	assert(regions < GLN_REPAINT_LIMIT);
1266 	total_regions += regions;
1267 
1268 #else
1269 	gln_graphics_paint_everything(gln_graphics_window, gln_graphics_palette, off_screen,
1270 		x_offset, y_offset, gln_graphics_width, gln_graphics_height);
1271 #endif
1272 
1273 	/* Stop graphics; there's no more to be done until something restarts us. */
1274 	gln_graphics_stop();
1275 }
1276 
1277 /*
1278  * gln_graphics_locate_bitmaps()
1279  *
1280  * Given the name of the game file being run, try to set up the graphics
1281  * directory and bitmap type for that game.  If none available, set the
1282  * directory to NULL, and bitmap type to NO_BITMAPS.
1283  */
gln_graphics_locate_bitmaps(const char * gamefile)1284 static void gln_graphics_locate_bitmaps(const char *gamefile) {
1285 	const char *basename;
1286 	char *dirname;
1287 	BitmapType bitmap_type;
1288 
1289 	/* Find the start of the last element of the filename passed in. */
1290 	basename = gamefile;
1291 
1292 	/* Take a copy of the directory part of the filename. */
1293 	dirname = (char *)gln_malloc(basename - gamefile + 1);
1294 	strncpy(dirname, gamefile, basename - gamefile);
1295 	dirname[basename - gamefile] = '\0';
1296 
1297 	/*
1298 	 * Use the core interpreter to search for suitable bitmaps.  If none found,
1299 	 * free allocated memory and return noting none available.
1300 	 */
1301 	bitmap_type = DetectBitmaps(dirname);
1302 	if (bitmap_type == NO_BITMAPS) {
1303 		free(dirname);
1304 		gln_graphics_bitmap_directory = NULL;
1305 		gln_graphics_bitmap_type = NO_BITMAPS;
1306 		return;
1307 	}
1308 
1309 	/* Record the bitmap details for later use. */
1310 	gln_graphics_bitmap_directory = dirname;
1311 	gln_graphics_bitmap_type = bitmap_type;
1312 }
1313 
1314 
1315 /*
1316  * gln_graphics_handle_title_picture()
1317  *
1318  * Picture 0 is special, normally the title picture.  Unless we handle it
1319  * specially, the next picture comes along and instantly overwrites it.
1320  * Here, then, we try to delay until the picture has rendered, allowing the
1321  * delay to be broken with a keypress.
1322  */
gln_graphics_handle_title_picture()1323 static void gln_graphics_handle_title_picture() {
1324 	event_t event;
1325 	int count;
1326 
1327 	gln_standout_string("\n[ Press any key to skip the title picture... ]\n\n");
1328 
1329 	/* Wait until a keypress or graphics rendering is complete. */
1330 	g_vm->glk_request_char_event(gln_main_window);
1331 	do {
1332 		gln_event_wait_2(evtype_CharInput, evtype_Timer, &event);
1333 
1334 		/*
1335 		 * If a character was pressed, return.  This will let the game
1336 		 * progress, probably into showing the next bitmap.
1337 		 */
1338 		if (event.type == evtype_CharInput) {
1339 			gln_watchdog_tick();
1340 			return;
1341 		}
1342 	} while (gln_graphics_active);
1343 
1344 	/*
1345 	 * Now wait another couple of seconds, or until a keypress.  We'll do this
1346 	 * in graphics timeout chunks, so that if graphics restarts while we're
1347 	 * delaying, and it requests timer events and overwrites ours, we wind up
1348 	 * with the identical timer event period to the one we're expecting anyway.
1349 	 */
1350 	g_vm->glk_request_timer_events(GLN_GRAPHICS_TIMEOUT);
1351 	for (count = 0; count < GLN_GRAPHICS_TITLE_WAIT; count++) {
1352 		gln_event_wait_2(evtype_CharInput, evtype_Timer, &event);
1353 
1354 		if (event.type == evtype_CharInput)
1355 			break;
1356 	}
1357 
1358 	/*
1359 	 * While we waited, a Glk arrange or redraw event could have triggered
1360 	 * graphics into repainting, and using timers.  To handle this, stop timers
1361 	 * only if graphics is inactive.  If active, graphics will stop timers
1362 	 * itself when it finishes rendering.  We can't stop timers here while
1363 	 * graphics is active; that will hang the graphics "thread".
1364 	 */
1365 	if (!gln_graphics_active)
1366 		g_vm->glk_request_timer_events(0);
1367 
1368 	/* Cancel possible pending character event, and continue on. */
1369 	g_vm->glk_cancel_char_event(gln_main_window);
1370 	gln_watchdog_tick();
1371 }
1372 
1373 
1374 /*
1375  * os_show_bitmap()
1376  *
1377  * Called by the main interpreter when it wants us to display a picture.
1378  *
1379  * The function gets the picture bitmap, palette, and dimensions, and saves
1380  * them, and the picture id, in module variables for the background rendering
1381  * function.
1382  */
os_show_bitmap(int picture,int x,int y)1383 void os_show_bitmap(int picture, int x, int y) {
1384 	Bitmap *bitmap;
1385 	long picture_bytes;
1386 
1387 	/*
1388 	 * If interpreter graphics are disabled, the only way we can get into here
1389 	 * is using #picture.  It seems that the interpreter won't always deliver
1390 	 * correct bitmaps with #picture when in text mode, so it's simplest here
1391 	 * if we just ignore those calls.
1392 	 */
1393 	if (gln_graphics_interpreter_state != GLN_GRAPHICS_BITMAP_MODE)
1394 		return;
1395 
1396 	/* Ignore repeat calls for the currently displayed picture. */
1397 	if (picture == gln_graphics_picture)
1398 		return;
1399 
1400 	/*
1401 	 * Get the core interpreter's bitmap for the requested picture.  If this
1402 	 * returns NULL, the picture doesn't exist, so ignore the call silently.
1403 	 */
1404 	bitmap = DecodeBitmap(gln_graphics_bitmap_directory,
1405 	                      gln_graphics_bitmap_type, picture, x, y);
1406 	if (!bitmap)
1407 		return;
1408 
1409 	/*
1410 	 * Note the last thing passed to os_show_bitmap, to avoid possible repaints
1411 	 * of the current picture.
1412 	 */
1413 	gln_graphics_picture = picture;
1414 
1415 	/* Calculate the picture size in bytes. */
1416 	picture_bytes = bitmap->width * bitmap->height * sizeof(*bitmap->bitmap);
1417 
1418 	/*
1419 	 * Save the picture details for the update code.  Here we take a complete
1420 	 * local copy of the bitmap, dimensions, and palette.  The core interpreter
1421 	 * may return a palette with fewer colors than our maximum, so unused local
1422 	 * palette entries are set to zero.
1423 	 */
1424 	free(gln_graphics_bitmap);
1425 	gln_graphics_bitmap = (gln_byte *)gln_malloc(picture_bytes);
1426 	memcpy(gln_graphics_bitmap, bitmap->bitmap, picture_bytes);
1427 	gln_graphics_width = bitmap->width;
1428 	gln_graphics_height = bitmap->height;
1429 	memset(gln_graphics_palette, 0, sizeof(gln_graphics_palette));
1430 	memcpy(gln_graphics_palette, bitmap->palette,
1431 	       bitmap->npalette * sizeof(bitmap->palette[0]));
1432 
1433 	/*
1434 	 * If graphics are enabled, both at the Glk level and in the core
1435 	 * interpreter, ensure the window is displayed, set the appropriate flags,
1436 	 * and start graphics update.  If they're not enabled, the picture details
1437 	 * will simply stick around in module variables until they are required.
1438 	 */
1439 	if (gln_graphics_enabled
1440 	        && gln_graphics_interpreter_state == GLN_GRAPHICS_BITMAP_MODE) {
1441 		/*
1442 		 * Ensure graphics on, then set the new picture flag and start the
1443 		 * updating "thread".  If this is the title picture, start special
1444 		 * handling.
1445 		 */
1446 		if (gln_graphics_open()) {
1447 			gln_graphics_new_picture = TRUE;
1448 			gln_graphics_start();
1449 
1450 			if (picture == GLN_GRAPHICS_TITLE_PICTURE)
1451 				gln_graphics_handle_title_picture();
1452 		}
1453 	}
1454 }
1455 
1456 
1457 /*
1458  * gln_graphics_picture_is_available()
1459  *
1460  * Return TRUE if the graphics module data is loaded with a usable picture,
1461  * FALSE if there is no picture available to display.
1462  */
gln_graphics_picture_is_available()1463 static int gln_graphics_picture_is_available() {
1464 	return gln_graphics_bitmap != nullptr;
1465 }
1466 
1467 
1468 /*
1469  * gln_graphics_get_picture_details()
1470  *
1471  * Return the width and height of the currently loaded picture.  The function
1472  * returns FALSE if no picture is loaded, otherwise TRUE, with picture details
1473  * in the return arguments.
1474  */
gln_graphics_get_picture_details(int * width,int * height)1475 static int gln_graphics_get_picture_details(int *width, int *height) {
1476 	if (gln_graphics_picture_is_available()) {
1477 		if (width)
1478 			*width = gln_graphics_width;
1479 		if (height)
1480 			*height = gln_graphics_height;
1481 
1482 		return TRUE;
1483 	}
1484 
1485 	return FALSE;
1486 }
1487 
1488 
1489 /*
1490  * gln_graphics_get_rendering_details()
1491  *
1492  * Returns the type of bitmap in use (if any), as a string, the count of
1493  * colors in the picture, and a flag indicating if graphics is active (busy).
1494  * The function return FALSE if graphics is not enabled or if not being
1495  * displayed, otherwise TRUE with the bitmap type, color count, and active
1496  * flag in the return arguments.
1497  *
1498  * This function races with the graphics timeout, as it returns information
1499  * set up by the first timeout following a new picture.  There's a very
1500  * very small chance that it might win the race, in which case out-of-date
1501  * values are returned.
1502  */
gln_graphics_get_rendering_details(const char ** bitmap_type,int * color_count,int * is_active)1503 static int gln_graphics_get_rendering_details(const char **bitmap_type,
1504 		int *color_count, int *is_active) {
1505 	if (gln_graphics_enabled && gln_graphics_are_displayed()) {
1506 		/*
1507 		 * Convert the detected bitmap type into a string and return it.
1508 		 * A nullptr bitmap string implies no bitmaps.
1509 		 */
1510 		if (bitmap_type) {
1511 			const char *return_type;
1512 
1513 			switch (gln_graphics_bitmap_type) {
1514 			case AMIGA_BITMAPS:
1515 				return_type = "Amiga";
1516 				break;
1517 			case PC1_BITMAPS:
1518 				return_type = "IBM PC(1)";
1519 				break;
1520 			case PC2_BITMAPS:
1521 				return_type = "IBM PC(2)";
1522 				break;
1523 			case C64_BITMAPS:
1524 				return_type = "Commodore 64";
1525 				break;
1526 			case BBC_BITMAPS:
1527 				return_type = "BBC B";
1528 				break;
1529 			case CPC_BITMAPS:
1530 				return_type = "Amstrad CPC/Spectrum";
1531 				break;
1532 			case MAC_BITMAPS:
1533 				return_type = "Macintosh";
1534 				break;
1535 			case ST1_BITMAPS:
1536 				return_type = "Atari ST(1)";
1537 				break;
1538 			case ST2_BITMAPS:
1539 				return_type = "Atari ST(2)";
1540 				break;
1541 			case NO_BITMAPS:
1542 			default:
1543 				return_type = nullptr;
1544 				break;
1545 			}
1546 
1547 			*bitmap_type = return_type;
1548 		}
1549 
1550 		/*
1551 		 * Return the color count noted by timeouts on the first timeout
1552 		 * following a new picture.  We might return the one for the prior
1553 		 * picture.
1554 		 */
1555 		if (color_count)
1556 			*color_count = gln_graphics_color_count;
1557 
1558 		/* Return graphics active flag. */
1559 		if (is_active)
1560 			*is_active = gln_graphics_active;
1561 
1562 		return TRUE;
1563 	}
1564 
1565 	return FALSE;
1566 }
1567 
1568 
1569 /*
1570  * gln_graphics_interpreter_enabled()
1571  *
1572  * Return TRUE if it looks like interpreter graphics are turned on, FALSE
1573  * otherwise.
1574  */
gln_graphics_interpreter_enabled()1575 static int gln_graphics_interpreter_enabled() {
1576 	return gln_graphics_interpreter_state != GLN_GRAPHICS_OFF;
1577 }
1578 
1579 
1580 /*
1581  * gln_graphics_cleanup()
1582  *
1583  * Free memory resources allocated by graphics functions.  Called on game
1584  * end.
1585  */
gln_graphics_cleanup()1586 static void gln_graphics_cleanup() {
1587 	free(gln_graphics_bitmap);
1588 	gln_graphics_bitmap = nullptr;
1589 	free(gln_graphics_off_screen);
1590 	gln_graphics_off_screen = nullptr;
1591 	free(gln_graphics_on_screen);
1592 	gln_graphics_on_screen = nullptr;
1593 	free(gln_graphics_bitmap_directory);
1594 	gln_graphics_bitmap_directory = nullptr;
1595 
1596 	gln_graphics_bitmap_type = NO_BITMAPS;
1597 	gln_graphics_picture = -1;
1598 }
1599 
1600 
1601 /*---------------------------------------------------------------------*/
1602 /*  Glk port line drawing picture adapter functions                    */
1603 /*---------------------------------------------------------------------*/
1604 
1605 /*
1606  * Graphics color table.  These eight colors are selected into the four-
1607  * color palette by os_setcolour().  The standard Amiga palette is rather
1608  * over-vibrant, so to soften it a bit this table uses non-primary colors.
1609  */
1610 static const gln_rgb_t GLN_LINEGRAPHICS_COLOR_TABLE[] = {
1611 	{ 47,  79,  79},  /* DarkSlateGray  [Black] */
1612 	{238,  44,  44},  /* Firebrick2     [Red] */
1613 	{ 67, 205, 128},  /* SeaGreen3      [Green] */
1614 	{238, 201,   0},  /* Gold2          [Yellow] */
1615 	{ 92, 172, 238},  /* SteelBlue2     [Blue] */
1616 	{139,  87,  66},  /* LightSalmon4   [Brown] */
1617 	{175, 238, 238},  /* PaleTurquoise  [Cyan] */
1618 	{245, 245, 245},  /* WhiteSmoke     [White] */
1619 };
1620 
1621 /*
1622  * Structure of a Seed Fill segment entry, and a growable stack-based array
1623  * of segments pending fill.  When length exceeds size, size is increased
1624  * and the array grown.
1625  */
1626 struct gln_linegraphics_segment_t {
1627 	int y;   /* Segment y coordinate */
1628 	int xl;  /* Segment x left hand side coordinate */
1629 	int xr;  /* Segment x right hand side coordinate */
1630 	int dy;  /* Segment y delta */
1631 };
1632 
1633 static gln_linegraphics_segment_t *gln_linegraphics_fill_segments = nullptr;
1634 static int gln_linegraphics_fill_segments_allocation = 0,
1635 		   gln_linegraphics_fill_segments_length = 0;
1636 
1637 
1638 /*
1639  * gln_linegraphics_create_context()
1640  *
1641  * Initialize a new constructed bitmap graphics context for line drawn
1642  * graphics.
1643  */
gln_linegraphics_create_context()1644 static void gln_linegraphics_create_context() {
1645 	int width, height;
1646 	long picture_bytes;
1647 
1648 	/* Get the picture size, and calculate the bytes in the bitmap. */
1649 	GetPictureSize(&width, &height);
1650 	picture_bytes = width * height * sizeof(*gln_graphics_bitmap);
1651 
1652 	/*
1653 	 * Destroy any current bitmap, and begin a fresh one.  Here we set the
1654 	 * bitmap and the palette to all zeroes; this equates to all black.
1655 	 */
1656 	free(gln_graphics_bitmap);
1657 	gln_graphics_bitmap = (gln_byte *)gln_malloc(picture_bytes);
1658 	memset(gln_graphics_bitmap, 0, picture_bytes);
1659 	gln_graphics_width = width;
1660 	gln_graphics_height = height;
1661 	memset(gln_graphics_palette, 0, sizeof(gln_graphics_palette));
1662 
1663 	/* Set graphics picture number to -1; this is not a real game bitmap. */
1664 	gln_graphics_picture = -1;
1665 }
1666 
1667 
1668 /*
1669  * gln_linegraphics_clear_context()
1670  *
1671  * Clear the complete graphical drawing area, setting all pixels to zero,
1672  * and resetting the palette to all black as well.
1673  */
gln_linegraphics_clear_context()1674 static void gln_linegraphics_clear_context() {
1675 	long picture_bytes;
1676 
1677 	/* Get the picture size, and zero all bytes in the bitmap. */
1678 	picture_bytes = gln_graphics_width
1679 	                * gln_graphics_height * sizeof(*gln_graphics_bitmap);
1680 	memset(gln_graphics_bitmap, 0, picture_bytes);
1681 
1682 	/* Clear palette colors to all black. */
1683 	memset(gln_graphics_palette, 0, sizeof(gln_graphics_palette));
1684 }
1685 
1686 
1687 /*
1688  * gln_linegraphics_set_palette_color()
1689  *
1690  * Copy the indicated main color table entry into the palette.
1691  */
gln_linegraphics_set_palette_color(int colour,int index)1692 static void gln_linegraphics_set_palette_color(int colour, int index) {
1693 	const gln_rgb_t *entry;
1694 	assert(colour < GLN_PALETTE_SIZE);
1695 	assert(index < (int)sizeof(GLN_LINEGRAPHICS_COLOR_TABLE)
1696 	       / (int)sizeof(GLN_LINEGRAPHICS_COLOR_TABLE[0]));
1697 
1698 	/* Copy the color table entry to the constructed game palette. */
1699 	entry = GLN_LINEGRAPHICS_COLOR_TABLE + index;
1700 	gln_graphics_palette[colour].red   = entry->red;
1701 	gln_graphics_palette[colour].green = entry->green;
1702 	gln_graphics_palette[colour].blue  = entry->blue;
1703 }
1704 
1705 
1706 /*
1707  * gln_linegraphics_get_pixel()
1708  * gln_linegraphics_set_pixel()
1709  *
1710  * Return and set the bitmap pixel at x,y.
1711  */
gln_linegraphics_get_pixel(int x,int y)1712 static gln_byte gln_linegraphics_get_pixel(int x, int y) {
1713 	assert(x >= 0 && x < gln_graphics_width
1714 	       && y >= 0 && y < gln_graphics_height);
1715 
1716 	return gln_graphics_bitmap[y * gln_graphics_width + x];
1717 }
1718 
gln_linegraphics_set_pixel(int x,int y,gln_byte color)1719 static void gln_linegraphics_set_pixel(int x, int y, gln_byte color) {
1720 	assert(x >= 0 && x < gln_graphics_width
1721 	       && y >= 0 && y < gln_graphics_height);
1722 
1723 	gln_graphics_bitmap[y * gln_graphics_width + x] = color;
1724 }
1725 
1726 
1727 /*
1728  * gln_linegraphics_plot_clip()
1729  * gln_linegraphics_draw_line_if()
1730  *
1731  * Draw a line from x1,y1 to x2,y2 in colour1, where the existing pixel
1732  * colour is colour2.  The function uses Bresenham's algorithm.  The second
1733  * function, gln_graphics_plot_clip, is a line drawing helper; it handles
1734  * clipping, and the requirement to plot a point only if it matches colour2.
1735  */
gln_linegraphics_plot_clip(int x,int y,int colour1,int colour2)1736 static void gln_linegraphics_plot_clip(int x, int y, int colour1, int colour2) {
1737 	/*
1738 	 * Clip the plot if the value is outside the context.  Otherwise, plot the
1739 	 * pixel as colour1 if it is currently colour2.
1740 	 */
1741 	if (x >= 0 && x < gln_graphics_width && y >= 0 && y < gln_graphics_height) {
1742 		if (gln_linegraphics_get_pixel(x, y) == colour2)
1743 			gln_linegraphics_set_pixel(x, y, colour1);
1744 	}
1745 }
1746 
gln_linegraphics_draw_line_if(int x1,int y1,int x2,int y2,int colour1,int colour2)1747 static void gln_linegraphics_draw_line_if(int x1, int y1, int x2, int y2,
1748 		int colour1, int colour2) {
1749 	int x, y, dx, dy, incx, incy, balance;
1750 
1751 	/* Ignore any odd request where there will be no colour changes. */
1752 	if (colour1 == colour2)
1753 		return;
1754 
1755 	/* Normalize the line into deltas and increments. */
1756 	if (x2 >= x1) {
1757 		dx = x2 - x1;
1758 		incx = 1;
1759 	} else {
1760 		dx = x1 - x2;
1761 		incx = -1;
1762 	}
1763 
1764 	if (y2 >= y1) {
1765 		dy = y2 - y1;
1766 		incy = 1;
1767 	} else {
1768 		dy = y1 - y2;
1769 		incy = -1;
1770 	}
1771 
1772 	/* Start at x1,y1. */
1773 	x = x1;
1774 	y = y1;
1775 
1776 	/* Decide on a direction to progress in. */
1777 	if (dx >= dy) {
1778 		dy <<= 1;
1779 		balance = dy - dx;
1780 		dx <<= 1;
1781 
1782 		/* Loop until we reach the end point of the line. */
1783 		while (x != x2) {
1784 			gln_linegraphics_plot_clip(x, y, colour1, colour2);
1785 			if (balance >= 0) {
1786 				y += incy;
1787 				balance -= dx;
1788 			}
1789 			balance += dy;
1790 			x += incx;
1791 		}
1792 		gln_linegraphics_plot_clip(x, y, colour1, colour2);
1793 	} else {
1794 		dx <<= 1;
1795 		balance = dx - dy;
1796 		dy <<= 1;
1797 
1798 		/* Loop until we reach the end point of the line. */
1799 		while (y != y2) {
1800 			gln_linegraphics_plot_clip(x, y, colour1, colour2);
1801 			if (balance >= 0) {
1802 				x += incx;
1803 				balance -= dy;
1804 			}
1805 			balance += dx;
1806 			y += incy;
1807 		}
1808 		gln_linegraphics_plot_clip(x, y, colour1, colour2);
1809 	}
1810 }
1811 
1812 
1813 /*
1814  * gln_linegraphics_push_fill_segment()
1815  * gln_linegraphics_pop_fill_segment()
1816  * gln_linegraphics_fill_4way_if()
1817  *
1818  * Area fill algorithm, set a region to colour1 if it is currently set to
1819  * colour2.  This function is a derivation of Paul Heckbert's Seed Fill,
1820  * from "Graphics Gems", Academic Press, 1990, which fills 4-connected
1821  * neighbors.
1822  *
1823  * The main modification is to make segment stacks growable, through the
1824  * helper push and pop functions.  There is also a small adaptation to
1825  * check explicitly for color2, to meet the Level 9 API.
1826  */
gln_linegraphics_push_fill_segment(int y,int xl,int xr,int dy)1827 static void gln_linegraphics_push_fill_segment(int y, int xl, int xr, int dy) {
1828 	/* Clip points outside the graphics context. */
1829 	if (!(y + dy < 0 || y + dy >= gln_graphics_height)) {
1830 		int length, allocation;
1831 
1832 		length = ++gln_linegraphics_fill_segments_length;
1833 		allocation = gln_linegraphics_fill_segments_allocation;
1834 
1835 		/* Grow the segments stack if required, successively doubling. */
1836 		if (length > allocation) {
1837 			size_t bytes;
1838 
1839 			allocation = allocation == 0 ? 1 : allocation << 1;
1840 
1841 			bytes = allocation * sizeof(*gln_linegraphics_fill_segments);
1842 			gln_linegraphics_fill_segments =
1843 			    (gln_linegraphics_segment_t *)gln_realloc(gln_linegraphics_fill_segments, bytes);
1844 		}
1845 
1846 		/* Push top of segments stack. */
1847 		gln_linegraphics_fill_segments[length - 1].y  = y;
1848 		gln_linegraphics_fill_segments[length - 1].xl = xl;
1849 		gln_linegraphics_fill_segments[length - 1].xr = xr;
1850 		gln_linegraphics_fill_segments[length - 1].dy = dy;
1851 
1852 		/* Write back local dimensions copies. */
1853 		gln_linegraphics_fill_segments_length = length;
1854 		gln_linegraphics_fill_segments_allocation = allocation;
1855 	}
1856 }
1857 
gln_linegraphics_pop_fill_segment(int * y,int * xl,int * xr,int * dy)1858 static void gln_linegraphics_pop_fill_segment(int *y, int *xl, int *xr, int *dy) {
1859 	int length;
1860 	assert(gln_linegraphics_fill_segments_length > 0);
1861 
1862 	length = --gln_linegraphics_fill_segments_length;
1863 
1864 	/* Pop top of segments stack. */
1865 	*y  = gln_linegraphics_fill_segments[length].y;
1866 	*xl = gln_linegraphics_fill_segments[length].xl;
1867 	*xr = gln_linegraphics_fill_segments[length].xr;
1868 	*dy = gln_linegraphics_fill_segments[length].dy;
1869 }
1870 
gln_linegraphics_fill_4way_if(int x,int y,int colour1,int colour2)1871 static void gln_linegraphics_fill_4way_if(int x, int y, int colour1, int colour2) {
1872 	/* Ignore any odd request where there will be no colour changes. */
1873 	if (colour1 == colour2)
1874 		return;
1875 
1876 	/* Clip fill requests to visible graphics region. */
1877 	if (x >= 0 && x < gln_graphics_width && y >= 0 && y < gln_graphics_height) {
1878 		int left, x1, x2, dy, x_lo, x_hi;
1879 
1880 		/*
1881 		 * Level 9 API; explicit check for a match against colour2.  This also
1882 		 * covers the standard Seed Fill check that old pixel value should not
1883 		 * equal colour1, because of the color1 == colour2 comparison above.
1884 		 */
1885 		if (gln_linegraphics_get_pixel(x, y) != colour2)
1886 			return;
1887 
1888 		/*
1889 		 * Set up inclusive window dimension to ease algorithm translation.
1890 		 * The original worked with inclusive rectangle limits.
1891 		 */
1892 		x_lo = 0;
1893 		x_hi = gln_graphics_width - 1;
1894 
1895 		/*
1896 		 * The first of these is "needed in some cases", the second is the seed
1897 		 * segment, popped first.
1898 		 */
1899 		gln_linegraphics_push_fill_segment(y, x, x, 1);
1900 		gln_linegraphics_push_fill_segment(y + 1, x, x, -1);
1901 
1902 		while (gln_linegraphics_fill_segments_length > 0) {
1903 			/* Pop segment off stack and add delta to y coord. */
1904 			gln_linegraphics_pop_fill_segment(&y, &x1, &x2, &dy);
1905 			y += dy;
1906 
1907 			/*
1908 			 * Segment of scan line y-dy for x1<=x<=x2 was previously filled,
1909 			 * now explore adjacent pixels in scan line y.
1910 			 */
1911 			for (x = x1;
1912 			        x >= x_lo && gln_linegraphics_get_pixel(x, y) == colour2;
1913 			        x--) {
1914 				gln_linegraphics_set_pixel(x, y, colour1);
1915 			}
1916 
1917 			if (x >= x1)
1918 				goto skip;
1919 
1920 			left = x + 1;
1921 			if (left < x1) {
1922 				/* Leak on left? */
1923 				gln_linegraphics_push_fill_segment(y, left, x1 - 1, -dy);
1924 			}
1925 
1926 			x = x1 + 1;
1927 			do {
1928 				for (;
1929 				        x <= x_hi && gln_linegraphics_get_pixel(x, y) == colour2;
1930 				        x++) {
1931 					gln_linegraphics_set_pixel(x, y, colour1);
1932 				}
1933 
1934 				gln_linegraphics_push_fill_segment(y, left, x - 1, dy);
1935 
1936 				if (x > x2 + 1) {
1937 					/* Leak on right? */
1938 					gln_linegraphics_push_fill_segment(y, x2 + 1, x - 1, -dy);
1939 				}
1940 
1941 skip:
1942 				for (x++;
1943 				        x <= x2 && gln_linegraphics_get_pixel(x, y) != colour2;
1944 				        x++)
1945 					;
1946 
1947 				left = x;
1948 			} while (x <= x2);
1949 		}
1950 	}
1951 }
1952 
1953 
1954 /*
1955  * os_cleargraphics()
1956  * os_setcolour()
1957  * os_drawline()
1958  * os_fill()
1959  *
1960  * Interpreter entry points for line drawing graphics.  All calls to these
1961  * are ignored if line drawing mode is not set.
1962  */
os_cleargraphics()1963 void os_cleargraphics() {
1964 	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
1965 		gln_linegraphics_clear_context();
1966 }
1967 
os_setcolour(int colour,int index)1968 void os_setcolour(int colour, int index) {
1969 	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
1970 		gln_linegraphics_set_palette_color(colour, index);
1971 }
1972 
os_drawline(int x1,int y1,int x2,int y2,int colour1,int colour2)1973 void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) {
1974 	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
1975 		gln_linegraphics_draw_line_if(x1, y1, x2, y2, colour1, colour2);
1976 }
1977 
os_fill(int x,int y,int colour1,int colour2)1978 void os_fill(int x, int y, int colour1, int colour2) {
1979 	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
1980 		gln_linegraphics_fill_4way_if(x, y, colour1, colour2);
1981 }
1982 
1983 
1984 /*
1985  * gln_linegraphics_process()
1986  *
1987  * Process as many graphics opcodes as are available, constructing the
1988  * resulting image as a bitmap.  When complete, treat as normal bitmaps.
1989  */
gln_linegraphics_process()1990 static void gln_linegraphics_process() {
1991 	/*
1992 	 * If interpreter graphics are not set to line mode, ignore any call that
1993 	 * arrives here.
1994 	 */
1995 	if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE) {
1996 		int opcodes_count;
1997 
1998 		/* Run all the available graphics opcodes. */
1999 		for (opcodes_count = 0; RunGraphics();) {
2000 			opcodes_count++;
2001 			g_vm->glk_tick();
2002 		}
2003 
2004 		/*
2005 		 * If graphics is enabled and we created an image with graphics
2006 		 * opcodes above, open a graphics window and start bitmap display.
2007 		 */
2008 		if (gln_graphics_enabled && opcodes_count > 0) {
2009 			if (gln_graphics_open()) {
2010 				/* Set the new picture flag, and start the updating "thread". */
2011 				gln_graphics_new_picture = TRUE;
2012 				gln_graphics_start();
2013 			}
2014 		}
2015 	}
2016 }
2017 
2018 
2019 /*
2020  * gln_linegraphics_cleanup()
2021  *
2022  * Free memory resources allocated by line graphics functions.  Called on
2023  * game end.
2024  */
gln_linegraphics_cleanup()2025 static void gln_linegraphics_cleanup() {
2026 	free(gln_linegraphics_fill_segments);
2027 	gln_linegraphics_fill_segments = nullptr;
2028 
2029 	gln_linegraphics_fill_segments_allocation = 0;
2030 	gln_linegraphics_fill_segments_length = 0;
2031 }
2032 
2033 
2034 /*---------------------------------------------------------------------*/
2035 /*  Glk picture dispatch (bitmap or line), and timer arbitration       */
2036 /*---------------------------------------------------------------------*/
2037 
2038 /*
2039  * Note of the current set graphics mode, to detect changes in mode from
2040  * the core interpreter.
2041  */
2042 static int gln_graphics_current_mode = -1;
2043 
2044 /* Note indicating if the graphics "thread" is temporarily suspended. */
2045 static int gln_graphics_suspended = FALSE;
2046 
2047 
2048 /*
2049  * os_graphics()
2050  *
2051  * Called by the main interpreter to turn graphics on and off.  Mode 0
2052  * turns graphics off, mode 1 is line drawing graphics, and mode 2 is
2053  * bitmap graphics.
2054  *
2055  * This function tracks the current state of interpreter graphics setting
2056  * using gln_graphics_interpreter_state.
2057  */
os_graphics(int mode)2058 void os_graphics(int mode) {
2059 	/* Ignore the call unless it changes the graphics mode. */
2060 	if (mode != gln_graphics_current_mode) {
2061 		/* Set tracked interpreter state given the input mode. */
2062 		switch (mode) {
2063 		case 0:
2064 			gln_graphics_interpreter_state = GLN_GRAPHICS_OFF;
2065 			break;
2066 
2067 		case 1:
2068 			gln_graphics_interpreter_state = GLN_GRAPHICS_LINE_MODE;
2069 			break;
2070 
2071 		case 2:
2072 			/* If no graphics bitmaps were detected, ignore this call. */
2073 			if (!gln_graphics_bitmap_directory
2074 			        || gln_graphics_bitmap_type == NO_BITMAPS)
2075 				return;
2076 
2077 			gln_graphics_interpreter_state = GLN_GRAPHICS_BITMAP_MODE;
2078 			break;
2079 		}
2080 
2081 		/* Given the interpreter state, update graphics activities. */
2082 		switch (gln_graphics_interpreter_state) {
2083 		case GLN_GRAPHICS_OFF:
2084 
2085 			/* If currently displaying graphics, stop and close window. */
2086 			if (gln_graphics_enabled && gln_graphics_are_displayed()) {
2087 				gln_graphics_stop();
2088 				gln_graphics_close();
2089 			}
2090 			break;
2091 
2092 		case GLN_GRAPHICS_LINE_MODE:
2093 		case GLN_GRAPHICS_BITMAP_MODE:
2094 
2095 			/* Create a new graphics context on switch to line mode. */
2096 			if (gln_graphics_interpreter_state == GLN_GRAPHICS_LINE_MODE)
2097 				gln_linegraphics_create_context();
2098 
2099 			/*
2100 			 * If we have a picture loaded already, restart graphics. If not,
2101 			 * we'll delay this until one is supplied by a call to
2102 			 * os_show_bitmap().
2103 			 */
2104 			if (gln_graphics_enabled && gln_graphics_bitmap) {
2105 				if (gln_graphics_open())
2106 					gln_graphics_restart();
2107 			}
2108 			break;
2109 		}
2110 
2111 		/* Note the current mode so changes can be detected. */
2112 		gln_graphics_current_mode = mode;
2113 	}
2114 }
2115 
2116 
2117 /*
2118  * gln_arbitrate_request_timer_events()
2119  *
2120  * Shim function for g_vm->glk_request_timer_events(), this function should be
2121  * called by other functional areas in place of the main timer event setting
2122  * function.  It suspends graphics if busy when setting timer events, and
2123  * resumes graphics if necessary when clearing timer events.
2124  *
2125  * On resuming, it calls the graphics timeout function to simulate the
2126  * timeout that has (probably) been missed.  This also ensures that tight
2127  * loops that enable then disable timers using this function don't lock out
2128  * the graphics completely.
2129  *
2130  * Use only in paired calls, the first non-zero, the second zero, and use
2131  * no graphics functions between calls.
2132  */
gln_arbitrate_request_timer_events(glui32 millisecs)2133 static void gln_arbitrate_request_timer_events(glui32 millisecs) {
2134 	if (millisecs > 0) {
2135 		/* Setting timer events; suspend graphics if currently active. */
2136 		if (gln_graphics_active) {
2137 			gln_graphics_suspended = TRUE;
2138 			gln_graphics_stop();
2139 		}
2140 
2141 		/* Set timer events as requested. */
2142 		g_vm->glk_request_timer_events(millisecs);
2143 	} else {
2144 		/*
2145 		 * Resume graphics if currently suspended, otherwise cancel timer
2146 		 * events as requested by the caller.
2147 		 */
2148 		if (gln_graphics_suspended) {
2149 			gln_graphics_suspended = FALSE;
2150 			gln_graphics_start();
2151 
2152 			/* Simulate the "missed" graphics timeout. */
2153 			gln_graphics_timeout();
2154 		} else
2155 			g_vm->glk_request_timer_events(0);
2156 	}
2157 }
2158 
2159 
2160 /*---------------------------------------------------------------------*/
2161 /*  Glk port infinite loop detection functions                         */
2162 /*---------------------------------------------------------------------*/
2163 
2164 /* Short timeout to wait purely in order to get the display updated. */
2165 static const glui32 GLN_WATCHDOG_FIXUP = 50;
2166 
2167 /*
2168  * Timestamp of the last watchdog tick call, and timeout.  This is used to
2169  * monitor the elapsed time since the interpreter made an I/O call.  If it
2170  * remains silent for long enough, set by the timeout, we'll offer the
2171  * option to end the game.  A timeout of zero disables the watchdog.
2172  */
2173 static uint32 gln_watchdog_monitor = 0;
2174 static double gln_watchdog_timeout_secs = 0.0;
2175 
2176 /*
2177  * To save thrashing in time(), we want to check for timeouts less frequently
2178  * than we're polled.  Here's the control for that.
2179  */
2180 static int gln_watchdog_check_period = 0,
2181 		   gln_watchdog_check_counter = 0;
2182 
2183 
2184 /*
2185  * gln_watchdog_start()
2186  * gln_watchdog_stop()
2187  *
2188  * Start and stop watchdog monitoring.
2189  */
gln_watchdog_start(int timeout,int period)2190 static void gln_watchdog_start(int timeout, int period) {
2191 	assert(timeout > 0 && period > 0);
2192 
2193 	gln_watchdog_timeout_secs = (double) timeout;
2194 	gln_watchdog_check_period = period;
2195 	gln_watchdog_check_counter = period;
2196 	gln_watchdog_monitor = g_system->getMillis();
2197 }
2198 
gln_watchdog_stop()2199 static void gln_watchdog_stop() {
2200 	gln_watchdog_timeout_secs = 0;
2201 }
2202 
2203 
2204 /*
2205  * gln_watchdog_tick()
2206  *
2207  * Set the watchdog timestamp to the current system time.
2208  *
2209  * This function should be called just before almost every os_* function
2210  * returns to the interpreter, as a means of timing how long the interpreter
2211  * dwells in running game code.
2212  */
gln_watchdog_tick()2213 static void gln_watchdog_tick() {
2214 	gln_watchdog_monitor = g_system->getMillis();
2215 }
2216 
2217 
2218 /*
2219  * gln_watchdog_has_timed_out()
2220  *
2221  * Check to see if too much time has elapsed since the last tick.  If it has,
2222  * offer the option to stop the game, and if accepted, return TRUE.  Otherwise,
2223  * if no timeout, or if the watchdog is disabled, return FALSE.
2224  *
2225  * This function only checks every N calls; it's called extremely frequently
2226  * from opcode handling, and will thrash in time() if it checks on each call.
2227  */
gln_watchdog_has_timed_out()2228 static int gln_watchdog_has_timed_out() {
2229 	/* If loop detection is off or the timeout is set to zero, do nothing. */
2230 	if (gln_loopcheck_enabled && gln_watchdog_timeout_secs > 0) {
2231 		uint32 now;
2232 		double delta_time;
2233 
2234 		/*
2235 		 * Wait until we've seen enough calls to make a timeout check.  If we
2236 		 * haven't, return FALSE, otherwise reset the counter and continue.
2237 		 */
2238 		if (--gln_watchdog_check_counter > 0)
2239 			return FALSE;
2240 		else
2241 			gln_watchdog_check_counter = gln_watchdog_check_period;
2242 
2243 		/*
2244 		 * Determine how much time has passed, and offer to end the game if it
2245 		 * exceeds the allowable timeout.
2246 		 */
2247 		now = g_system->getMillis();
2248 		delta_time = (now - gln_watchdog_monitor) / 1000;
2249 
2250 		if (delta_time >= gln_watchdog_timeout_secs) {
2251 			if (gln_confirm("\nThe game may be in an infinite loop.  Do you"
2252 			                " want to stop it? [Y or N] ")) {
2253 				gln_watchdog_tick();
2254 				return TRUE;
2255 			}
2256 
2257 			/*
2258 			 * If we have timers, set a really short timeout and let it expire.
2259 			 * This is to force a display update with the response of the
2260 			 * confirm -- without this, we may not get a screen update for a
2261 			 * while since at this point the game isn't, by definition, doing
2262 			 * any input or output.  If we don't have timers, no biggie.
2263 			 */
2264 			if (g_vm->glk_gestalt(gestalt_Timer, 0)) {
2265 				event_t event;
2266 
2267 				gln_arbitrate_request_timer_events(GLN_WATCHDOG_FIXUP);
2268 				gln_event_wait(evtype_Timer, &event);
2269 				gln_arbitrate_request_timer_events(0);
2270 			}
2271 
2272 			/* Reset the monitor and drop into FALSE return -- stop rejected. */
2273 			gln_watchdog_tick();
2274 		}
2275 	}
2276 
2277 	/* No timeout indicated, or offer rejected by the user. */
2278 	return FALSE;
2279 }
2280 
2281 
2282 /*---------------------------------------------------------------------*/
2283 /*  Glk port status line functions                                     */
2284 /*---------------------------------------------------------------------*/
2285 
2286 /* Default width used for non-windowing Glk status lines. */
2287 static const int GLN_DEFAULT_STATUS_WIDTH = 74;
2288 
2289 
2290 /*
2291  * gln_status_update()
2292  *
2293  * Update the information in the status window with the current contents of
2294  * the current game identity string, or a default string if no game identity
2295  * could be established.
2296  */
gln_status_update()2297 static void gln_status_update() {
2298 	uint width, height;
2299 	assert(gln_status_window);
2300 
2301 	g_vm->glk_window_get_size(gln_status_window, &width, &height);
2302 	if (height > 0) {
2303 		const char *game_name;
2304 
2305 		g_vm->glk_window_clear(gln_status_window);
2306 		g_vm->glk_window_move_cursor(gln_status_window, 0, 0);
2307 		g_vm->glk_set_window(gln_status_window);
2308 
2309 		/*
2310 		 * Try to establish a game identity to display; if none, use a standard
2311 		 * message instead.
2312 		 */
2313 		game_name = g_vm->_detection._gameName;
2314 		g_vm->glk_put_string(game_name ? game_name : "ScummVM GLK Level 9 Game");
2315 
2316 		g_vm->glk_set_window(gln_main_window);
2317 	}
2318 }
2319 
2320 
2321 /*
2322  * gln_status_print()
2323  *
2324  * Print the current contents of the game identity out in the main window,
2325  * if it has changed since the last call.  This is for non-windowing Glk
2326  * libraries.
2327  *
2328  * To save memory management hassles, this function uses the CRC functions
2329  * to detect changes of game identity string, and gambles a little on the
2330  * belief that two games' strings won't have the same CRC.
2331  */
gln_status_print()2332 static void gln_status_print() {
2333 	static int is_initialized = FALSE;
2334 	static gln_uint16 crc = 0;
2335 
2336 	const char *game_name;
2337 
2338 	/* Get the current game name, and do nothing if none available. */
2339 	game_name = g_vm->_detection._gameName;
2340 	if (game_name) {
2341 		gln_uint16 new_crc;
2342 
2343 		/*
2344 		 * If not the first call and the game identity string has not changed,
2345 		 * again, do nothing.
2346 		 */
2347 		new_crc = g_vm->_detection.gln_get_buffer_crc(game_name, strlen(game_name));
2348 		if (!is_initialized || new_crc != crc) {
2349 			int index;
2350 
2351 #ifndef GARGLK
2352 			/* Set fixed width font to try to preserve status line formatting. */
2353 			g_vm->glk_set_style(style_Preformatted);
2354 #endif
2355 
2356 			/* Bracket, and output the extracted game name. */
2357 			g_vm->glk_put_string("[ ");
2358 			g_vm->glk_put_string(game_name);
2359 
2360 			for (index = strlen(game_name);
2361 			        index <= GLN_DEFAULT_STATUS_WIDTH; index++)
2362 				g_vm->glk_put_char(' ');
2363 			g_vm->glk_put_string(" ]\n");
2364 
2365 			crc = new_crc;
2366 			is_initialized = TRUE;
2367 		}
2368 	}
2369 }
2370 
2371 
2372 /*
2373  * gln_status_notify()
2374  *
2375  * Front end function for updating status.  Either updates the status window
2376  * or prints the status line to the main window.
2377  */
gln_status_notify()2378 static void gln_status_notify() {
2379 	if (gln_status_window)
2380 		gln_status_update();
2381 	else
2382 		gln_status_print();
2383 }
2384 
2385 
2386 /*
2387  * gln_status_redraw()
2388  *
2389  * Redraw the contents of any status window with the buffered status string.
2390  * This function should be called on the appropriate Glk window resize and
2391  * arrange events.
2392  */
gln_status_redraw()2393 static void gln_status_redraw() {
2394 	if (gln_status_window) {
2395 		winid_t parent;
2396 
2397 		/*
2398 		 * Rearrange the status window, without changing its actual arrangement
2399 		 * in any way.  This is a hack to work round incorrect window repainting
2400 		 * in Xglk; it forces a complete repaint of affected windows on Glk
2401 		 * window resize and arrange events, and works in part because Xglk
2402 		 * doesn't check for actual arrangement changes in any way before
2403 		 * invalidating its windows.  The hack should be harmless to Glk
2404 		 * libraries other than Xglk, moreover, we're careful to activate it
2405 		 * only on resize and arrange events.
2406 		 */
2407 		parent = g_vm->glk_window_get_parent(gln_status_window);
2408 		g_vm->glk_window_set_arrangement(parent,
2409 		                                 winmethod_Above | winmethod_Fixed, 1, nullptr);
2410 
2411 		gln_status_update();
2412 	}
2413 }
2414 
2415 
2416 /*---------------------------------------------------------------------*/
2417 /*  Glk port output functions                                          */
2418 /*---------------------------------------------------------------------*/
2419 
2420 /*
2421  * Flag for if the user entered "help" as their last input, or if hints have
2422  * been silenced as a result of already using a Glk command.
2423  */
2424 static int gln_help_requested = FALSE,
2425 		   gln_help_hints_silenced = FALSE;
2426 
2427 /*
2428  * Output buffer.  We receive characters one at a time, and it's a bit
2429  * more efficient for everyone if we buffer them, and output a complete
2430  * string on a flush call.
2431  */
2432 static char *gln_output_buffer = nullptr;
2433 static int gln_output_allocation = 0,
2434 		   gln_output_length = 0;
2435 
2436 /*
2437  * Output activity flag.  Set when os_printchar() is called, and queried
2438  * periodically by os_readchar().  Helps os_readchar() judge whether it must
2439  * request input, or when it's being used as a crude scroll control.
2440  */
2441 static int gln_output_activity = FALSE;
2442 
2443 /*
2444  * Flag to indicate if the last buffer flushed looked like it ended in a
2445  * "> " prompt.  Some later games switch to this mode after a while, and
2446  * it's nice not to duplicate this prompt with our own.
2447  */
2448 static int gln_output_prompt = FALSE;
2449 
2450 
2451 /*
2452  * gln_output_notify()
2453  *
2454  * Register recent text output from the interpreter.  This function is
2455  * called by os_printchar().
2456  */
gln_output_notify()2457 static void gln_output_notify() {
2458 	gln_output_activity = TRUE;
2459 }
2460 
2461 
2462 /*
2463  * gln_recent_output()
2464  *
2465  * Return TRUE if the interpreter has recently output text, FALSE otherwise.
2466  * Clears the flag, so that more output text is required before the next
2467  * call returns TRUE.
2468  */
gln_recent_output()2469 static int gln_recent_output() {
2470 	int result;
2471 
2472 	result = gln_output_activity;
2473 	gln_output_activity = FALSE;
2474 
2475 	return result;
2476 }
2477 
2478 
2479 /*
2480  * gln_output_register_help_request()
2481  * gln_output_silence_help_hints()
2482  * gln_output_provide_help_hint()
2483  *
2484  * Register a request for help, and print a note of how to get Glk command
2485  * help from the interpreter unless silenced.
2486  */
gln_output_register_help_request()2487 static void gln_output_register_help_request() {
2488 	gln_help_requested = TRUE;
2489 }
2490 
gln_output_silence_help_hints()2491 static void gln_output_silence_help_hints() {
2492 	gln_help_hints_silenced = TRUE;
2493 }
2494 
gln_output_provide_help_hint()2495 static void gln_output_provide_help_hint() {
2496 	if (gln_help_requested && !gln_help_hints_silenced) {
2497 		g_vm->glk_set_style(style_Emphasized);
2498 		g_vm->glk_put_string("[Try 'glk help' for help on special interpreter"
2499 		                     " commands]\n");
2500 
2501 		gln_help_requested = FALSE;
2502 		g_vm->glk_set_style(style_Normal);
2503 	}
2504 }
2505 
2506 
2507 /*
2508  * gln_game_prompted()
2509  *
2510  * Return TRUE if the last game output appears to have been a "> " prompt.
2511  * Once called, the flag is reset to FALSE, and requires more game output
2512  * to set it again.
2513  */
gln_game_prompted()2514 static int gln_game_prompted() {
2515 	int result;
2516 
2517 	result = gln_output_prompt;
2518 	gln_output_prompt = FALSE;
2519 
2520 	return result;
2521 }
2522 
2523 
2524 /*
2525  * gln_detect_game_prompt()
2526  *
2527  * See if the last non-newline-terminated line in the output buffer seems
2528  * to be a prompt, and set the game prompted flag if it does, otherwise
2529  * clear it.
2530  */
gln_detect_game_prompt()2531 static void gln_detect_game_prompt() {
2532 	int index;
2533 
2534 	gln_output_prompt = FALSE;
2535 
2536 	/*
2537 	 * Search for a prompt across any last unterminated buffered line; a prompt
2538 	 * is any non-space character on that line.
2539 	 */
2540 	for (index = gln_output_length - 1;
2541 	        index >= 0 && gln_output_buffer[index] != '\n'; index--) {
2542 		if (gln_output_buffer[index] != ' ') {
2543 			gln_output_prompt = TRUE;
2544 			break;
2545 		}
2546 	}
2547 }
2548 
2549 
2550 /*
2551  * gln_output_delete()
2552  *
2553  * Delete all buffered output text.  Free all malloc'ed buffer memory, and
2554  * return the buffer variables to their initial values.
2555  */
gln_output_delete()2556 static void gln_output_delete() {
2557 	free(gln_output_buffer);
2558 	gln_output_buffer = nullptr;
2559 	gln_output_allocation = gln_output_length = 0;
2560 }
2561 
2562 
2563 /*
2564  * gln_output_flush()
2565  *
2566  * Flush any buffered output text to the Glk main window, and clear the
2567  * buffer.  Check in passing for game prompts that duplicate our's.
2568  */
gln_output_flush()2569 static void gln_output_flush() {
2570 	assert(g_vm->glk_stream_get_current());
2571 
2572 	if (gln_output_length > 0) {
2573 		/*
2574 		 * See if the game issued a standard prompt, then print the buffer to
2575 		 * the main window.  If providing a help hint, position that before
2576 		 * the game's prompt (if any).
2577 		 */
2578 		gln_detect_game_prompt();
2579 
2580 		if (gln_output_prompt) {
2581 			int index;
2582 
2583 			for (index = gln_output_length - 1;
2584 			        index >= 0 && gln_output_buffer[index] != '\n';)
2585 				index--;
2586 
2587 			g_vm->glk_put_buffer(gln_output_buffer, index + 1);
2588 			gln_output_provide_help_hint();
2589 			g_vm->glk_put_buffer(gln_output_buffer + index + 1,
2590 			                     gln_output_length - index - 1);
2591 		} else {
2592 			g_vm->glk_put_buffer(gln_output_buffer, gln_output_length);
2593 			gln_output_provide_help_hint();
2594 		}
2595 
2596 		gln_output_delete();
2597 	}
2598 }
2599 
2600 
2601 /*
2602  * os_printchar()
2603  *
2604  * Buffer a character for eventual printing to the main window.
2605  */
os_printchar(char c)2606 void os_printchar(char c) {
2607 	int bytes;
2608 	assert(gln_output_length <= gln_output_allocation);
2609 
2610 	/* Grow the output buffer if necessary. */
2611 	for (bytes = gln_output_allocation; bytes < gln_output_length + 1;)
2612 		bytes = bytes == 0 ? 1 : bytes << 1;
2613 
2614 	if (bytes > gln_output_allocation) {
2615 		gln_output_buffer = (char *)gln_realloc(gln_output_buffer, bytes);
2616 		gln_output_allocation = bytes;
2617 	}
2618 
2619 	/*
2620 	 * Add the character to the buffer, handling return as a newline, and
2621 	 * note that the game created some output.
2622 	 */
2623 	gln_output_buffer[gln_output_length++] = (c == '\r' ? '\n' : c);
2624 	gln_output_notify();
2625 }
2626 
2627 
2628 /*
2629  * gln_styled_string()
2630  * gln_styled_char()
2631  * gln_standout_string()
2632  * gln_standout_char()
2633  * gln_normal_string()
2634  * gln_normal_char()
2635  * gln_header_string()
2636  * gln_banner_string()
2637  *
2638  * Convenience functions to print strings in assorted styles.  A standout
2639  * string is one that hints that it's from the interpreter, not the game.
2640  */
gln_styled_string(glui32 style,const char * message)2641 static void gln_styled_string(glui32 style, const char *message) {
2642 	assert(message);
2643 
2644 	g_vm->glk_set_style(style);
2645 	g_vm->glk_put_string(message);
2646 	g_vm->glk_set_style(style_Normal);
2647 }
2648 
gln_styled_char(glui32 style,char c)2649 static void gln_styled_char(glui32 style, char c) {
2650 	char buffer[2];
2651 
2652 	buffer[0] = c;
2653 	buffer[1] = '\0';
2654 	gln_styled_string(style, buffer);
2655 }
2656 
gln_standout_string(const char * message)2657 static void gln_standout_string(const char *message) {
2658 	gln_styled_string(style_Emphasized, message);
2659 }
2660 
2661 #ifndef GARGLK
2662 
gln_standout_char(char c)2663 static void gln_standout_char(char c) {
2664 	gln_styled_char(style_Emphasized, c);
2665 }
2666 
2667 #endif
2668 
gln_normal_string(const char * message)2669 static void gln_normal_string(const char *message) {
2670 	gln_styled_string(style_Normal, message);
2671 }
2672 
gln_normal_char(char c)2673 static void gln_normal_char(char c) {
2674 	gln_styled_char(style_Normal, c);
2675 }
2676 
gln_header_string(const char * message)2677 static void gln_header_string(const char *message) {
2678 	gln_styled_string(style_Header, message);
2679 }
2680 
2681 #ifndef GARGLK
2682 
gln_banner_string(const char * message)2683 static void gln_banner_string(const char *message) {
2684 	gln_styled_string(style_Subheader, message);
2685 }
2686 
2687 #endif
2688 
2689 
2690 /*
2691  * os_flush()
2692  *
2693  * Handle a core interpreter call to flush the output buffer.  Because Glk
2694  * only flushes its buffers and displays text on g_vm->glk_select(), we can ignore
2695  * these calls as long as we call g_vm->glk_output_flush() when reading line or
2696  * character input.
2697  *
2698  * Taking os_flush() at face value can cause game text to appear before status
2699  * line text where we are working with a non-windowing Glk, so it's best
2700  * ignored where we can.
2701  */
os_flush()2702 void os_flush() {
2703 }
2704 
2705 
2706 /*---------------------------------------------------------------------*/
2707 /*  Glk command escape functions                                       */
2708 /*---------------------------------------------------------------------*/
2709 
2710 /*
2711  * gln_command_script()
2712  *
2713  * Turn game output scripting (logging) on and off.
2714  */
gln_command_script(const char * argument)2715 static void gln_command_script(const char *argument) {
2716 	assert(argument);
2717 
2718 	if (gln_strcasecmp(argument, "on") == 0) {
2719 		frefid_t fileref;
2720 
2721 		if (gln_transcript_stream) {
2722 			gln_normal_string("Glk transcript is already on.\n");
2723 			return;
2724 		}
2725 
2726 		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_Transcript
2727 		          | fileusage_TextMode,
2728 		          filemode_WriteAppend, 0);
2729 		if (!fileref) {
2730 			gln_standout_string("Glk transcript failed.\n");
2731 			return;
2732 		}
2733 
2734 		gln_transcript_stream = g_vm->glk_stream_open_file(fileref,
2735 		                        filemode_WriteAppend, 0);
2736 		g_vm->glk_fileref_destroy(fileref);
2737 		if (!gln_transcript_stream) {
2738 			gln_standout_string("Glk transcript failed.\n");
2739 			return;
2740 		}
2741 
2742 		g_vm->glk_window_set_echo_stream(gln_main_window, gln_transcript_stream);
2743 
2744 		gln_normal_string("Glk transcript is now on.\n");
2745 	}
2746 
2747 	else if (gln_strcasecmp(argument, "off") == 0) {
2748 		if (!gln_transcript_stream) {
2749 			gln_normal_string("Glk transcript is already off.\n");
2750 			return;
2751 		}
2752 
2753 		g_vm->glk_stream_close(gln_transcript_stream, nullptr);
2754 		gln_transcript_stream = nullptr;
2755 
2756 		g_vm->glk_window_set_echo_stream(gln_main_window, nullptr);
2757 
2758 		gln_normal_string("Glk transcript is now off.\n");
2759 	}
2760 
2761 	else if (strlen(argument) == 0) {
2762 		gln_normal_string("Glk transcript is ");
2763 		gln_normal_string(gln_transcript_stream ? "on" : "off");
2764 		gln_normal_string(".\n");
2765 	}
2766 
2767 	else {
2768 		gln_normal_string("Glk transcript can be ");
2769 		gln_standout_string("on");
2770 		gln_normal_string(", or ");
2771 		gln_standout_string("off");
2772 		gln_normal_string(".\n");
2773 	}
2774 }
2775 
2776 
2777 /*
2778  * gln_command_inputlog()
2779  *
2780  * Turn game input logging on and off.
2781  */
gln_command_inputlog(const char * argument)2782 static void gln_command_inputlog(const char *argument) {
2783 	assert(argument);
2784 
2785 	if (gln_strcasecmp(argument, "on") == 0) {
2786 		frefid_t fileref;
2787 
2788 		if (gln_inputlog_stream) {
2789 			gln_normal_string("Glk input logging is already on.\n");
2790 			return;
2791 		}
2792 
2793 		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
2794 		          | fileusage_BinaryMode,
2795 		          filemode_WriteAppend, 0);
2796 		if (!fileref) {
2797 			gln_standout_string("Glk input logging failed.\n");
2798 			return;
2799 		}
2800 
2801 		gln_inputlog_stream = g_vm->glk_stream_open_file(fileref,
2802 		                      filemode_WriteAppend, 0);
2803 		g_vm->glk_fileref_destroy(fileref);
2804 		if (!gln_inputlog_stream) {
2805 			gln_standout_string("Glk input logging failed.\n");
2806 			return;
2807 		}
2808 
2809 		gln_normal_string("Glk input logging is now on.\n");
2810 	}
2811 
2812 	else if (gln_strcasecmp(argument, "off") == 0) {
2813 		if (!gln_inputlog_stream) {
2814 			gln_normal_string("Glk input logging is already off.\n");
2815 			return;
2816 		}
2817 
2818 		g_vm->glk_stream_close(gln_inputlog_stream, nullptr);
2819 		gln_inputlog_stream = nullptr;
2820 
2821 		gln_normal_string("Glk input log is now off.\n");
2822 	}
2823 
2824 	else if (strlen(argument) == 0) {
2825 		gln_normal_string("Glk input logging is ");
2826 		gln_normal_string(gln_inputlog_stream ? "on" : "off");
2827 		gln_normal_string(".\n");
2828 	}
2829 
2830 	else {
2831 		gln_normal_string("Glk input logging can be ");
2832 		gln_standout_string("on");
2833 		gln_normal_string(", or ");
2834 		gln_standout_string("off");
2835 		gln_normal_string(".\n");
2836 	}
2837 }
2838 
2839 
2840 /*
2841  * gln_command_readlog()
2842  *
2843  * Set the game input log, to read input from a file.
2844  */
gln_command_readlog(const char * argument)2845 static void gln_command_readlog(const char *argument) {
2846 	assert(argument);
2847 
2848 	if (gln_strcasecmp(argument, "on") == 0) {
2849 		frefid_t fileref;
2850 
2851 		if (gln_readlog_stream) {
2852 			gln_normal_string("Glk read log is already on.\n");
2853 			return;
2854 		}
2855 
2856 		fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
2857 		          | fileusage_BinaryMode,
2858 		          filemode_Read, 0);
2859 		if (!fileref) {
2860 			gln_standout_string("Glk read log failed.\n");
2861 			return;
2862 		}
2863 
2864 		if (!g_vm->glk_fileref_does_file_exist(fileref)) {
2865 			g_vm->glk_fileref_destroy(fileref);
2866 			gln_standout_string("Glk read log failed.\n");
2867 			return;
2868 		}
2869 
2870 		gln_readlog_stream = g_vm->glk_stream_open_file(fileref, filemode_Read, 0);
2871 		g_vm->glk_fileref_destroy(fileref);
2872 		if (!gln_readlog_stream) {
2873 			gln_standout_string("Glk read log failed.\n");
2874 			return;
2875 		}
2876 
2877 		gln_normal_string("Glk read log is now on.\n");
2878 	}
2879 
2880 	else if (gln_strcasecmp(argument, "off") == 0) {
2881 		if (!gln_readlog_stream) {
2882 			gln_normal_string("Glk read log is already off.\n");
2883 			return;
2884 		}
2885 
2886 		g_vm->glk_stream_close(gln_readlog_stream, nullptr);
2887 		gln_readlog_stream = nullptr;
2888 
2889 		gln_normal_string("Glk read log is now off.\n");
2890 	}
2891 
2892 	else if (strlen(argument) == 0) {
2893 		gln_normal_string("Glk read log is ");
2894 		gln_normal_string(gln_readlog_stream ? "on" : "off");
2895 		gln_normal_string(".\n");
2896 	}
2897 
2898 	else {
2899 		gln_normal_string("Glk read log can be ");
2900 		gln_standout_string("on");
2901 		gln_normal_string(", or ");
2902 		gln_standout_string("off");
2903 		gln_normal_string(".\n");
2904 	}
2905 }
2906 
2907 
2908 /*
2909  * gln_command_abbreviations()
2910  *
2911  * Turn abbreviation expansions on and off.
2912  */
gln_command_abbreviations(const char * argument)2913 static void gln_command_abbreviations(const char *argument) {
2914 	assert(argument);
2915 
2916 	if (gln_strcasecmp(argument, "on") == 0) {
2917 		if (gln_abbreviations_enabled) {
2918 			gln_normal_string("Glk abbreviation expansions are already on.\n");
2919 			return;
2920 		}
2921 
2922 		gln_abbreviations_enabled = TRUE;
2923 		gln_normal_string("Glk abbreviation expansions are now on.\n");
2924 	}
2925 
2926 	else if (gln_strcasecmp(argument, "off") == 0) {
2927 		if (!gln_abbreviations_enabled) {
2928 			gln_normal_string("Glk abbreviation expansions are already off.\n");
2929 			return;
2930 		}
2931 
2932 		gln_abbreviations_enabled = FALSE;
2933 		gln_normal_string("Glk abbreviation expansions are now off.\n");
2934 	}
2935 
2936 	else if (strlen(argument) == 0) {
2937 		gln_normal_string("Glk abbreviation expansions are ");
2938 		gln_normal_string(gln_abbreviations_enabled ? "on" : "off");
2939 		gln_normal_string(".\n");
2940 	}
2941 
2942 	else {
2943 		gln_normal_string("Glk abbreviation expansions can be ");
2944 		gln_standout_string("on");
2945 		gln_normal_string(", or ");
2946 		gln_standout_string("off");
2947 		gln_normal_string(".\n");
2948 	}
2949 }
2950 
2951 
2952 /*
2953  * gln_command_graphics()
2954  *
2955  * Enable or disable graphics more permanently than is done by the main
2956  * interpreter.  Also, print out a few brief details about the graphics
2957  * state of the program.
2958  */
gln_command_graphics(const char * argument)2959 static void gln_command_graphics(const char *argument) {
2960 	assert(argument);
2961 
2962 	if (!gln_graphics_possible) {
2963 		gln_normal_string("Glk graphics are not available.\n");
2964 		return;
2965 	}
2966 
2967 	if (gln_strcasecmp(argument, "on") == 0) {
2968 		if (gln_graphics_enabled) {
2969 			gln_normal_string("Glk graphics are already on.\n");
2970 			return;
2971 		}
2972 
2973 		gln_graphics_enabled = TRUE;
2974 
2975 		/* If a picture is loaded, call the restart function to repaint it. */
2976 		if (gln_graphics_picture_is_available()) {
2977 			if (!gln_graphics_open()) {
2978 				gln_normal_string("Glk graphics error.\n");
2979 				return;
2980 			}
2981 			gln_graphics_restart();
2982 		}
2983 
2984 		gln_normal_string("Glk graphics are now on.\n");
2985 	}
2986 
2987 	else if (gln_strcasecmp(argument, "off") == 0) {
2988 		if (!gln_graphics_enabled) {
2989 			gln_normal_string("Glk graphics are already off.\n");
2990 			return;
2991 		}
2992 
2993 		/*
2994 		 * Set graphics to disabled, and stop any graphics processing.  Close
2995 		 * the graphics window.
2996 		 */
2997 		gln_graphics_enabled = FALSE;
2998 		gln_graphics_stop();
2999 		gln_graphics_close();
3000 
3001 		gln_normal_string("Glk graphics are now off.\n");
3002 	}
3003 
3004 	else if (strlen(argument) == 0) {
3005 		gln_normal_string("Glk graphics are available,");
3006 		gln_normal_string(gln_graphics_enabled
3007 		                  ? " and enabled.\n" : " but disabled.\n");
3008 
3009 		if (gln_graphics_picture_is_available()) {
3010 			int width, height;
3011 
3012 			if (gln_graphics_get_picture_details(&width, &height)) {
3013 				char buffer[16];
3014 
3015 				gln_normal_string("There is a picture loaded, ");
3016 
3017 				sprintf(buffer, "%d", width);
3018 				gln_normal_string(buffer);
3019 				gln_normal_string(" by ");
3020 
3021 				sprintf(buffer, "%d", height);
3022 				gln_normal_string(buffer);
3023 
3024 				gln_normal_string(" pixels.\n");
3025 			}
3026 		}
3027 
3028 		if (!gln_graphics_interpreter_enabled())
3029 			gln_normal_string("Interpreter graphics are disabled.\n");
3030 
3031 		if (gln_graphics_enabled && gln_graphics_are_displayed()) {
3032 			const char *bitmap_type;
3033 			int color_count, is_active;
3034 
3035 			if (gln_graphics_get_rendering_details(&bitmap_type,
3036 			                                       &color_count, &is_active)) {
3037 				char buffer[16];
3038 
3039 				gln_normal_string("Graphics are ");
3040 				gln_normal_string(is_active ? "active, " : "displayed, ");
3041 
3042 				sprintf(buffer, "%d", color_count);
3043 				gln_normal_string(buffer);
3044 				gln_normal_string(" colours");
3045 
3046 				if (bitmap_type) {
3047 					gln_normal_string(", ");
3048 					gln_normal_string(bitmap_type);
3049 					gln_normal_string(" bitmaps");
3050 				}
3051 				gln_normal_string(".\n");
3052 			} else
3053 				gln_normal_string("Graphics are being displayed.\n");
3054 		}
3055 
3056 		if (gln_graphics_enabled && !gln_graphics_are_displayed())
3057 			gln_normal_string("Graphics are not being displayed.\n");
3058 	}
3059 
3060 	else {
3061 		gln_normal_string("Glk graphics can be ");
3062 		gln_standout_string("on");
3063 		gln_normal_string(", or ");
3064 		gln_standout_string("off");
3065 		gln_normal_string(".\n");
3066 	}
3067 }
3068 
3069 
3070 /*
3071  * gln_command_loopchecks()
3072  *
3073  * Turn loop checking (for game infinite loops) on and off.
3074  */
gln_command_loopchecks(const char * argument)3075 static void gln_command_loopchecks(const char *argument) {
3076 	assert(argument);
3077 
3078 	if (gln_strcasecmp(argument, "on") == 0) {
3079 		if (gln_loopcheck_enabled) {
3080 			gln_normal_string("Glk loop detection is already on.\n");
3081 			return;
3082 		}
3083 
3084 		gln_loopcheck_enabled = TRUE;
3085 		gln_normal_string("Glk loop detection is now on.\n");
3086 	}
3087 
3088 	else if (gln_strcasecmp(argument, "off") == 0) {
3089 		if (!gln_loopcheck_enabled) {
3090 			gln_normal_string("Glk loop detection is already off.\n");
3091 			return;
3092 		}
3093 
3094 		gln_loopcheck_enabled = FALSE;
3095 		gln_normal_string("Glk loop detection is now off.\n");
3096 	}
3097 
3098 	else if (strlen(argument) == 0) {
3099 		gln_normal_string("Glk loop detection is ");
3100 		gln_normal_string(gln_loopcheck_enabled ? "on" : "off");
3101 		gln_normal_string(".\n");
3102 	}
3103 
3104 	else {
3105 		gln_normal_string("Glk loop detection can be ");
3106 		gln_standout_string("on");
3107 		gln_normal_string(", or ");
3108 		gln_standout_string("off");
3109 		gln_normal_string(".\n");
3110 	}
3111 }
3112 
3113 
3114 /*
3115  * gln_command_locals()
3116  *
3117  * Turn local interpretation of "quit" etc. on and off.
3118  */
gln_command_locals(const char * argument)3119 static void gln_command_locals(const char *argument) {
3120 	assert(argument);
3121 
3122 	if (gln_strcasecmp(argument, "on") == 0) {
3123 		if (gln_intercept_enabled) {
3124 			gln_normal_string("Glk local commands are already on.\n");
3125 			return;
3126 		}
3127 
3128 		gln_intercept_enabled = TRUE;
3129 		gln_normal_string("Glk local commands are now on.\n");
3130 	}
3131 
3132 	else if (gln_strcasecmp(argument, "off") == 0) {
3133 		if (!gln_intercept_enabled) {
3134 			gln_normal_string("Glk local commands are already off.\n");
3135 			return;
3136 		}
3137 
3138 		gln_intercept_enabled = FALSE;
3139 		gln_normal_string("Glk local commands are now off.\n");
3140 	}
3141 
3142 	else if (strlen(argument) == 0) {
3143 		gln_normal_string("Glk local commands are ");
3144 		gln_normal_string(gln_intercept_enabled ? "on" : "off");
3145 		gln_normal_string(".\n");
3146 	}
3147 
3148 	else {
3149 		gln_normal_string("Glk local commands can be ");
3150 		gln_standout_string("on");
3151 		gln_normal_string(", or ");
3152 		gln_standout_string("off");
3153 		gln_normal_string(".\n");
3154 	}
3155 }
3156 
3157 
3158 /*
3159  * gln_command_prompts()
3160  *
3161  * Turn the extra "> " prompt output on and off.
3162  */
gln_command_prompts(const char * argument)3163 static void gln_command_prompts(const char *argument) {
3164 	assert(argument);
3165 
3166 	if (gln_strcasecmp(argument, "on") == 0) {
3167 		if (gln_prompt_enabled) {
3168 			gln_normal_string("Glk extra prompts are already on.\n");
3169 			return;
3170 		}
3171 
3172 		gln_prompt_enabled = TRUE;
3173 		gln_normal_string("Glk extra prompts are now on.\n");
3174 
3175 		/* Check for a game prompt to clear the flag. */
3176 		gln_game_prompted();
3177 	}
3178 
3179 	else if (gln_strcasecmp(argument, "off") == 0) {
3180 		if (!gln_prompt_enabled) {
3181 			gln_normal_string("Glk extra prompts are already off.\n");
3182 			return;
3183 		}
3184 
3185 		gln_prompt_enabled = FALSE;
3186 		gln_normal_string("Glk extra prompts are now off.\n");
3187 	}
3188 
3189 	else if (strlen(argument) == 0) {
3190 		gln_normal_string("Glk extra prompts are ");
3191 		gln_normal_string(gln_prompt_enabled ? "on" : "off");
3192 		gln_normal_string(".\n");
3193 	}
3194 
3195 	else {
3196 		gln_normal_string("Glk extra prompts can be ");
3197 		gln_standout_string("on");
3198 		gln_normal_string(", or ");
3199 		gln_standout_string("off");
3200 		gln_normal_string(".\n");
3201 	}
3202 }
3203 
3204 
3205 /*
3206  * gln_command_print_version_number()
3207  * gln_command_version()
3208  *
3209  * Print out the Glk library version number.
3210  */
gln_command_print_version_number(glui32 version)3211 static void gln_command_print_version_number(glui32 version) {
3212 	char buffer[64];
3213 
3214 	sprintf(buffer, "%lu.%lu.%lu",
3215 	        (unsigned long) version >> 16,
3216 	        (unsigned long)(version >> 8) & 0xff,
3217 	        (unsigned long) version & 0xff);
3218 	gln_normal_string(buffer);
3219 }
3220 
gln_command_version(const char * argument)3221 static void gln_command_version(const char *argument) {
3222 	glui32 version;
3223 	assert(argument);
3224 
3225 	gln_normal_string("This is version ");
3226 	gln_command_print_version_number(GLN_PORT_VERSION);
3227 	gln_normal_string(" of the Glk Level 9 port.\n");
3228 
3229 	version = g_vm->glk_gestalt(gestalt_Version, 0);
3230 	gln_normal_string("The Glk library version is ");
3231 	gln_command_print_version_number(version);
3232 	gln_normal_string(".\n");
3233 }
3234 
3235 
3236 /*
3237  * gln_command_commands()
3238  *
3239  * Turn command escapes off.  Once off, there's no way to turn them back on.
3240  * Commands must be on already to enter this function.
3241  */
gln_command_commands(const char * argument)3242 static void gln_command_commands(const char *argument) {
3243 	assert(argument);
3244 
3245 	if (gln_strcasecmp(argument, "on") == 0) {
3246 		gln_normal_string("Glk commands are already on.\n");
3247 	}
3248 
3249 	else if (gln_strcasecmp(argument, "off") == 0) {
3250 		gln_commands_enabled = FALSE;
3251 		gln_normal_string("Glk commands are now off.\n");
3252 	}
3253 
3254 	else if (strlen(argument) == 0) {
3255 		gln_normal_string("Glk commands are ");
3256 		gln_normal_string(gln_commands_enabled ? "on" : "off");
3257 		gln_normal_string(".\n");
3258 	}
3259 
3260 	else {
3261 		gln_normal_string("Glk commands can be ");
3262 		gln_standout_string("on");
3263 		gln_normal_string(", or ");
3264 		gln_standout_string("off");
3265 		gln_normal_string(".\n");
3266 	}
3267 }
3268 
3269 
3270 /* Glk subcommands and handler functions. */
3271 struct gln_command_t {
3272 	const char *const command;                      /* Glk subcommand. */
3273 	void (* const handler)(const char *argument);   /* Subcommand handler. */
3274 	const int takes_argument;                       /* Argument flag. */
3275 };
3276 typedef const gln_command_t *gln_commandref_t;
3277 
3278 static void gln_command_summary(const char *argument);
3279 static void gln_command_help(const char *argument);
3280 
3281 static const gln_command_t GLN_COMMAND_TABLE[] = {
3282 	{"summary",        gln_command_summary,        FALSE},
3283 	{"script",         gln_command_script,         TRUE},
3284 	{"inputlog",       gln_command_inputlog,       TRUE},
3285 	{"readlog",        gln_command_readlog,        TRUE},
3286 	{"abbreviations",  gln_command_abbreviations,  TRUE},
3287 	{"graphics",       gln_command_graphics,       TRUE},
3288 	{"loopchecks",     gln_command_loopchecks,     TRUE},
3289 	{"locals",         gln_command_locals,         TRUE},
3290 	{"prompts",        gln_command_prompts,        TRUE},
3291 	{"version",        gln_command_version,        FALSE},
3292 	{"commands",       gln_command_commands,       TRUE},
3293 	{"help",           gln_command_help,           TRUE},
3294 	{nullptr, nullptr, FALSE}
3295 };
3296 
3297 
3298 /*
3299  * gln_command_summary()
3300  *
3301  * Report all current Glk settings.
3302  */
gln_command_summary(const char * argument)3303 static void gln_command_summary(const char *argument) {
3304 	gln_commandref_t entry;
3305 	assert(argument);
3306 
3307 	/*
3308 	 * Call handlers that have status to report with an empty argument,
3309 	 * prompting each to print its current setting.
3310 	 */
3311 	for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
3312 		if (entry->handler == gln_command_summary
3313 		        || entry->handler == gln_command_help)
3314 			continue;
3315 
3316 		entry->handler("");
3317 	}
3318 }
3319 
3320 
3321 /*
3322  * gln_command_help()
3323  *
3324  * Document the available Glk commands.
3325  */
gln_command_help(const char * command)3326 static void gln_command_help(const char *command) {
3327 	gln_commandref_t entry, matched;
3328 	assert(command);
3329 
3330 	if (strlen(command) == 0) {
3331 		gln_normal_string("Glk commands are");
3332 		for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
3333 			gln_commandref_t next;
3334 
3335 			next = entry + 1;
3336 			gln_normal_string(next->command ? " " : " and ");
3337 			gln_standout_string(entry->command);
3338 			gln_normal_string(next->command ? "," : ".\n\n");
3339 		}
3340 
3341 		gln_normal_string("Glk commands may be abbreviated, as long as"
3342 		                  " the abbreviation is unambiguous.  Use ");
3343 		gln_standout_string("glk help");
3344 		gln_normal_string(" followed by a Glk command name for help on that"
3345 		                  " command.\n");
3346 		return;
3347 	}
3348 
3349 	matched = nullptr;
3350 	for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
3351 		if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
3352 			if (matched) {
3353 				gln_normal_string("The Glk command ");
3354 				gln_standout_string(command);
3355 				gln_normal_string(" is ambiguous.  Try ");
3356 				gln_standout_string("glk help");
3357 				gln_normal_string(" for more information.\n");
3358 				return;
3359 			}
3360 			matched = entry;
3361 		}
3362 	}
3363 	if (!matched) {
3364 		gln_normal_string("The Glk command ");
3365 		gln_standout_string(command);
3366 		gln_normal_string(" is not valid.  Try ");
3367 		gln_standout_string("glk help");
3368 		gln_normal_string(" for more information.\n");
3369 		return;
3370 	}
3371 
3372 	if (matched->handler == gln_command_summary) {
3373 		gln_normal_string("Prints a summary of all the current Glk Level 9"
3374 		                  " settings.\n");
3375 	}
3376 
3377 	else if (matched->handler == gln_command_script) {
3378 		gln_normal_string("Logs the game's output to a file.\n\nUse ");
3379 		gln_standout_string("glk script on");
3380 		gln_normal_string(" to begin logging game output, and ");
3381 		gln_standout_string("glk script off");
3382 		gln_normal_string(" to end it.  Glk Level 9 will ask you for a file"
3383 		                  " when you turn scripts on.\n");
3384 	}
3385 
3386 	else if (matched->handler == gln_command_inputlog) {
3387 		gln_normal_string("Records the commands you type into a game.\n\nUse ");
3388 		gln_standout_string("glk inputlog on");
3389 		gln_normal_string(", to begin recording your commands, and ");
3390 		gln_standout_string("glk inputlog off");
3391 		gln_normal_string(" to turn off input logs.  You can play back"
3392 		                  " recorded commands into a game with the ");
3393 		gln_standout_string("glk readlog");
3394 		gln_normal_string(" command.\n");
3395 	}
3396 
3397 	else if (matched->handler == gln_command_readlog) {
3398 		gln_normal_string("Plays back commands recorded with ");
3399 		gln_standout_string("glk inputlog on");
3400 		gln_normal_string(".\n\nUse ");
3401 		gln_standout_string("glk readlog on");
3402 		gln_normal_string(".  Command play back stops at the end of the"
3403 		                  " file.  You can also play back commands from a"
3404 		                  " text file created using any standard editor.\n");
3405 	}
3406 
3407 	else if (matched->handler == gln_command_abbreviations) {
3408 		gln_normal_string("Controls abbreviation expansion.\n\nGlk Level 9"
3409 		                  " automatically expands several standard single"
3410 		                  " letter abbreviations for you; for example, \"x\""
3411 		                  " becomes \"examine\".  Use ");
3412 		gln_standout_string("glk abbreviations on");
3413 		gln_normal_string(" to turn this feature on, and ");
3414 		gln_standout_string("glk abbreviations off");
3415 		gln_normal_string(" to turn it off.  While the feature is on, you"
3416 		                  " can bypass abbreviation expansion for an"
3417 		                  " individual game command by prefixing it with a"
3418 		                  " single quote.\n");
3419 	}
3420 
3421 	else if (matched->handler == gln_command_graphics) {
3422 		gln_normal_string("Turns interpreter graphics on and off.\n\nUse ");
3423 		gln_standout_string("glk graphics on");
3424 		gln_normal_string(" to enable interpreter graphics, and ");
3425 		gln_standout_string("glk graphics off");
3426 		gln_normal_string(" to turn graphics off and close the graphics window."
3427 		                  "  This control works slightly differently to the"
3428 		                  " 'graphics' command in Level 9 games themselves; the"
3429 		                  " game's 'graphics' command may disable new images,"
3430 		                  " but leave old ones displayed.  For graphics to be"
3431 		                  " displayed, they must be turned on in both the game"
3432 		                  " and the interpreter.\n");
3433 	}
3434 
3435 	else if (matched->handler == gln_command_loopchecks) {
3436 		gln_normal_string("Controls game infinite loop monitoring.\n\n"
3437 		                  "Some Level 9 games can enter an infinite loop if they"
3438 		                  " have nothing better to do.  A game might do this"
3439 		                  " after it has ended, should you decline its offer"
3440 		                  " to rerun.  To avoid the need to kill the interpreter"
3441 		                  " completely if a game does this, Glk Level 9 monitors"
3442 		                  " a game's input and output, and offers the option to"
3443 		                  " end the program gracefully if a game is silent for"
3444 		                  " a few seconds.  Use ");
3445 		gln_standout_string("glk loopchecks on");
3446 		gln_normal_string(" to turn this feature on, and ");
3447 		gln_standout_string("glk loopchecks off");
3448 		gln_normal_string(" to turn it off.\n");
3449 	}
3450 
3451 	else if (matched->handler == gln_command_locals) {
3452 		gln_normal_string("Controls interception of selected game commands.\n\n"
3453 		                  "Some Level 9 games were written for cassette tape"
3454 		                  " based microprocessor systems, and the way in which"
3455 		                  " they save, restore, and restart games can reflect"
3456 		                  " this.  There is also often no straightforward way"
3457 		                  " to quit from a game.\n\nTo make playing a Level 9"
3458 		                  " game appear similar to other systems, Glk Level 9"
3459 		                  " will trap the commands 'quit', 'restart', 'save',"
3460 		                  " 'restore', and 'load' (a synonym for 'restore') and"
3461 		                  " handle them locally within the interpreter.  Use ");
3462 		gln_standout_string("glk locals on");
3463 		gln_normal_string(" to turn this feature on, and ");
3464 		gln_standout_string("glk locals off");
3465 		gln_normal_string(" to turn it off.\n");
3466 	}
3467 
3468 	else if (matched->handler == gln_command_prompts) {
3469 		gln_normal_string("Controls extra input prompting.\n\n"
3470 		                  "Glk Level 9 can issue a replacement '>' input"
3471 		                  " prompt if it detects that the game hasn't prompted"
3472 		                  " after, say, an empty input line.  Use ");
3473 		gln_standout_string("glk prompts on");
3474 		gln_normal_string(" to turn this feature on, and ");
3475 		gln_standout_string("glk prompts off");
3476 		gln_normal_string(" to turn it off.\n");
3477 	}
3478 
3479 	else if (matched->handler == gln_command_version) {
3480 		gln_normal_string("Prints the version numbers of the Glk library"
3481 		                  " and the Glk Level 9 port.\n");
3482 	}
3483 
3484 	else if (matched->handler == gln_command_commands) {
3485 		gln_normal_string("Turn off Glk commands.\n\nUse ");
3486 		gln_standout_string("glk commands off");
3487 		gln_normal_string(" to disable all Glk commands, including this one."
3488 		                  "  Once turned off, there is no way to turn Glk"
3489 		                  " commands back on while inside the game.\n");
3490 	}
3491 
3492 	else if (matched->handler == gln_command_help)
3493 		gln_command_help("");
3494 
3495 	else
3496 		gln_normal_string("There is no help available on that Glk command."
3497 		                  "  Sorry.\n");
3498 }
3499 
3500 
3501 /*
3502  * gln_command_escape()
3503  *
3504  * This function is handed each input line.  If the line contains a specific
3505  * Glk port command, handle it and return TRUE, otherwise return FALSE.
3506  */
gln_command_escape(const char * string)3507 static int gln_command_escape(const char *string) {
3508 	int posn;
3509 	char *string_copy, *command, *argument;
3510 	assert(string);
3511 
3512 	/*
3513 	 * Return FALSE if the string doesn't begin with the Glk command escape
3514 	 * introducer.
3515 	 */
3516 	posn = strspn(string, "\t ");
3517 	if (gln_strncasecmp(string + posn, "glk", strlen("glk")) != 0)
3518 		return FALSE;
3519 
3520 	/* Take a copy of the string, without any leading space or introducer. */
3521 	string_copy = (char *)gln_malloc(strlen(string + posn) + 1 - strlen("glk"));
3522 	strcpy(string_copy, string + posn + strlen("glk"));
3523 
3524 	/*
3525 	 * Find the subcommand; the first word in the string copy.  Find its end,
3526 	 * and ensure it terminates with a NUL.
3527 	 */
3528 	posn = strspn(string_copy, "\t ");
3529 	command = string_copy + posn;
3530 	posn += strcspn(string_copy + posn, "\t ");
3531 	if (string_copy[posn] != '\0')
3532 		string_copy[posn++] = '\0';
3533 
3534 	/*
3535 	 * Now find any argument data for the command, ensuring it too terminates
3536 	 * with a NUL.
3537 	 */
3538 	posn += strspn(string_copy + posn, "\t ");
3539 	argument = string_copy + posn;
3540 	posn += strcspn(string_copy + posn, "\t ");
3541 	string_copy[posn] = '\0';
3542 
3543 	/*
3544 	 * Try to handle the command and argument as a Glk subcommand.  If it
3545 	 * doesn't run unambiguously, print command usage.  Treat an empty command
3546 	 * as "help".
3547 	 */
3548 	if (strlen(command) > 0) {
3549 		gln_commandref_t entry, matched;
3550 		int matches;
3551 
3552 		/*
3553 		 * Search for the first unambiguous table command string matching
3554 		 * the command passed in.
3555 		 */
3556 		matches = 0;
3557 		matched = nullptr;
3558 		for (entry = GLN_COMMAND_TABLE; entry->command; entry++) {
3559 			if (gln_strncasecmp(command, entry->command, strlen(command)) == 0) {
3560 				matches++;
3561 				matched = entry;
3562 			}
3563 		}
3564 
3565 		/* If the match was unambiguous, call the command handler. */
3566 		if (matches == 1) {
3567 			gln_normal_char('\n');
3568 			matched->handler(argument);
3569 
3570 			if (!matched->takes_argument && strlen(argument) > 0) {
3571 				gln_normal_string("[The ");
3572 				gln_standout_string(matched->command);
3573 				gln_normal_string(" command ignores arguments.]\n");
3574 			}
3575 		}
3576 
3577 		/* No match, or the command was ambiguous. */
3578 		else {
3579 			gln_normal_string("\nThe Glk command ");
3580 			gln_standout_string(command);
3581 			gln_normal_string(" is ");
3582 			gln_normal_string(matches == 0 ? "not valid" : "ambiguous");
3583 			gln_normal_string(".  Try ");
3584 			gln_standout_string("glk help");
3585 			gln_normal_string(" for more information.\n");
3586 		}
3587 	} else {
3588 		gln_normal_char('\n');
3589 		gln_command_help("");
3590 	}
3591 
3592 	/* The string contained a Glk command; return TRUE. */
3593 	free(string_copy);
3594 	return TRUE;
3595 }
3596 
3597 
3598 /*
3599  * gln_command_intercept()
3600  *
3601  * The Level 9 games handle the commands "quit" and "restart" oddly, and
3602  * somewhat similarly.  Both prompt "Press SPACE to play again", and then
3603  * ignore all characters except space.  This makes it especially hard to exit
3604  * from a game without killing the interpreter process.  They also handle
3605  * "restore" via an odd security mechanism which has no real place here (the
3606  * base Level 9 interpreter sidesteps this with its "#restore" command, and
3607  * has some bugs in "save").
3608  *
3609  * To try to improve these, here we'll catch and special case the input lines
3610  * "quit", "save", "restore", and "restart".  "Load" is a synonym for
3611  * "restore".
3612  *
3613  * On "quit" or "restart", the function sets the interpreter stop reason
3614  * code, stops the current game run.  On "save" or "restore" it calls the
3615  * appropriate internal interpreter function.
3616  *
3617  * The return value is TRUE if an intercepted command was found, otherwise
3618  * FALSE.
3619  */
gln_command_intercept(char * string)3620 static int gln_command_intercept(char *string) {
3621 	int posn, result;
3622 	char *string_copy, *trailing;
3623 	assert(string);
3624 
3625 	result = FALSE;
3626 
3627 	/* Take a copy of the string, excluding any leading whitespace. */
3628 	posn = strspn(string, "\t ");
3629 	string_copy = (char *)gln_malloc(strlen(string + posn) + 1);
3630 	strcpy(string_copy, string + posn);
3631 
3632 	/*
3633 	 * Find the space or NUL after the first word, and check that anything
3634 	 * after it the first word is whitespace only.
3635 	 */
3636 	posn = strcspn(string_copy, "\t ");
3637 	trailing = string_copy + posn;
3638 	if (trailing[strspn(trailing, "\t ")] == '\0') {
3639 		/* Terminate the string copy for easy comparisons. */
3640 		string_copy[posn] = '\0';
3641 
3642 		/* If this command was "quit", confirm, then call StopGame(). */
3643 		if (gln_strcasecmp(string_copy, "quit") == 0) {
3644 			if (gln_confirm("\nDo you really want to stop? [Y or N] ")) {
3645 				gln_stop_reason = STOP_EXIT;
3646 				StopGame();
3647 			}
3648 			result = TRUE;
3649 		}
3650 
3651 		/* If this command was "restart", confirm, then call StopGame(). */
3652 		else if (gln_strcasecmp(string_copy, "restart") == 0) {
3653 			if (gln_confirm("\nDo you really want to restart? [Y or N] ")) {
3654 				gln_stop_reason = STOP_RESTART;
3655 				StopGame();
3656 			}
3657 			result = TRUE;
3658 		}
3659 
3660 		/* If this command was "save", simply call save(). */
3661 		else if (gln_strcasecmp(string_copy, "save") == 0) {
3662 			gln_standout_string("\nSaving using interpreter\n\n");
3663 			save();
3664 			result = TRUE;
3665 		}
3666 
3667 		/* If this command was "restore" or "load", call restore(). */
3668 		else if (gln_strcasecmp(string_copy, "restore") == 0
3669 		         || gln_strcasecmp(string_copy, "load") == 0) {
3670 			gln_standout_string("\nRestoring using interpreter\n\n");
3671 			restore();
3672 			result = TRUE;
3673 		}
3674 	}
3675 
3676 	free(string_copy);
3677 	return result;
3678 }
3679 
3680 
3681 /*---------------------------------------------------------------------*/
3682 /*  Glk port input functions                                           */
3683 /*---------------------------------------------------------------------*/
3684 
3685 /* Ctrl-C and Ctrl-U character constants. */
3686 static const char GLN_CONTROL_C = '\003',
3687 				  GLN_CONTROL_U = '\025';
3688 
3689 /*
3690  * os_readchar() call count limit, after which we really read a character.
3691  * Also, call count limit on os_stoplist calls, after which we poll for a
3692  * character press to stop the listing, and a stoplist poll timeout.
3693  */
3694 static const int GLN_READCHAR_LIMIT = 1024,
3695 				 GLN_STOPLIST_LIMIT = 10;
3696 static const glui32 GLN_STOPLIST_TIMEOUT = 50;
3697 
3698 /* Quote used to suppress abbreviation expansion and local commands. */
3699 static const char GLN_QUOTED_INPUT = '\'';
3700 
3701 
3702 /*
3703  * Note of when the interpreter is in list output.  The last element of any
3704  * list generally lacks a terminating newline, and unless we do something
3705  * special with it, it'll look like a valid prompt to us.
3706  */
3707 static int gln_inside_list = FALSE;
3708 
3709 
3710 /* Table of single-character command abbreviations. */
3711 struct gln_abbreviation_t {
3712 	const char abbreviation;       /* Abbreviation character. */
3713 	const char *const expansion;   /* Expansion string. */
3714 };
3715 typedef const gln_abbreviation_t *gln_abbreviationref_t;
3716 
3717 static const gln_abbreviation_t GLN_ABBREVIATIONS[] = {
3718 	{'c', "close"},    {'g', "again"},  {'i', "inventory"},
3719 	{'k', "attack"},   {'l', "look"},   {'p', "open"},
3720 	{'q', "quit"},     {'r', "drop"},   {'t', "take"},
3721 	{'x', "examine"},  {'y', "yes"},    {'z', "wait"},
3722 	{'\0', nullptr}
3723 };
3724 
3725 
3726 /*
3727  * gln_expand_abbreviations()
3728  *
3729  * Expand a few common one-character abbreviations commonly found in other
3730  * game systems, but not always normal in Level 9 games.
3731  */
gln_expand_abbreviations(char * buffer,int size)3732 static void gln_expand_abbreviations(char *buffer, int size) {
3733 	char *command, abbreviation;
3734 	const char *expansion;
3735 	gln_abbreviationref_t entry;
3736 	assert(buffer);
3737 
3738 	/* Ignore anything that isn't a single letter command. */
3739 	command = buffer + strspn(buffer, "\t ");
3740 	if (!(strlen(command) == 1
3741 	        || (strlen(command) > 1 && Common::isSpace(command[1]))))
3742 		return;
3743 
3744 	/* Scan the abbreviations table for a match. */
3745 	abbreviation = g_vm->glk_char_to_lower((unsigned char) command[0]);
3746 	expansion = nullptr;
3747 	for (entry = GLN_ABBREVIATIONS; entry->expansion; entry++) {
3748 		if (entry->abbreviation == abbreviation) {
3749 			expansion = entry->expansion;
3750 			break;
3751 		}
3752 	}
3753 
3754 	/*
3755 	 * If a match found, check for a fit, then replace the character with the
3756 	 * expansion string.
3757 	 */
3758 	if (expansion) {
3759 		if (strlen(buffer) + strlen(expansion) - 1 >= (uint)size)
3760 			return;
3761 
3762 		memmove(command + strlen(expansion) - 1, command, strlen(command) + 1);
3763 		memcpy(command, expansion, strlen(expansion));
3764 
3765 #ifndef GARGLK
3766 		gln_standout_string("[");
3767 		gln_standout_char(abbreviation);
3768 		gln_standout_string(" -> ");
3769 		gln_standout_string(expansion);
3770 		gln_standout_string("]\n");
3771 #endif
3772 	}
3773 }
3774 
3775 
3776 /*
3777  * gln_output_endlist()
3778  *
3779  * The core interpreter doesn't terminate lists with a newline, so we take
3780  * care of that here; a fixup for input functions.
3781  */
gln_output_endlist()3782 static void gln_output_endlist() {
3783 	if (gln_inside_list) {
3784 		/*
3785 		 * Supply the missing newline, using os_printchar() so that list output
3786 		 * doesn't look like a prompt when we come to flush it.
3787 		 */
3788 		os_printchar('\n');
3789 
3790 		gln_inside_list = FALSE;
3791 	}
3792 }
3793 
3794 
3795 /*
3796  * os_input()
3797  *
3798  * Read a line from the keyboard.  This function makes a special case of
3799  * some command strings, and will also perform abbreviation expansion.
3800  */
os_input(char * buffer,int size)3801 gln_bool os_input(char *buffer, int size) {
3802 	event_t event;
3803 	assert(buffer);
3804 
3805 	/* If doing linemode graphics, run all graphic opcodes available. */
3806 	gln_linegraphics_process();
3807 
3808 	/*
3809 	 * Update the current status line display, flush any pending buffered
3810 	 * output, and terminate any open list.
3811 	 */
3812 	gln_status_notify();
3813 	gln_output_endlist();
3814 	gln_output_flush();
3815 
3816 	/*
3817 	 * Level 9 games tend not to issue a prompt after reading an empty
3818 	 * line of input, and the Adrian Mole games don't issue a prompt at
3819 	 * all when outside the 1/2/3 menuing system.  This can make for a
3820 	 * very blank looking screen.
3821 	 *
3822 	 * To slightly improve things, if it looks like we didn't get a
3823 	 * prompt from the game, do our own.
3824 	 */
3825 	if (gln_prompt_enabled && !gln_game_prompted()) {
3826 		gln_normal_char('\n');
3827 		gln_normal_string(GLN_INPUT_PROMPT);
3828 	}
3829 
3830 	/*
3831 	 * If we have an input log to read from, use that until it is exhausted.  On
3832 	 * end of file, close the stream and resume input from line requests.
3833 	 */
3834 	if (gln_readlog_stream) {
3835 		glui32 chars;
3836 
3837 		/* Get the next line from the log stream. */
3838 		chars = g_vm->glk_get_line_stream(gln_readlog_stream, buffer, size);
3839 		if (chars > 0) {
3840 			/* Echo the line just read in input style. */
3841 			g_vm->glk_set_style(style_Input);
3842 			g_vm->glk_put_buffer(buffer, chars);
3843 			g_vm->glk_set_style(style_Normal);
3844 
3845 			/* Tick the watchdog, and return. */
3846 			gln_watchdog_tick();
3847 			return TRUE;
3848 		}
3849 
3850 		/*
3851 		 * We're at the end of the log stream.  Close it, and then continue
3852 		 * on to request a line from Glk.
3853 		 */
3854 		g_vm->glk_stream_close(gln_readlog_stream, nullptr);
3855 		gln_readlog_stream = nullptr;
3856 	}
3857 
3858 	/*
3859 	 * No input log being read, or we just hit the end of file on one.  Revert
3860 	 * to normal line input; start by getting a new line from Glk.
3861 	 */
3862 	g_vm->glk_request_line_event(gln_main_window, buffer, size - 1, 0);
3863 	gln_event_wait(evtype_LineInput, &event);
3864 	if (g_vm->shouldQuit()) {
3865 		g_vm->glk_cancel_line_event(gln_main_window, &event);
3866 		gln_stop_reason = STOP_EXIT;
3867 		return FALSE;
3868 	}
3869 
3870 	/* Terminate the input line with a NUL. */
3871 	assert((int)event.val1 <= size - 1);
3872 	buffer[event.val1] = '\0';
3873 
3874 	/*
3875 	 * If neither abbreviations nor local commands are enabled, nor game
3876 	 * command interceptions, use the data read above without further massaging.
3877 	 */
3878 	if (gln_abbreviations_enabled
3879 	        || gln_commands_enabled || gln_intercept_enabled) {
3880 		char *command;
3881 
3882 		/*
3883 		 * If the first non-space input character is a quote, bypass all
3884 		 * abbreviation expansion and local command recognition, and use the
3885 		 * unadulterated input, less introductory quote.
3886 		 */
3887 		command = buffer + strspn(buffer, "\t ");
3888 		if (command[0] == GLN_QUOTED_INPUT) {
3889 			/* Delete the quote with memmove(). */
3890 			memmove(command, command + 1, strlen(command));
3891 		} else {
3892 			/* Check for, and expand, and abbreviated commands. */
3893 			if (gln_abbreviations_enabled)
3894 				gln_expand_abbreviations(buffer, size);
3895 
3896 			/*
3897 			 * Check for standalone "help", then for Glk port special commands;
3898 			 * suppress the interpreter's use of this input for Glk commands by
3899 			 * returning FALSE.
3900 			 */
3901 			if (gln_commands_enabled) {
3902 				int posn;
3903 
3904 				posn = strspn(buffer, "\t ");
3905 				if (gln_strncasecmp(buffer + posn, "help", strlen("help")) == 0) {
3906 					if (strspn(buffer + posn + strlen("help"), "\t ")
3907 					        == strlen(buffer + posn + strlen("help"))) {
3908 						gln_output_register_help_request();
3909 					}
3910 				}
3911 
3912 				if (gln_command_escape(buffer)) {
3913 					gln_output_silence_help_hints();
3914 					gln_watchdog_tick();
3915 					return FALSE;
3916 				}
3917 			}
3918 
3919 			/*
3920 			 * Check for locally intercepted commands, again returning FALSE if
3921 			 * one is handled.
3922 			 */
3923 			if (gln_intercept_enabled) {
3924 				if (gln_command_intercept(buffer)) {
3925 					gln_watchdog_tick();
3926 					return FALSE;
3927 				}
3928 			}
3929 		}
3930 	}
3931 
3932 	/*
3933 	 * If there is an input log active, log this input string to it. Note that
3934 	 * by logging here we get any abbreviation expansions but we won't log glk
3935 	 * special commands, nor any input read from a current open input log.
3936 	 */
3937 	if (gln_inputlog_stream) {
3938 		g_vm->glk_put_string_stream(gln_inputlog_stream, buffer);
3939 		g_vm->glk_put_char_stream(gln_inputlog_stream, '\n');
3940 	}
3941 
3942 	gln_watchdog_tick();
3943 	return TRUE;
3944 }
3945 
3946 
3947 /*
3948  * os_readchar()
3949  *
3950  * Poll the keyboard for characters, and return the character code of any key
3951  * pressed, or 0 if none pressed.
3952  *
3953  * Simple though this sounds, it's tough to do right in a timesharing OS, and
3954  * requires something close to an abuse of Glk.
3955  *
3956  * The initial, tempting, implementation is to wait inside this function for
3957  * a key press, then return the code.  Unfortunately, this causes problems in
3958  * the Level 9 interpreter.  Here's why: the interpreter is a VM emulating a
3959  * single-user microprocessor system.  On such a system, it's quite okay for
3960  * code to spin in a loop waiting for a keypress; there's nothing else
3961  * happening on the system, so it can burn CPU.  To wait for a keypress, game
3962  * code might first wait for no-keypress (0 from this function), then a
3963  * keypress (non-0), then no-keypress again (and it does indeed seem to do
3964  * just this).  If, in os_readchar(), we simply wait for and return key codes,
3965  * we'll never return a 0, so the above wait for a keypress in the game will
3966  * hang forever.
3967  *
3968  * To make matters more complex, some Level 9 games poll for keypresses as a
3969  * way for a user to halt scrolling.  For these polls, we really want to
3970  * return 0, otherwise the output grinds to a halt.  Moreover, some games even
3971  * use key polling as a crude form of timeout - poll and increment a counter,
3972  * and exit when either os_readchar() returns non-0, or after some 300 or so
3973  * polls.
3974  *
3975  * So, this function MUST return 0 sometimes, and real key codes other times.
3976  * The solution adopted is best described as expedient.  Depending on what Glk
3977  * provides in the way of timers, we'll do one of two things:
3978  *
3979  *   o If we have timers, we'll set up a timeout, and poll for a key press
3980  *     within that timeout.  As a way to smooth output for games that use key
3981  *     press polling for scroll control, we'll ignore calls until we get two
3982  *     in a row without intervening character output.
3983  *
3984  *   o If we don't have timers, then we'll return 0 most of the time, and then
3985  *     really wait for a key one time out of some number.  A game polling for
3986  *     keypresses to halt scrolling will probably be to the point where it
3987  *     cannot continue without user input at this juncture, and once we've
3988  *     rejected a few hundred calls we can now really wait for Glk key press
3989  *     event, and avoid a spinning loop.  A game using key polling as crude
3990  *     timing may, or may not, time out in the calls for which we return 0.
3991  *
3992  * Empirically, this all seems to work.  The only odd behaviour is with the
3993  * DEMO mode of Adrian Mole where Glk has no timers, and this is primarily
3994  * because the DEMO mode relies on the delay of keyboard polling for part of
3995  * its effect; on a modern system, the time to call through here is nowhere
3996  * near the time consumed by the original platform.  The other point of note
3997  * is that this all means that we can't return characters from any readlog
3998  * with this function; its timing stuff and its general polling nature make
3999  * it impossible to connect to readlog, so it just won't work at all with the
4000  * Adrian Mole games, Glk timers or otherwise.
4001  */
os_readchar(int millis)4002 char os_readchar(int millis) {
4003 	static int call_count = 0;
4004 
4005 	event_t event;
4006 	char character;
4007 
4008 	/* If doing linemode graphics, run all graphic opcodes available. */
4009 	gln_linegraphics_process();
4010 
4011 	/*
4012 	 * Here's the way we try to emulate keyboard polling for the case of no Glk
4013 	 * timers.  We'll say nothing is pressed for some number of consecutive
4014 	 * calls, then continue after that number of calls.
4015 	 */
4016 	if (!g_vm->glk_gestalt(gestalt_Timer, 0)) {
4017 		if (++call_count < GLN_READCHAR_LIMIT) {
4018 			/* Call tick as we may be outside an opcode loop. */
4019 			g_vm->glk_tick();
4020 			gln_watchdog_tick();
4021 			return 0;
4022 		} else
4023 			call_count = 0;
4024 	}
4025 
4026 	/*
4027 	 * If we have Glk timers, we can smooth game output with games that contin-
4028 	 * uously use this input function by pretending that there is no keypress
4029 	 * if the game printed output since the last call.  This helps with the
4030 	 * Adrian Mole games, which check for a keypress at the end of a line as a
4031 	 * way to temporarily halt scrolling.
4032 	 */
4033 	if (g_vm->glk_gestalt(gestalt_Timer, 0)) {
4034 		if (gln_recent_output()) {
4035 			/* Call tick as we may be outside an opcode loop. */
4036 			g_vm->glk_tick();
4037 			gln_watchdog_tick();
4038 			return 0;
4039 		}
4040 	}
4041 
4042 	/*
4043 	 * Now flush any pending buffered output.  We do it here rather than earlier
4044 	 * as it only needs to be done when we're going to request Glk input, and
4045 	 * we may have avoided this with the checks above.
4046 	 */
4047 	gln_status_notify();
4048 	gln_output_endlist();
4049 	gln_output_flush();
4050 
4051 	/*
4052 	 * Set up a character event request, and a timeout if the Glk library can
4053 	 * do them, and wait until one or the other occurs.  Loop until we read an
4054 	 * acceptable ASCII character (if we don't time out).
4055 	 */
4056 	do {
4057 		g_vm->glk_request_char_event(gln_main_window);
4058 		if (g_vm->glk_gestalt(gestalt_Timer, 0)) {
4059 			gln_arbitrate_request_timer_events(millis);
4060 			gln_event_wait_2(evtype_CharInput, evtype_Timer, &event);
4061 			gln_arbitrate_request_timer_events(0);
4062 
4063 			/*
4064 			 * If the event was a timeout, cancel the unfilled character
4065 			 * request, and return no-keypress value.
4066 			 */
4067 			if (event.type == evtype_Timer) {
4068 				g_vm->glk_cancel_char_event(gln_main_window);
4069 				gln_watchdog_tick();
4070 				return 0;
4071 			}
4072 		} else
4073 			gln_event_wait(evtype_CharInput, &event);
4074 	} while (event.val1 > BYTE_MAX && event.val1 != keycode_Return);
4075 
4076 	/* Extract the character from the event, converting Return, no echo. */
4077 	character = event.val1 == keycode_Return ? '\n' : event.val1;
4078 
4079 	/*
4080 	 * Special case ^U as a way to press a key on a wait, yet return a code to
4081 	 * the interpreter as if no key was pressed.  Useful if scrolling stops
4082 	 * where there are no Glk timers, to get scrolling started again.  ^U is
4083 	 * always active.
4084 	 */
4085 	if (character == GLN_CONTROL_U) {
4086 		gln_watchdog_tick();
4087 		return 0;
4088 	}
4089 
4090 	/*
4091 	 * Special case ^C to quit the program.  Without this, there's no easy way
4092 	 * to exit from a game that never uses os_input(), but instead continually
4093 	 * uses just os_readchar().  ^C handling can be disabled with command line
4094 	 * options.
4095 	 */
4096 	if (gln_intercept_enabled && character == GLN_CONTROL_C) {
4097 		if (gln_confirm("\n\nDo you really want to stop? [Y or N] ")) {
4098 			gln_stop_reason = STOP_EXIT;
4099 			StopGame();
4100 
4101 			gln_watchdog_tick();
4102 			return 0;
4103 		}
4104 	}
4105 
4106 	/*
4107 	 * If there is a transcript stream, send the input to it as a single line
4108 	 * string, otherwise it won't be visible in the transcript.
4109 	 */
4110 	if (gln_transcript_stream) {
4111 		g_vm->glk_put_char_stream(gln_transcript_stream, character);
4112 		g_vm->glk_put_char_stream(gln_transcript_stream, '\n');
4113 	}
4114 
4115 	/* Finally, return the single character read. */
4116 	gln_watchdog_tick();
4117 	return character;
4118 }
4119 
4120 
4121 /*
4122  * os_stoplist()
4123  *
4124  * This is called from #dictionary listings to poll for a request to stop
4125  * the listing.  A check for keypress is usual at this point.  However, Glk
4126  * cannot check for keypresses without a delay, which slows listing consid-
4127  * erably, since it also adjusts and renders the display.  As a compromise,
4128  * then, we'll check for keypresses on a small percentage of calls, say one
4129  * in ten, which means that listings happen with only a short delay, but
4130  * there's still an opportunity to stop them.
4131  *
4132  * This applies only where the Glk library has timers.  Where it doesn't, we
4133  * can't check for keypresses without blocking, so we do no checks at all,
4134  * and let lists always run to completion.
4135  */
os_stoplist()4136 gln_bool os_stoplist() {
4137 	static int call_count = 0;
4138 
4139 	event_t event;
4140 	int is_stop_confirmed;
4141 
4142 	/* Note that the interpreter is producing a list. */
4143 	gln_inside_list = TRUE;
4144 
4145 	/*
4146 	 * If there are no Glk timers, then polling for a keypress but continuing
4147 	 * on if there isn't one is not an option.  So flush output, return FALSE,
4148 	 * and just keep listing on to the end.
4149 	 */
4150 	if (!g_vm->glk_gestalt(gestalt_Timer, 0)) {
4151 		gln_output_flush();
4152 		gln_watchdog_tick();
4153 		return FALSE;
4154 	}
4155 
4156 	/* Increment the call count, and return FALSE if under the limit. */
4157 	if (++call_count < GLN_STOPLIST_LIMIT) {
4158 		/* Call tick as we may be outside an opcode loop. */
4159 		g_vm->glk_tick();
4160 		gln_watchdog_tick();
4161 		return FALSE;
4162 	} else
4163 		call_count = 0;
4164 
4165 	/* Flush any pending buffered output, delayed to here in case avoidable. */
4166 	gln_output_flush();
4167 
4168 	/*
4169 	 * Look for a keypress, with a very short timeout in place, in a similar
4170 	 * way as done for os_readchar() above.
4171 	 */
4172 	g_vm->glk_request_char_event(gln_main_window);
4173 	gln_arbitrate_request_timer_events(GLN_STOPLIST_TIMEOUT);
4174 	gln_event_wait_2(evtype_CharInput, evtype_Timer, &event);
4175 	gln_arbitrate_request_timer_events(0);
4176 
4177 	/*
4178 	 * If the event was a timeout, cancel the unfilled character request, and
4179 	 * return FALSE to continue listing.
4180 	 */
4181 	if (event.type == evtype_Timer) {
4182 		g_vm->glk_cancel_char_event(gln_main_window);
4183 		gln_watchdog_tick();
4184 		return FALSE;
4185 	}
4186 
4187 	/* Keypress detected, so offer to stop listing. */
4188 	assert(event.type == evtype_CharInput);
4189 	is_stop_confirmed = gln_confirm("\n\nStop listing? [Y or N] ");
4190 
4191 	/*
4192 	 * As we've output a newline, we no longer consider that we're inside a
4193 	 * list.  Clear the flag, and also clear prompt detection by polling it.
4194 	 */
4195 	gln_inside_list = FALSE;
4196 	gln_game_prompted();
4197 
4198 	/* Return TRUE if stop was confirmed, FALSE to keep listing. */
4199 	gln_watchdog_tick();
4200 	return is_stop_confirmed;
4201 }
4202 
4203 
4204 /*
4205  * gln_confirm()
4206  *
4207  * Print a confirmation prompt, and read a single input character, taking
4208  * only [YyNn] input.  If the character is 'Y' or 'y', return TRUE.
4209  */
gln_confirm(const char * prompt)4210 static int gln_confirm(const char *prompt) {
4211 	event_t event;
4212 	unsigned char response;
4213 	assert(prompt);
4214 
4215 	/*
4216 	 * Print the confirmation prompt, in a style that hints that it's from the
4217 	 * interpreter, not the game.
4218 	 */
4219 	gln_standout_string(prompt);
4220 
4221 	/* Wait for a single 'Y' or 'N' character response. */
4222 	response = ' ';
4223 	do {
4224 		g_vm->glk_request_char_event(gln_main_window);
4225 		gln_event_wait(evtype_CharInput, &event);
4226 
4227 		if (event.val1 <= BYTE_MAX)
4228 			response = g_vm->glk_char_to_upper(event.val1);
4229 	} while (!(response == 'Y' || response == 'N'));
4230 
4231 	/* Echo the confirmation response, and a blank line. */
4232 	g_vm->glk_set_style(style_Input);
4233 	g_vm->glk_put_string(response == 'Y' ? "Yes" : "No");
4234 	g_vm->glk_set_style(style_Normal);
4235 	g_vm->glk_put_string("\n\n");
4236 
4237 	return response == 'Y';
4238 }
4239 
4240 
4241 /*---------------------------------------------------------------------*/
4242 /*  Glk port event functions                                           */
4243 /*---------------------------------------------------------------------*/
4244 
4245 /*
4246  * gln_event_wait_2()
4247  * gln_event_wait()
4248  *
4249  * Process Glk events until one of the expected type, or types, arrives.
4250  * Return the event of that type.
4251  */
gln_event_wait_2(glui32 wait_type_1,glui32 wait_type_2,event_t * event)4252 static void gln_event_wait_2(glui32 wait_type_1, glui32 wait_type_2, event_t *event) {
4253 	assert(event);
4254 
4255 	do {
4256 		g_vm->glk_select(event);
4257 		if (g_vm->shouldQuit())
4258 			return;
4259 
4260 		switch (event->type) {
4261 		case evtype_Arrange:
4262 		case evtype_Redraw:
4263 			/* Refresh any sensitive windows on size events. */
4264 			gln_status_redraw();
4265 			gln_graphics_paint();
4266 			break;
4267 
4268 		case evtype_Timer:
4269 			/* Do background graphics updates on timeout. */
4270 			gln_graphics_timeout();
4271 			break;
4272 
4273 		default:
4274 			break;
4275 		}
4276 	} while (event->type != (EvType)wait_type_1 && event->type != (EvType)wait_type_2);
4277 }
4278 
gln_event_wait(glui32 wait_type,event_t * event)4279 static void gln_event_wait(glui32 wait_type, event_t *event) {
4280 	assert(event);
4281 	gln_event_wait_2(wait_type, evtype_None, event);
4282 }
4283 
4284 /*---------------------------------------------------------------------*/
4285 /*  Glk port multi-file game functions                                 */
4286 /*---------------------------------------------------------------------*/
4287 
4288 /*
4289  * os_get_game_file ()
4290  *
4291  * This function is a bit of a cheat.  It's called when the emulator has
4292  * detected a request from the game to restart the tape, on a tape-based
4293  * game.  Ordinarily, we should prompt the player for the name of the
4294  * system file containing the next game part.  Unfortunately, Glk doesn't
4295  * make this at all easy.  The requirement is to return a filename, but Glk
4296  * hides these inside fileref_t's, and won't let them out.
4297  *
4298  * Theoretically, according to the porting guide, this function should
4299  * prompt the user for a new game file name, that being the next part of the
4300  * game just (presumably) completed.
4301  *
4302  * However, the newname passed in is always the current game file name, as
4303  * level9.c ensures this for us.  If we search for, and find, and then inc-
4304  * rement, the last digit in the filename passed in, we wind up with, in
4305  * all likelihood, the right file path.  This is icky.
4306  *
4307  * This function is likely to be a source of portability problems on
4308  * platforms that don't implement a file path/name mechanism that matches
4309  * the expectations of the Level 9 base interpreter fairly closely.
4310  */
os_get_game_file(char * newname,int size)4311 gln_bool os_get_game_file(char *newname, int size) {
4312 	char *basename;
4313 	int index, digit, file_number;
4314 	Common::File f;
4315 	assert(newname);
4316 
4317 	basename = newname;
4318 
4319 	/* Search for the last numeric character in the basename. */
4320 	digit = -1;
4321 	for (index = strlen(basename) - 1; index >= 0; index--) {
4322 		if (Common::isDigit(basename[index])) {
4323 			digit = index;
4324 			break;
4325 		}
4326 	}
4327 	if (digit == -1) {
4328 		gln_watchdog_tick();
4329 		return FALSE;
4330 	}
4331 
4332 	/*
4333 	 * Convert the digit to a file number and increment it.  Fail if the new
4334 	 * file number is outside 1..9.
4335 	 */
4336 	file_number = basename[digit] - '0' + 1;
4337 	if (file_number < 1 || file_number > 9) {
4338 		gln_watchdog_tick();
4339 		return FALSE;
4340 	}
4341 
4342 	/* Write the new number back into the file. */
4343 	basename[digit] = file_number + '0';
4344 
4345 	/* Flush pending output, then display the filename generated. */
4346 	gln_output_flush();
4347 	gln_game_prompted();
4348 	gln_standout_string("\nNext load file: ");
4349 	gln_standout_string(basename);
4350 	gln_standout_string("\n\n");
4351 
4352 	/*
4353 	 * Try to confirm access to the file.  Otherwise, if we return TRUE but the
4354 	 * interpreter can't open the file, it stops the game, and we then lose any
4355 	 * chance to save it before quitting.
4356 	 */
4357 	if (!Common::File::exists(newname)) {
4358 		/* Restore newname to how it was, and return fail. */
4359 		basename[digit] = file_number - 1 + '0';
4360 		gln_watchdog_tick();
4361 		return FALSE;
4362 	}
4363 
4364 	/* Encourage game name re-lookup, and return success. */
4365 	g_vm->_detection.gln_gameid_game_name_reset();
4366 	gln_watchdog_tick();
4367 	return TRUE;
4368 }
4369 
4370 
4371 /*
4372  * os_set_filenumber()
4373  *
4374  * This function returns the next file in a game series for a disk-based
4375  * game (typically, gamedat1.dat, gamedat2.dat...).  It finds a single digit
4376  * in a filename, and resets it to the new value passed in.  The implemen-
4377  * tation here is based on the generic interface version, and with the same
4378  * limitations, specifically being limited to file numbers in the range 0
4379  * to 9, since it works on only the last digit character in the filename
4380  * buffer passed in.
4381  *
4382  * This function may also be a source of portability problems on platforms
4383  * that don't use "traditional" file path schemes.
4384  */
os_set_filenumber(char * newname,int size,int file_number)4385 void os_set_filenumber(char *newname, int size, int file_number) {
4386 	char *basename;
4387 	int index, digit;
4388 	assert(newname);
4389 
4390 	/* Do nothing if the file number is beyond what we can handle. */
4391 	if (file_number < 0 || file_number > 9) {
4392 		gln_watchdog_tick();
4393 		return;
4394 	}
4395 
4396 	basename = newname;
4397 
4398 	/* Search for the last numeric character in the basename. */
4399 	digit = -1;
4400 	for (index = strlen(basename) - 1; index >= 0; index--) {
4401 		if (Common::isDigit(basename[index])) {
4402 			digit = index;
4403 			break;
4404 		}
4405 	}
4406 	if (digit == -1) {
4407 		gln_watchdog_tick();
4408 		return;
4409 	}
4410 
4411 	/* Reset the digit in the file name. */
4412 	basename[digit] = file_number + '0';
4413 
4414 	/* Flush pending output, then display the filename generated. */
4415 	gln_output_flush();
4416 	gln_game_prompted();
4417 	gln_standout_string("\nNext disk file: ");
4418 	gln_standout_string(basename);
4419 	gln_standout_string("\n\n");
4420 
4421 	/* Encourage game name re-lookup, and return. */
4422 	g_vm->_detection.gln_gameid_game_name_reset();
4423 	gln_watchdog_tick();
4424 }
4425 
4426 
4427 /*
4428  * os_open_script_file()
4429  *
4430  * Handles player calls to the "#play" meta-command.  Because we have our
4431  * own way of handling scripts, this function is a stub.
4432  */
os_open_script_file()4433 Common::SeekableReadStream *os_open_script_file() {
4434 	return nullptr;
4435 }
4436 
4437 
4438 /*---------------------------------------------------------------------*/
4439 /*  Functions intercepted by link-time wrappers                        */
4440 /*---------------------------------------------------------------------*/
4441 
4442 /*
4443  * __wrap_toupper()
4444  * __wrap_tolower()
4445  *
4446  * Wrapper functions around toupper() and tolower().  The Linux linker's
4447  * --wrap option will convert calls to mumble() to __wrap_mumble() if we
4448  * give it the right options.  We'll use this feature to translate all
4449  * toupper() and tolower() calls in the interpreter code into calls to
4450  * Glk's versions of these functions.
4451  *
4452  * It's not critical that we do this.  If a linker, say a non-Linux one,
4453  * won't do --wrap, then just do without it.  It's unlikely that there
4454  * will be much noticeable difference.
4455  */
__wrap_toupper(int ch)4456 int __wrap_toupper(int ch) {
4457 	unsigned char uch;
4458 
4459 	uch = g_vm->glk_char_to_upper((unsigned char) ch);
4460 	return (int) uch;
4461 }
4462 
__wrap_tolower(int ch)4463 int __wrap_tolower(int ch) {
4464 	unsigned char lch;
4465 
4466 	lch = g_vm->glk_char_to_lower((unsigned char) ch);
4467 	return (int) lch;
4468 }
4469 
4470 
4471 /*---------------------------------------------------------------------*/
4472 /*  main() and options parsing                                         */
4473 /*---------------------------------------------------------------------*/
4474 
4475 /*
4476  * Watchdog timeout -- we'll wait for five seconds of silence from the core
4477  * interpreter before offering to stop the game forcibly, and we'll check
4478  * it every 10,240 opcodes.
4479  */
4480 static const int GLN_WATCHDOG_TIMEOUT = 5,
4481 				 GLN_WATCHDOG_PERIOD = 10240;
4482 
4483 /*
4484  * gln_establish_picture_filename()
4485  *
4486  * Given a game name, try to create an (optional) graphics data file. For
4487  * an input "file" X, the function looks for X.PIC or X.pic, then for
4488  * PICTURE.DAT or picture.dat in the same directory as X.  If the input file
4489  * already ends with a three-letter extension, it's stripped first.
4490  *
4491  * The function returns nullptr if a graphics file is not available.  It's not
4492  * fatal for this to be the case.  Filenames are malloc'ed, and need to be
4493  * freed by the caller.
4494  *
4495  * The function uses fopen() rather than access() since fopen() is an ANSI
4496  * standard function, and access() isn't.
4497  */
gln_establish_picture_filename(const char * name,char ** graphics)4498 static void gln_establish_picture_filename(const char *name, char **graphics) {
4499 	char *base, *graphics_file;
4500 	Common::File f;
4501 	assert(name && graphics);
4502 
4503 	/* Take a destroyable copy of the input filename. */
4504 	base = (char *)gln_malloc(strlen(name) + 1);
4505 	strcpy(base, name);
4506 
4507 	/* If base has an extension .LEV, .SNA, or similar, remove it. */
4508 	if (strrchr(base, '.')) {
4509 		base[strlen(base) - strlen(strrchr(base, '.'))] = '\0';
4510 	}
4511 
4512 	/* Allocate space for the return graphics file. */
4513 	graphics_file = (char *)gln_malloc(strlen(base) + strlen(".___") + 1);
4514 
4515 	/* Form a candidate graphics file, using a .PIC extension. */
4516 	if (!f.isOpen()) {
4517 		strcpy(graphics_file, base);
4518 		strcat(graphics_file, ".PIC");
4519 		f.open(graphics_file);
4520 	}
4521 
4522 	if (!f.isOpen()) {
4523 		strcpy(graphics_file, base);
4524 		strcat(graphics_file, ".pic");
4525 		f.open(graphics_file);
4526 	}
4527 
4528 	/* Form a candidate graphics file, using a .CGA extension. */
4529 	if (!f.isOpen()) {
4530 		strcpy(graphics_file, base);
4531 		strcat(graphics_file, ".CGA");
4532 		f.open(graphics_file);
4533 	}
4534 
4535 	if (!f.isOpen()) {
4536 		strcpy(graphics_file, base);
4537 		strcat(graphics_file, ".cga");
4538 		f.open(graphics_file);
4539 	}
4540 
4541 	/* Form a candidate graphics file, using a .HRC extension. */
4542 	if (!f.isOpen()) {
4543 		strcpy(graphics_file, base);
4544 		strcat(graphics_file, ".HRC");
4545 		f.open(graphics_file);
4546 	}
4547 
4548 	if (!f.isOpen()) {
4549 		strcpy(graphics_file, base);
4550 		strcat(graphics_file, ".hrc");
4551 		f.open(graphics_file);
4552 	}
4553 
4554 	/* No access to graphics file. */
4555 	if (!f.isOpen()) {
4556 		free(graphics_file);
4557 		graphics_file = nullptr;
4558 	}
4559 
4560 	f.close();
4561 
4562 	/* If we found a graphics file, return its name immediately. */
4563 	if (graphics_file) {
4564 		*graphics = graphics_file;
4565 		free(base);
4566 		return;
4567 	}
4568 
4569 	/* Again, allocate space for the return graphics file. */
4570 	graphics_file = (char *)gln_malloc(strlen(base) + strlen("PICTURE.DAT") + 1);
4571 
4572 	/* As above, form a candidate graphics file. */
4573 	strcpy(graphics_file, base);
4574 	strcat(graphics_file, "PICTURE.DAT");
4575 
4576 	if (!f.open(graphics_file)) {
4577 		/* Retry, using picture.dat extension instead. */
4578 		strcpy(graphics_file, base);
4579 		strcat(graphics_file, "picture.dat");
4580 		if (!f.open(graphics_file)) {
4581 			/*
4582 			 * No access to this graphics file.  In this case, free memory
4583 			 * and reset graphics file to nullptr.
4584 			 */
4585 			free(graphics_file);
4586 			graphics_file = nullptr;
4587 		}
4588 	}
4589 
4590 	f.close();
4591 
4592 	/*
4593 	 * Return whatever we found for the graphics file (nullptr if none found),
4594 	 * and free base.
4595 	 */
4596 	*graphics = graphics_file;
4597 	free(base);
4598 }
4599 
4600 /*
4601  * gln_startup_code()
4602  * gln_main()
4603  *
4604  * Together, these functions take the place of the original main().  The
4605  * first one is called from glkunix_startup_code(), to parse and generally
4606  * handle options.  The second is called from g_vm->glk_main(), and does the real
4607  * work of running the game.
4608  */
gln_startup_code(int argc,char * argv[])4609 int gln_startup_code(int argc, char *argv[]) {
4610 	int argv_index;
4611 
4612 	/* Handle command line arguments. */
4613 	for (argv_index = 1;
4614 	        argv_index < argc && argv[argv_index][0] == '-'; argv_index++) {
4615 		if (strcmp(argv[argv_index], "-ni") == 0) {
4616 			gln_intercept_enabled = FALSE;
4617 			continue;
4618 		}
4619 		if (strcmp(argv[argv_index], "-nc") == 0) {
4620 			gln_commands_enabled = FALSE;
4621 			continue;
4622 		}
4623 		if (strcmp(argv[argv_index], "-na") == 0) {
4624 			gln_abbreviations_enabled = FALSE;
4625 			continue;
4626 		}
4627 		if (strcmp(argv[argv_index], "-np") == 0) {
4628 			gln_graphics_enabled = FALSE;
4629 			continue;
4630 		}
4631 		if (strcmp(argv[argv_index], "-ne") == 0) {
4632 			gln_prompt_enabled = FALSE;
4633 			continue;
4634 		}
4635 		if (strcmp(argv[argv_index], "-nl") == 0) {
4636 			gln_loopcheck_enabled = FALSE;
4637 			continue;
4638 		}
4639 		return FALSE;
4640 	}
4641 
4642 	/* All startup options were handled successfully. */
4643 	return TRUE;
4644 }
4645 
gln_main(const char * filename)4646 void gln_main(const char *filename) {
4647 	char *graphics_file = nullptr;
4648 	int is_running;
4649 	int saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
4650 
4651 	/* Create the main Glk window, and set its stream as current. */
4652 	gln_main_window = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
4653 	if (!gln_main_window) {
4654 		gln_fatal("GLK: Can't open main window");
4655 		g_vm->glk_exit();
4656 	}
4657 	g_vm->glk_window_clear(gln_main_window);
4658 	g_vm->glk_set_window(gln_main_window);
4659 	g_vm->glk_set_style(style_Normal);
4660 
4661 	/*
4662 	 * Given the basic game name, try to come up with a usable graphics
4663 	 * filenames.  The graphics file may be null.
4664 	 */
4665 	gln_establish_picture_filename(filename, &graphics_file);
4666 
4667 	/*
4668 	 * Check Glk library capabilities, and note pictures are impossible if the
4669 	 * library can't offer both graphics and timers.  We need timers to create
4670 	 * the background "thread" for picture updates.
4671 	 */
4672 	gln_graphics_possible = g_vm->glk_gestalt(gestalt_Graphics, 0)
4673 	                        && g_vm->glk_gestalt(gestalt_Timer, 0);
4674 
4675 	/*
4676 	 * If pictures are impossible, clear pictures enabled flag.  That is, act
4677 	 * as if -np was given on the command line, even though it may not have
4678 	 * been.  If pictures are impossible, they can never be enabled.
4679 	 */
4680 	if (!gln_graphics_possible)
4681 		gln_graphics_enabled = FALSE;
4682 
4683 	/* If pictures are possible, search for bitmap graphics. */
4684 	if (gln_graphics_possible)
4685 		gln_graphics_locate_bitmaps(filename);
4686 
4687 	/* Try to create a one-line status window.  We can live without it. */
4688 	/*
4689 	  gln_status_window = g_vm->glk_window_open (gln_main_window,
4690 	                                       winmethod_Above | winmethod_Fixed,
4691 	                                       1, wintype_TextGrid, 0);
4692 	*/
4693 
4694 	/* Repeat this game until no more restarts requested. */
4695 	do {
4696 		g_vm->glk_window_clear(gln_main_window);
4697 
4698 		/*
4699 		 * In a multi-file game, restarting may mean reverting back to part one
4700 		 * of the game.  So we have to encourage a re-lookup of the game name
4701 		 * at this point.
4702 		 */
4703 		g_vm->_detection.gln_gameid_game_name_reset();
4704 
4705 		/* Load the game, sending in any established graphics file. */
4706 		int errNum = 0;
4707 		if (!LoadGame(filename, graphics_file)) {
4708 			if (gln_status_window)
4709 				g_vm->glk_window_close(gln_status_window, nullptr);
4710 			gln_header_string("Glk Level 9 Error\n\n");
4711 			gln_normal_string("Can't find, open, or load game file '");
4712 			gln_normal_string(filename);
4713 			gln_normal_char('\'');
4714 			if (errNum != 0) {
4715 				gln_normal_string(": ERROR");
4716 			}
4717 			gln_normal_char('\n');
4718 
4719 			/*
4720 			 * Nothing more to be done, so we'll free interpreter allocated
4721 			 * memory, then break rather than exit, to run memory cleanup and
4722 			 * close any open streams.
4723 			 */
4724 			FreeMemory();
4725 			break;
4726 		}
4727 
4728 		/* Print out a short banner. */
4729 		gln_header_string("\nLevel 9 Interpreter, ScummVM version\n");
4730 
4731 		/*
4732 		 * Set the stop reason indicator to none.  A game will then exit with a
4733 		 * reason if we call StopGame(), or none if it exits of its own accord
4734 		 * (or with the "#quit" command, say).
4735 		 */
4736 		gln_stop_reason = STOP_NONE;
4737 
4738 		/* Start, or restart, watchdog checking. */
4739 		gln_watchdog_start(GLN_WATCHDOG_TIMEOUT, GLN_WATCHDOG_PERIOD);
4740 
4741 		/* Load any savegame selected directly from the ScummVM launcher */
4742 		if (saveSlot != -1) {
4743 			if (g_vm->loadGameState(saveSlot).getCode() == Common::kNoError)
4744 				printstring("\rGame restored.\r");
4745 			else
4746 				printstring("\rUnable to restore game.\r");
4747 
4748 			saveSlot = -1;
4749 		}
4750 
4751 		/* Run the game until StopGame called, or RunGame() returns FALSE. */
4752 		do {
4753 			is_running = RunGame();
4754 			g_vm->glk_tick();
4755 
4756 			/* Poll for watchdog timeout. */
4757 			if (is_running && gln_watchdog_has_timed_out()) {
4758 				gln_stop_reason = STOP_FORCE;
4759 				StopGame();
4760 				break;
4761 			}
4762 		} while (is_running);
4763 
4764 		/* Stop watchdog functions, and flush any pending buffered output. */
4765 		gln_watchdog_stop();
4766 		gln_status_notify();
4767 		gln_output_flush();
4768 
4769 		/* Free interpreter allocated memory. */
4770 		FreeMemory();
4771 
4772 		/*
4773 		 * Unset any "stuck" game 'cheating' flag.  This can get stuck on if
4774 		 * exit is forced from the #cheat mode in the Adrian Mole games, which
4775 		 * otherwise loop infinitely.  Unsetting the flag here permits restarts;
4776 		 * without this, the core interpreter remains permanently in silent
4777 		 * #cheat mode.
4778 		 */
4779 		Cheating = FALSE;
4780 
4781 		/*
4782 		 * If the stop reason is none, something in the game stopped itself, or
4783 		 * the user entered "#quit".  If the stop reason is force, the user
4784 		 * terminated because of an apparent infinite loop.  For both of these,
4785 		 * offer the choice to restart, or not (equivalent to exit).
4786 		 */
4787 		if (gln_stop_reason == STOP_NONE || gln_stop_reason == STOP_FORCE) {
4788 			gln_standout_string(gln_stop_reason == STOP_NONE
4789 			                    ? "\nThe game has exited.\n"
4790 			                    : "\nGame exit was forced.  The current game"
4791 			                    " state is unrecoverable.  Sorry.\n");
4792 
4793 			if (gln_confirm("\nDo you want to restart? [Y or N] "))
4794 				gln_stop_reason = STOP_RESTART;
4795 			else
4796 				gln_stop_reason = STOP_EXIT;
4797 		}
4798 	} while (gln_stop_reason == STOP_RESTART);
4799 
4800 	/* Free any temporary memory that may have been used by graphics. */
4801 	gln_graphics_cleanup();
4802 	gln_linegraphics_cleanup();
4803 
4804 	/* Close any open transcript, input log, and/or read log. */
4805 	if (gln_transcript_stream) {
4806 		g_vm->glk_stream_close(gln_transcript_stream, nullptr);
4807 		gln_transcript_stream = nullptr;
4808 	}
4809 	if (gln_inputlog_stream) {
4810 		g_vm->glk_stream_close(gln_inputlog_stream, nullptr);
4811 		gln_inputlog_stream = nullptr;
4812 	}
4813 	if (gln_readlog_stream) {
4814 		g_vm->glk_stream_close(gln_readlog_stream, nullptr);
4815 		gln_readlog_stream = nullptr;
4816 	}
4817 
4818 	/* Free any graphics file path. */
4819 	free(graphics_file);
4820 }
4821 
4822 } // End of namespace Level9
4823 } // End of namespace Glk
4824