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