1 /*
2  * ux_init.c - Unix interface, initialisation
3  *
4  * This file is part of Frotz.
5  *
6  * Frotz is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Frotz is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  * Or visit http://www.fsf.org/
20  */
21 
22 #define __UNIX_PORT_FILE
23 
24 /* For the MACOS port, get the _DARWIN_C_SOURCE define */
25 #include "../common/defs.h"
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include <time.h>
32 
33 #include <unistd.h>
34 #include <ctype.h>
35 #include <libgen.h>
36 
37 #include "ux_defines.h"
38 
39 #ifdef USE_NCURSES_H
40 #include <ncurses.h>
41 #else
42 #include <curses.h>
43 #endif
44 
45 #ifndef WIN32
46 #include <termios.h>
47 #endif
48 
49 #include "ux_frotz.h"
50 #include "ux_blorb.h"
51 
52 #ifdef USE_UTF8
53 #include <locale.h>
54 #endif
55 
56 extern f_setup_t f_setup;
57 extern z_header_t z_header;
58 
59 volatile sig_atomic_t terminal_resized = 0;
60 
61 static void sigwinch_handler(int);
62 #define INFORMATION "\
63 An interpreter for all Infocom and other Z-Machine games.\n\
64 \n\
65 Syntax: frotz [options] story-file\n\
66   -a   watch attribute setting    \t -O   watch object locating\n\
67   -A   watch attribute testing    \t -p   plain ASCII output only\n\
68   -b <colorname> background color \t -P   alter piracy opcode\n\
69   -c # context lines              \t -q   quiet (disable sound effects)\n\
70   -d   disable color              \t -r # right margin\n\
71   -e   enable sound               \t -R <path> restricted read/write\n\
72   -f <colorname> foreground color \t -s # random number seed value\n\
73   -F   Force color mode           \t -S # transcript width\n\
74   -h # text height                \t -t   set Tandy bit\n\
75   -i   ignore fatal errors        \t -u # slots for multiple undo\n\
76   -I # interpreter number         \t -v   show version information\n\
77   -l # left margin                \t -w # text width\n\
78   -L <file> load this save file   \t -x   expand abbreviations g/x/z\n\
79   -o   watch object movement      \t -Z # error checking (see below)\n"
80 
81 #define INFO2 "\
82 Error checking: 0 none, 1 first only (default), 2 all, 3 exit after any error.\n\
83 For more options and explanations, please read the manual page.\n"
84 
85 
86 /*
87 char stripped_story_name[FILENAME_MAX+1];
88 char semi_stripped_story_name[FILENAME_MAX+1];
89 */
90 
91 extern u_setup_t u_setup;
92 
93 /* static void sigwinch_handler(int); */
94 static void sigint_handler(int);
95 /* static void redraw(void); */
96 
97 static void	print_version(void);
98 static int	getconfig(char *);
99 static int	getbool(char *);
100 static int	getcolor(char *);
101 static int	geterrmode(char *);
102 static FILE	*pathopen(const char *, const char *, const char *);
103 
104 
print_c_string(const char * s)105 static void print_c_string (const char *s)
106 {
107 	zchar c;
108 
109 	while ((c = *s++) != 0)
110 		os_display_char (c);
111 } /* print_c_string */
112 
113 
114 /*
115  * os_warn
116  *
117  * Display a warning message and continue with the game.
118  *
119  */
os_warn(const char * s,...)120 void os_warn (const char *s, ...)
121 {
122 	if (u_setup.curses_active) {
123 		/* Solaris 2.6's cc complains if the below cast is missing */
124 		print_c_string("\n\n");
125 		os_beep(BEEP_HIGH);
126 		os_set_text_style(BOLDFACE_STYLE);
127 		print_c_string("Warning: ");
128 		os_set_text_style(0);
129 		print_c_string(s);
130 		print_c_string("\n");
131 		new_line();
132 	}
133 } /* os_warn */
134 
135 
136 /*
137  * os_fatal
138  *
139  * Display error message and exit program.
140  *
141  */
os_fatal(const char * s,...)142 void os_fatal (const char *s, ...)
143 {
144 	if (u_setup.curses_active) {
145 		/* Solaris 2.6's cc complains if the below cast is missing */
146 		print_c_string("\n\n");
147 		os_beep(BEEP_HIGH);
148 		os_set_text_style(BOLDFACE_STYLE);
149 		print_c_string("Fatal error: ");
150 		os_set_text_style(0);
151 		print_c_string(s);
152 		print_c_string("\n");
153 		new_line();
154 		if (f_setup.ignore_errors) {
155 			print_c_string("Continuing anyway...");
156 			new_line();
157 			scrollok(stdscr, TRUE);
158 			scroll(stdscr);
159 			flush_buffer();
160 			refresh();
161 			return;
162 		} else {
163 			os_reset_screen();
164 			ux_blorb_stop();
165 			os_quit(EXIT_FAILURE);
166 		}
167 	}
168 
169 	fputs ("\nFatal error: ", stderr);
170 	fputs (s, stderr);
171 	if (f_setup.ignore_errors) {
172 		fputs ("\n\rContinuing anyway", stderr);
173 		return;
174 	}
175 
176 	fputs ("\n\n", stderr);
177 
178 	os_quit (EXIT_FAILURE);
179 } /* os_fatal */
180 
181 /* extern char script_name[]; */
182 /* extern char command_name[]; */
183 /* extern char save_name[];*/
184 /*extern char auxilary_name[];*/
185 
186 
187 /*
188  * os_process_arguments
189  *
190  * Handle command line switches.
191  * Some variables may be set to activate special features of Frotz.
192  *
193  */
os_process_arguments(int argc,char * argv[])194 void os_process_arguments (int argc, char *argv[])
195 {
196 	int c;
197 	char *p = NULL;
198 /*
199  * FIXME: Remove this after K&R treatment
200  * FIXME: put this back before committing merge fixes
201  *    char *blorb_ext = NULL;
202  */
203 	char *home;
204 	char configfile[FILENAME_MAX + 1];
205 
206 	zoptarg = NULL;
207 
208 #if !defined(WIN32) && !defined(__HAIKU__)
209 	if ((getuid() == 0) || (geteuid() == 0)) {
210 		printf("I won't run as root!\n");
211 		os_quit(EXIT_FAILURE);
212 	}
213 #endif
214 
215 #ifdef WIN32
216 #define HOMEDIR "USERPROFILE"
217 #else
218 #define HOMEDIR "HOME"
219 #endif
220 
221 	if ((home = getenv(HOMEDIR)) == NULL) {
222 		printf("Hard drive on fire!\n");
223 		os_quit(EXIT_FAILURE);
224 	}
225 
226 
227 /*
228  * It doesn't look like Frotz can reliably be resized given its current
229  * screen-handling code.  While playing with Nitfol, I noticed that it
230  * resized itself fairly reliably, even though the terminal looked rather
231  * ugly to begin with.  Since Nitfol uses the Glk library for screen I/O,
232  * I think there might be something in Glk that can make resizing easier.
233  * Something to think about for later.
234  *
235  */
236 
237 
238 /*	if (signal(SIGWINCH, SIG_IGN) != SIG_IGN) */
239 	signal(SIGWINCH, sigwinch_handler);
240 
241 	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
242 		signal(SIGINT, sigint_handler);
243 
244 	if (signal(SIGTTIN, SIG_IGN) != SIG_IGN)
245 		signal(SIGTTIN, SIG_IGN);
246 
247 	if (signal(SIGTTOU, SIG_IGN) != SIG_IGN)
248 		signal(SIGTTOU, SIG_IGN);
249 
250 	/* First check for a "$HOME/.frotzrc". */
251 	/* If not found, look for CONFIG_DIR/frotz.conf */
252 	/* $HOME/.frotzrc overrides CONFIG_DIR/frotz.conf */
253 
254 	strncpy(configfile, home, FILENAME_MAX);
255 	strncat(configfile, "/", 2);
256 
257 	strncat(configfile, USER_CONFIG, strlen(USER_CONFIG) + 1);
258 	if (!getconfig(configfile)) {
259 		strncpy(configfile, CONFIG_DIR, FILENAME_MAX);
260 		strncat(configfile, "/", 2);    /* added by DJP */
261 		strncat(configfile, MASTER_CONFIG, FILENAME_MAX-10);
262 		getconfig(configfile);  /* we're not concerned if this fails */
263 	}
264 
265 	/* Parse the options */
266 	do {
267 		c = zgetopt(argc, argv, "-aAb:c:def:Fh:iI:l:L:oOpPqr:R:s:S:tu:vw:W:xZ:");
268 		switch(c) {
269 		case 'a': f_setup.attribute_assignment = 1; break;
270 		case 'A': f_setup.attribute_testing = 1; break;
271 		case 'b':
272 			u_setup.background_color = getcolor(zoptarg);
273 			u_setup.force_color = 1;
274 			u_setup.disable_color = 0;
275 			if ((u_setup.background_color < 2) ||
276 			    (u_setup.background_color > 9))
277 				u_setup.background_color = -1;
278 			break;
279 		case 'c': f_setup.context_lines = atoi(zoptarg); break;
280 		case 'd': u_setup.disable_color = 1; break;
281 		case 'e': f_setup.sound = 1; break;
282 		case 'f':
283 			u_setup.foreground_color = getcolor(zoptarg);
284 			u_setup.force_color = 1;
285 			u_setup.disable_color = 0;
286 			if ((u_setup.foreground_color < 2) ||
287 			  (u_setup.foreground_color > 9))
288 				u_setup.foreground_color = -1;
289 			break;
290 		case 'F':
291 			u_setup.force_color = 1;
292 			u_setup.disable_color = 0;
293 			break;
294 		case 'h': u_setup.screen_height = atoi(zoptarg); break;
295 		case 'i': f_setup.ignore_errors = 1; break;
296 		case 'I': f_setup.interpreter_number = atoi(zoptarg); break;
297 		case 'l': f_setup.left_margin = atoi(zoptarg); break;
298 		case 'L':
299 			f_setup.restore_mode = 1;
300 			f_setup.tmp_save_name = strdup(zoptarg);
301 			break;
302 		case 'o': f_setup.object_movement = 1; break;
303 		case 'O': f_setup.object_locating = 1; break;
304 		case 'p': u_setup.plain_ascii = 1; break;
305 		case 'P': f_setup.piracy = 1; break;
306 		case 'q': f_setup.sound = 0; break;
307 		case 'r': f_setup.right_margin = atoi(zoptarg); break;
308 		case 'R': f_setup.restricted_path = strndup(zoptarg, PATH_MAX); break;
309 		case 's': u_setup.random_seed = atoi(zoptarg); break;
310 		case 'S': f_setup.script_cols = atoi(zoptarg); break;
311 		case 't': u_setup.tandy_bit = 1; break;
312 		case 'u': f_setup.undo_slots = atoi(zoptarg); break;
313 		case 'v': print_version(); os_quit(EXIT_SUCCESS); break;
314 		case 'w': u_setup.screen_width = atoi(zoptarg); break;
315 		case 'x': f_setup.expand_abbreviations = 1; break;
316 		case 'Z':
317 			f_setup.err_report_mode = atoi(zoptarg);
318 			if ((f_setup.err_report_mode < ERR_REPORT_NEVER) ||
319 			  (f_setup.err_report_mode > ERR_REPORT_FATAL))
320 				f_setup.err_report_mode = ERR_DEFAULT_REPORT_MODE;
321 			break;
322 		}
323 	} while (c != EOF);
324 
325 	if (argv[zoptind] == NULL) {
326 		printf("FROTZ V%s - Curses interface.  ", VERSION);
327 
328 #ifndef NO_SOUND
329 		printf("Audio output enabled.");
330 #else
331 		printf("Audio output disabled.");
332 #endif
333 		putchar('\n');
334 
335 		puts (INFORMATION);
336 		puts (INFO2);
337 		os_quit (EXIT_SUCCESS);
338 	}
339 
340 	/* This section is exceedingly messy and really can't be fixed
341 	 * without major changes all over the place.
342 	 */
343 
344 	/* Save the story file name */
345 
346 	f_setup.story_file = strdup(argv[zoptind]);
347 	f_setup.story_name = strdup(basename(argv[zoptind]));
348 
349 #ifndef NO_BLORB
350 	if (argv[zoptind+1] != NULL)
351 		f_setup.blorb_file = strdup(argv[zoptind+1]);
352 #endif
353 
354 	/* Now strip off the extension */
355 	p = strrchr(f_setup.story_name, '.');
356 	if ( p != NULL )
357 		*p = '\0';  /* extension removed */
358 
359 	/* Create nice default file names */
360 	f_setup.script_name = malloc((strlen(f_setup.story_name) + strlen(EXT_SCRIPT)) * sizeof(char) + 1);
361 	memcpy(f_setup.script_name, f_setup.story_name, (strlen(f_setup.story_name) + strlen(EXT_SCRIPT)) * sizeof(char));
362 	strncat(f_setup.script_name, EXT_SCRIPT, strlen(EXT_SCRIPT) + 1);
363 
364 	f_setup.command_name = malloc((strlen(f_setup.story_name) + strlen(EXT_COMMAND)) * sizeof(char) + 1);
365 	memcpy(f_setup.command_name, f_setup.story_name, (strlen(f_setup.story_name) + strlen(EXT_COMMAND)) * sizeof(char));
366 	strncat(f_setup.command_name, EXT_COMMAND, strlen(EXT_COMMAND) + 1);
367 
368 	if (!f_setup.restore_mode) {
369 		f_setup.save_name = malloc((strlen(f_setup.story_name) + strlen(EXT_SAVE)) * sizeof(char) + 1);
370 		memcpy(f_setup.save_name, f_setup.story_name, (strlen(f_setup.story_name) + strlen(EXT_SAVE)) * sizeof(char));
371 		strncat(f_setup.save_name, EXT_SAVE, strlen(EXT_SAVE) + 1);
372 	} else {  /*Set our auto load save as the name_save*/
373 		f_setup.save_name = malloc((strlen(f_setup.tmp_save_name) + strlen(EXT_SAVE)) * sizeof(char) + 1);
374 		memcpy(f_setup.save_name, f_setup.tmp_save_name, (strlen(f_setup.tmp_save_name) + strlen(EXT_SAVE)) * sizeof(char));
375 		free(f_setup.tmp_save_name);
376 	}
377 
378 	f_setup.aux_name = malloc((strlen(f_setup.story_name) + strlen(EXT_AUX)) * sizeof(char) + 1);
379 	memcpy(f_setup.aux_name, f_setup.story_name, (strlen(f_setup.story_name) + strlen(EXT_AUX)) * sizeof(char));
380 	strncat(f_setup.aux_name, EXT_AUX, strlen(EXT_AUX) + 1);
381 } /* os_process_arguments */
382 
383 
unix_get_terminal_size()384 void unix_get_terminal_size()
385 {
386 	int y, x;
387 	getmaxyx(stdscr, y, x);
388 
389 	/* 255 disables paging entirely. */
390 	if (u_setup.screen_height != -1)
391 		z_header.screen_rows = u_setup.screen_height;
392 	else
393 		z_header.screen_rows = MIN(254, y);
394 
395 	if (u_setup.screen_width != -1)
396 		z_header.screen_cols = u_setup.screen_width;
397 	else
398 		z_header.screen_cols = MIN(255, x);
399 
400 	if (z_header.screen_cols < 1) {
401 		endwin();
402 		u_setup.curses_active = FALSE;
403 		os_fatal("Invalid screen width. Must be between 1 and 255.");
404 	}
405 
406 	z_header.font_width = 1;
407 	z_header.font_height = 1;
408 
409 	z_header.screen_width = z_header.screen_cols;
410 	z_header.screen_height = z_header.screen_rows;
411 } /* unix_get_terminal */
412 
413 
414 /*
415  * os_init_screen
416  *
417  * Initialise the IO interface. Prepare the screen and other devices
418  * (mouse, sound board). Set various OS depending story file header
419  * entries:
420  *
421  *     z_header.config (aka flags 1)
422  *     z_header.flags (aka flags 2)
423  *     z_header.screen_cols (aka screen width in characters)
424  *     z_header.screen_rows (aka screen height in lines)
425  *     z_header.screen_width
426  *     z_header.screen_height
427  *     z_header.font_height (defaults to 1)
428  *     z_header.font_width (defaults to 1)
429  *     z_header.default_foreground
430  *     z_header.default_background
431  *     z_header.interpreter_number
432  *     z_header.interpreter_version
433  *     z_header.user_name (optional; not used by any game)
434  *
435  * Finally, set reserve_mem to the amount of memory (in bytes) that
436  * should not be used for multiple undo and reserved for later use.
437  *
438  * (Unix has a non brain-damaged memory model which dosen't require such
439  *  ugly hacks, neener neener neener. --GH :)
440  *
441  */
os_init_screen(void)442 void os_init_screen (void)
443 {
444 	/*trace(TRACE_CALLS);*/
445 
446 #ifdef USE_UTF8
447 	setlocale(LC_ALL, "");
448 #endif
449 
450 	if (getenv("ESCDELAY") == NULL)
451 		setenv("ESCDELAY", "50", 1);
452 
453 	if (initscr() == NULL) {    /* Set up curses */
454 		os_fatal("Unable to initialize curses. Maybe your $TERM setting is bad.");
455 		os_quit(EXIT_FAILURE);
456 	}
457 	u_setup.curses_active = 1;	/* Let os_fatal know curses is running */
458 	raw();				/* Raw input mode, no line processing */
459 	noecho();			/* No input echo */
460 	nonl();				/* No newline translation */
461 	intrflush(stdscr, TRUE);	/* Flush output on interrupt */
462 	keypad(stdscr, TRUE);		/* Enable the keypad and function keys */
463 	scrollok(stdscr, FALSE);	/* No scrolling unless explicitly asked for */
464 
465 	if (z_header.version == V3 && u_setup.tandy_bit != 0)
466 		z_header.config |= CONFIG_TANDY;
467 
468 	if (z_header.version == V3)
469 		z_header.config |= CONFIG_SPLITSCREEN;
470 
471 	if (z_header.version >= V4)
472 		z_header.config |= CONFIG_BOLDFACE | CONFIG_EMPHASIS | CONFIG_FIXED | CONFIG_TIMEDINPUT;
473 
474 	if (z_header.version >= V5)
475 		z_header.flags &= ~(GRAPHICS_FLAG | MOUSE_FLAG | MENU_FLAG);
476 
477 #ifdef NO_SOUND
478 	if (z_header.version >= V5)
479 		z_header.flags &= ~SOUND_FLAG;
480 
481 	if (z_header.version == V3)
482 		z_header.flags &= ~OLD_SOUND_FLAG;
483 #else
484 	if ((z_header.version >= V5) && (z_header.flags & SOUND_FLAG))
485 		z_header.flags |= SOUND_FLAG;
486 
487 	if ((z_header.version == V3) && (z_header.flags & OLD_SOUND_FLAG))
488 		z_header.flags |= OLD_SOUND_FLAG;
489 
490 	if ((z_header.version == V6) && (f_setup.sound != 0))
491 		z_header.config |= CONFIG_SOUND;
492 #endif
493 
494 	if (z_header.version >= V5 && (z_header.flags & UNDO_FLAG)) {
495 		if (f_setup.undo_slots == 0)
496 			z_header.flags &= ~UNDO_FLAG;
497 	}
498 
499 	unix_get_terminal_size();
500 
501 	/* Must be after screen dimensions are computed.  */
502 	if (z_header.version == V6) {
503 		if (unix_init_pictures())
504 			z_header.config |= CONFIG_PICTURES;
505 		else
506 			z_header.flags &= ~GRAPHICS_FLAG;
507 	}
508 
509 	/* Use the ms-dos interpreter number for v6, because that's the
510 	 * kind of graphics files we understand.  Otherwise, use DEC.  */
511 	if (f_setup.interpreter_number == INTERP_DEFAULT)
512 		z_header.interpreter_number = z_header.version == V6 ? INTERP_MSDOS : INTERP_DEC_20;
513 	else
514 		z_header.interpreter_number = f_setup.interpreter_number;
515 
516 	z_header.interpreter_version = 'F';
517 
518 #ifdef COLOR_SUPPORT
519 	/* Enable colors if the terminal supports them, the user did not
520 	 * disable colors, and the game or user requested colors.  User
521 	 * requests them by specifying a foreground or background.
522 	 */
523 	u_setup.color_enabled = (has_colors() && !u_setup.disable_color
524 		&& (((z_header.version >= V5) && (z_header.flags & COLOUR_FLAG))
525 		|| (u_setup.foreground_color != -1)
526 		|| (u_setup.background_color != -1)));
527 
528 	/* Maybe we don't want to muck about with changing $TERM to
529 	 * xterm-color which some supposedly current Unicies still don't
530 	 * understand.
531 	 */
532 	if (u_setup.force_color)
533 		u_setup.color_enabled = TRUE;
534 
535 	if (u_setup.color_enabled) {
536 		z_header.config |= CONFIG_COLOUR;
537 		z_header.flags |= COLOUR_FLAG; /* FIXME: beyond zork handling? */
538 		start_color();
539 		z_header.default_foreground = (u_setup.foreground_color == -1)
540 			? FOREGROUND_DEF : u_setup.foreground_color;
541 		z_header.default_background = (u_setup.background_color ==-1)
542 			? BACKGROUND_DEF : u_setup.background_color;
543 	} else
544 #endif /* COLOR_SUPPORT */
545 	{
546 		/* Set these per spec 8.3.2. */
547 		z_header.default_foreground = WHITE_COLOUR;
548 		z_header.default_background = BLACK_COLOUR;
549 		if (z_header.flags & COLOUR_FLAG) z_header.flags &= ~COLOUR_FLAG;
550 	}
551 	os_set_colour(z_header.default_foreground, z_header.default_background);
552 	os_erase_area(1, 1, z_header.screen_rows, z_header.screen_cols, 0);
553 } /* os_init_screen */
554 
555 
556 /*
557  * os_reset_screen
558  *
559  * Reset the screen before the program stops.
560  *
561  */
os_reset_screen(void)562 void os_reset_screen (void)
563 {
564 	os_stop_sample(0);
565 	os_set_text_style(0);
566 	print_c_string("[Hit any key to exit.]\n");
567 	os_read_key(0, FALSE);
568 } /* os_reset_screen */
569 
570 
571 /*
572  * os_quit
573  *
574  * Immediately and cleanly exit, passing along exit status.
575  *
576  */
os_quit(int status)577 void os_quit(int status)
578 {
579 	os_stop_sample(0);
580 	ux_blorb_stop();
581 	if (u_setup.curses_active) {
582 		scrollok(stdscr, TRUE);
583 		scroll(stdscr);
584 		refresh();
585 		endwin();
586 	}
587 	exit(status);
588 } /* os_quit */
589 
590 
591 /*
592  * os_restart_game
593  *
594  * This routine allows the interface to interfere with the process of
595  * restarting a game at various stages:
596  *
597  *     RESTART_BEGIN - restart has just begun
598  *     RESTART_WPROP_SET - window properties have been initialised
599  *     RESTART_END - restart is complete
600  *
601  */
os_restart_game(int UNUSED (stage))602 void os_restart_game (int UNUSED (stage))
603 {
604 	/* Nothing here yet */
605 } /* os_restart_game */
606 
607 
608 /*
609  * os_random_seed
610  *
611  * Return an appropriate random seed value in the range from 0 to
612  * 32767, possibly by using the current system time.
613  *
614  */
os_random_seed(void)615 int os_random_seed (void)
616 {
617 	/* Use the epoch as seed value */
618 	if (u_setup.random_seed == -1)
619 		return (time(0) & 0x7fff);
620 	else
621 		return u_setup.random_seed;
622 } /* os_random_seed */
623 
624 
625 /*
626  * os_path_open
627  *
628  * Open a file in the current directory.  If this fails, then search the
629  * directories in the ZCODE_PATH environmental variable.  If that's not
630  * defined, search INFOCOM_PATH.
631  *
632  */
os_path_open(const char * name,const char * mode)633 FILE *os_path_open(const char *name, const char *mode)
634 {
635 	FILE *fp;
636 	char *p;
637 
638 	/* Let's see if the file is in the currect directory */
639 	/* or if the user gave us a full path. */
640 	if ((fp = fopen(name, mode)))
641 		return fp;
642 
643 	/* If zcodepath is defined in a config file, check that path. */
644 	/* If we find the file a match in that path, great. */
645 	/* Otherwise, check some environmental variables. */
646 	if (f_setup.zcode_path != NULL) {
647 		if ((fp = pathopen(name, f_setup.zcode_path, mode)) != NULL)
648 			return fp;
649 	}
650 
651 	if ( (p = getenv(PATH1) ) == NULL)
652 		p = getenv(PATH2);
653 
654 	if (p != NULL) {
655 		fp = pathopen(name, p, mode);
656 		return fp;
657 	}
658 	return NULL;	/* give up */
659 } /* os_path_open() */
660 
661 
662 /*
663  * os_load_story
664  *
665  * This is different from os_path_open() because we need to see if the
666  * story file is actually a chunk inside a blorb file.  Right now we're
667  * looking only at the exact path we're given on the command line.
668  *
669  * Open a file in the current directory.  If this fails, then search the
670  * directories in the ZCODE_PATH environmental variable.  If that's not
671  * defined, search INFOCOM_PATH.
672  *
673  */
os_load_story(void)674 FILE *os_load_story(void)
675 {
676 #ifndef NO_BLORB
677 	FILE *fp;
678 
679 	switch (ux_blorb_init(f_setup.story_file)) {
680 	case bb_err_NoBlorb:
681 		/* printf("No blorb file found.\n\n"); */
682 		break;
683 	case bb_err_Format:
684 		printf("Blorb file loaded, but unable to build map.\n\n");
685 		break;
686 	case bb_err_NotFound:
687 		printf("Blorb file loaded, but lacks ZCOD executable chunk.\n\n");
688 		break;
689 	case bb_err_None:
690 		/* printf("No blorb errors.\n\n"); */
691 		break;
692 	}
693 
694 	fp = os_path_open(f_setup.story_file, "rb");
695 
696 	/* Is this a Blorb file containing Zcode? */
697 	if (f_setup.exec_in_blorb)
698 		fseek(fp, blorb_res.data.startpos, SEEK_SET);
699 
700 	return fp;
701 #else
702 	return os_path_open(f_setup.story_file, "rb");
703 #endif
704 } /* os_load_story */
705 
706 
707 /*
708  * os_storyfile_seek
709  *
710  * Seek into a storyfile, either a standalone file or the
711  * ZCODE chunk of a blorb file.
712  *
713  */
os_storyfile_seek(FILE * fp,long offset,int whence)714 int os_storyfile_seek(FILE * fp, long offset, int whence)
715 {
716 #ifndef NO_BLORB
717 	/* Is this a Blorb file containing Zcode? */
718 	if (f_setup.exec_in_blorb) {
719 		switch (whence) {
720 		case SEEK_END:
721 			return fseek(fp, blorb_res.data.startpos + blorb_res.length + offset, SEEK_SET);
722 			break;
723 		case SEEK_CUR:
724 			return fseek(fp, offset, SEEK_CUR);
725 			break;
726 		case SEEK_SET:
727 			/* SEEK_SET falls through to default */
728 		default:
729 			return fseek(fp, blorb_res.data.startpos + offset, SEEK_SET);
730 			break;
731 		}
732 	} else
733 		return fseek(fp, offset, whence);
734 #else
735 	return fseek(fp, offset, whence);
736 #endif
737 } /* os_storyfile_seek */
738 
739 
740 /*
741  * os_storyfile_tell
742  *
743  * Tell the position in a storyfile, either a standalone file
744  * or the ZCODE chunk of a blorb file.
745  *
746  */
os_storyfile_tell(FILE * fp)747 int os_storyfile_tell(FILE * fp)
748 {
749 #ifdef NO_BLORB
750 	/* Is this a Blorb file containing Zcode? */
751 	if (f_setup.exec_in_blorb)
752 		return ftell(fp) - blorb_res.data.startpos;
753 	else
754 		return ftell(fp);
755 #else
756 	return ftell(fp);
757 #endif
758 } /* os_storyfile_tell */
759 
760 
761 /*
762  * pathopen
763  *
764  * Given a standard Unix-style path and a filename, search the path for
765  * that file.  If found, return a pointer to that file
766  *
767  */
pathopen(const char * name,const char * path,const char * mode)768 static FILE *pathopen(const char *name, const char *path, const char *mode)
769 {
770 	FILE *fp;
771 	char *buf;
772 
773 	/*
774 	 * If the path variable doesn't end in a "/" a "/"
775 	 * will be added, so the buffer needs to be long enough
776 	 * for the path + / + name + \0
777 	 */
778 	size_t buf_sz = strlen(path) + strlen(name) + sizeof(DIRSEP) + 1;
779 	buf = malloc(buf_sz);
780 
781 	if (path[strlen(path)-1] != DIRSEP) {
782 		snprintf(buf, buf_sz, "%s%c%s", path, DIRSEP, name);
783 	} else {
784 		snprintf(buf, buf_sz, "%s%s", path, name);
785 	}
786 
787 	fp = fopen(buf, mode);
788 	free(buf);
789 	return fp;
790 
791 } /* pathopen */
792 
793 
794 /*
795  * getconfig
796  *
797  * Parse a <variable> <whitespace> <value> config file.
798  * The til-end-of-line comment character is the COMMENT define.  I use '#'
799  * here.  This code originally appeared in my q2-wrapper program.  Find it
800  * at metalab.cs.unc.edu or assorted Quake2 websites.
801  *
802  * This section must be modified whenever new options are added to
803  * the config file.  Ordinarily I would use yacc and lex, but the grammar
804  * is too simple to involve those resources, and I can't really expect all
805  * compile targets to have those two tools installed.
806  *
807  */
getconfig(char * configfile)808 static int getconfig(char *configfile)
809 {
810 	FILE	*fp;
811 
812 	size_t	num, num2;
813 
814 	char	varname[LINELEN + 1];
815 	char	value[LINELEN + 1];
816 
817 	/*
818 	 * We shouldn't care if the config file is unreadable or not
819 	 * present.  Just use the defaults.
820 	 *
821 	 */
822 
823 	if ((fp = fopen(configfile, "r")) == NULL)
824 		return FALSE;
825 
826 	while (fgets(varname, LINELEN, fp) != NULL) {
827 
828 		/* If we don't get a whole line, dump the rest of the line */
829 		if (varname[strlen(varname)-1] != '\n')
830 			while (fgetc(fp) != '\n')
831 			;
832 
833 		/* Remove trailing whitespace and newline */
834 		for (num = strlen(varname) - 1; (ssize_t) num >= 0 && isspace((int) varname[num]); num--)
835 		;
836 		varname[num+1] = 0;
837 
838 		/* Drop everything past the comment character */
839 		for (num = 0; num <= strlen(varname)+1; num++) {
840 			if (varname[num] == COMMENT)
841 				varname[num] = 0;
842 		}
843 
844 		/* Find end of variable name */
845 		for (num = 0; varname[num] != 0 && !isspace((int) varname[num]) && num < LINELEN; num++);
846 
847 		for (num2 = num; isspace((int) varname[num2]) && num2 < LINELEN; num2++);
848 
849 		/* Find the beginning of the value */
850 		strncpy(value, &varname[num2], LINELEN);
851 		varname[num] = 0; /* chop off value from the var name */
852 
853 		/* varname now contains the variable name */
854 
855 
856 		/* First, boolean config stuff */
857 		if (strcmp(varname, "attrib_set") == 0) {
858 			f_setup.attribute_assignment = getbool(value);
859 		}
860 		else if (strcmp(varname, "attrib_test") == 0) {
861 			f_setup.attribute_testing = getbool(value);
862 		}
863 
864 		else if (strcmp(varname, "ignore_fatal") == 0) {
865 			f_setup.ignore_errors = getbool(value);
866 		}
867 
868 		else if (strcmp(varname, "color") == 0) {
869 			u_setup.disable_color = !getbool(value);
870 		}
871 		else if (strcmp(varname, "colour") == 0) {
872 			u_setup.disable_color = !getbool(value);
873 		}
874 		else if (strcmp(varname, "force_color") == 0) {
875 			u_setup.force_color = getbool(value);
876 		}
877 		else if (strcmp(varname, "obj_move") == 0) {
878 			f_setup.object_movement = getbool(value);
879 		}
880 		else if (strcmp(varname, "obj_loc") == 0) {
881 			f_setup.object_locating = getbool(value);
882 		}
883 		else if (strcmp(varname, "piracy") == 0) {
884 			f_setup.piracy = getbool(value);
885 		}
886 		else if (strcmp(varname, "ascii") == 0) {
887 			u_setup.plain_ascii = getbool(value);
888 		}
889 		else if (strcmp(varname, "sound") == 0) {
890 			f_setup.sound = getbool(value);
891 		}
892 		else if (strcmp(varname, "tandy") == 0) {
893 			u_setup.tandy_bit = getbool(value);
894 		}
895 		else if (strcmp(varname, "expand_abb") == 0) {
896 			f_setup.expand_abbreviations = getbool(value);
897 		}
898 
899 		/* now for stringtype yet still numeric variables */
900 		else if (strcmp(varname, "background") == 0) {
901 			u_setup.background_color = getcolor(value);
902 		}
903 		else if (strcmp(varname, "foreground") == 0) {
904 			u_setup.foreground_color = getcolor(value);
905 		}
906 		else if (strcmp(varname, "context_lines") == 0) {
907 			f_setup.context_lines = atoi(value);
908 		}
909 		else if (strcmp(varname, "screen_height") == 0) {
910 			u_setup.screen_height = atoi(value);
911 		}
912 		else if (strcmp(varname, "left_margin") == 0) {
913 			f_setup.left_margin = atoi(value);
914 		}
915 		else if (strcmp(varname, "right_margin") == 0) {
916 			f_setup.right_margin = atoi(value);
917 		}
918 		else if (strcmp(varname, "randseed") == 0) {
919 			u_setup.random_seed = atoi(value);
920 		}
921 		else if (strcmp(varname, "script_width") == 0) {
922 			f_setup.script_cols = atoi(value);
923 		}
924 		else if (strcmp(varname, "undo_slots") == 0) {
925 			f_setup.undo_slots = atoi(value);
926 		}
927 		else if (strcmp(varname, "screen_width") == 0) {
928 			u_setup.screen_width = atoi(value);
929 		}
930 		/* default is set in main() by call to init_err() */
931 		else if (strcmp(varname, "errormode") == 0) {
932 			f_setup.err_report_mode = geterrmode(value);
933 		}
934 
935 		/* now for really stringtype variable */
936 
937 		else if (strcmp(varname, "zcode_path") == 0) {
938 			size_t sz = strlen(value) * sizeof(char) + 1;
939 			f_setup.zcode_path = malloc(sz);
940 			memcpy(f_setup.zcode_path, value, sz);
941 		} /* The big nasty if-else thingy is finished */
942 	} /* while */
943 	return TRUE;
944 } /* getconfig */
945 
946 
947 /*
948  * getbool
949  *
950  * Check a string for something that means "yes" and return TRUE.
951  * Otherwise return FALSE.
952  *
953  */
getbool(char * value)954 static int getbool(char *value)
955 {
956 	int num;
957 
958 	/* Be case-insensitive */
959 	for (num = 0; value[num] !=0; num++)
960 		value[num] = tolower((int) value[num]);
961 
962 	if (strncmp(value, "y", 1) == 0)
963 		return TRUE;
964 	if (strcmp(value, "true") == 0)
965 		return TRUE;
966 	if (strcmp(value, "on") == 0)
967 		return TRUE;
968 	if (strcmp(value, "1") == 0)
969 		return TRUE;
970 
971 	return FALSE;
972 } /* getbool */
973 
974 
975 /*
976  * getcolor
977  *
978  * Figure out what color this string might indicate and returns an integer
979  * corresponding to the color macros defined in frotz.h.
980  *
981  */
getcolor(char * value)982 static int getcolor(char *value)
983 {
984 	int num;
985 
986 	/* Be case-insensitive */
987 	for (num = 0; value[num] !=0; num++)
988 		value[num] = tolower((int) value[num]);
989 
990 	if (strcmp(value, "black") == 0)
991 		return BLACK_COLOUR;
992 	if (strcmp(value, "red") == 0)
993 		return RED_COLOUR;
994 	if (strcmp(value, "green") == 0)
995 		return GREEN_COLOUR;
996 	if (strcmp(value, "blue") == 0)
997 		return BLUE_COLOUR;
998 	if (strcmp(value, "magenta") == 0)
999 		return MAGENTA_COLOUR;
1000 	if (strcmp(value, "cyan") == 0)
1001 		return CYAN_COLOUR;
1002 	if (strcmp(value, "white") == 0)
1003 		return WHITE_COLOUR;
1004 	if (strcmp(value, "yellow") == 0)
1005 		return YELLOW_COLOUR;
1006 
1007 	if (strcmp(value, "purple") == 0)
1008 		return MAGENTA_COLOUR;
1009 	if (strcmp(value, "violet") == 0)
1010 		return MAGENTA_COLOUR;
1011 	if (strcmp(value, "aqua") == 0)
1012 		return CYAN_COLOUR;
1013 
1014 	/* If we can't tell what that string means,
1015 	 * we tell caller to use the default.
1016 	 */
1017 
1018 	return -1;
1019 
1020 } /* getcolor */
1021 
1022 
1023 /*
1024  * geterrmode
1025  *
1026  * Parse for "never", "once", "always", or "fatal" and return a macro
1027  * defined in ux_frotz.h related to the error reporting mode.
1028  *
1029  */
geterrmode(char * value)1030 static int geterrmode(char *value)
1031 {
1032 	int num;
1033 
1034         /* Be case-insensitive */
1035 	for (num = 0; value[num] !=0; num++)
1036 		value[num] = tolower((int) value[num]);
1037 
1038 	if (strcmp(value, "never") == 0)
1039 		return ERR_REPORT_NEVER;
1040 	if (strcmp(value, "once") == 0)
1041 		return ERR_REPORT_ONCE;
1042 	if (strcmp(value, "always") == 0)
1043 		return ERR_REPORT_ALWAYS;
1044 	if (strcmp(value, "fatal") == 0)
1045 		return ERR_REPORT_FATAL;
1046 
1047 	return ERR_DEFAULT_REPORT_MODE;
1048 } /* geterrmode() */
1049 
1050 
1051 /*
1052  * sigwinch_handler
1053  *
1054  * Called whenever Frotz receives a SIGWINCH signal to make curses
1055  * cleanly resize the window.  To be safe, just set a flag here.
1056  * It is checked and cleared in unix_read_char.
1057  *
1058  */
sigwinch_handler(int UNUSED (sig))1059 static void sigwinch_handler(int UNUSED(sig))
1060 {
1061 	terminal_resized = 1;
1062 	signal(SIGWINCH, sigwinch_handler);
1063 }
1064 
1065 
1066 /*
1067  * sigint_handler
1068  * Sometimes the screen will be left in a weird state if the following
1069  * is not done.
1070  *
1071  */
sigint_handler(int UNUSED (dummy))1072 static void sigint_handler(int UNUSED(dummy))
1073 {
1074 	signal(SIGINT, sigint_handler);
1075 	os_quit(EXIT_FAILURE);
1076 } /* sigint_handler */
1077 
1078 
os_init_setup(void)1079 void os_init_setup(void)
1080 {
1081 	f_setup.interpreter_number = INTERP_DEC_20;
1082 
1083 	u_setup.disable_color = 0;
1084 	u_setup.force_color = 0;
1085 	u_setup.foreground_color = -1;
1086 	u_setup.background_color = -1;
1087 	u_setup.screen_width = -1;
1088 	u_setup.screen_height = -1;
1089 	u_setup.random_seed = -1;
1090 	u_setup.random_seed = -1;
1091 	u_setup.tandy_bit = 0;
1092 	u_setup.current_text_style = 0;
1093 			/* Since I can't use attr_get, which
1094 			would make things easier, I need
1095 			to use the same hack the MS-DOS port
1096 			does...keep the current style in a
1097 			global variable. */
1098 	u_setup.plain_ascii = 0; /* true if user wants to disable Latin-1 */
1099 	u_setup.curses_active = 0;      /* true if os_init_screen has run */
1100 	/* u_setup.interpreter = INTERP_DEFAULT; */
1101 	u_setup.current_color = 0;
1102 	u_setup.color_enabled = FALSE;
1103 } /* os_init_setup */
1104 
1105 
1106 #ifdef NO_STRRCHR
1107 /*
1108  * This is for operating systems that lack strrchr(3).
1109  *
1110  */
my_strrchr(const char * s,int c)1111 char *my_strrchr(const char *s, int c)
1112 {
1113 	const char *save;
1114 
1115 	if (c == 0) return (char *)s + strlen(s);
1116 		save = 0;
1117 	while (*s) {
1118 		if (*s == c)
1119 			save = s;
1120 		s++;
1121 	}
1122 	return (char *)save;
1123 } /* my_strrchr */
1124 #endif	/* NO_STRRCHR */
1125 
1126 
print_version(void)1127 static void print_version(void)
1128 {
1129 	printf("FROTZ V%s\tCurses interface.  ", VERSION);
1130 #ifndef NO_SOUND
1131 	printf("Audio output enabled.");
1132 #else
1133 	printf("Audio output disabled.");
1134 #endif
1135 	printf("\nCommit date:\t%s\n", GIT_DATE);
1136 	printf("Git commit:\t%s\n", GIT_HASH);
1137 	printf("  Frotz was originally written by Stefan Jokisch.\n");
1138 	printf("  It complies with standard 1.0 of Graham Nelson's specification.\n");
1139 	printf("  It was ported to Unix by Galen Hazelwood.\n");
1140 	printf("  The core and Unix port are maintained by David Griffith.\n");
1141 	printf("  Frotz's homepage is https://661.org/proj/if/frotz/\n\n");
1142 	return;
1143 } /* print_version */
1144