1 /*
2  * bcinit.c - DOS interface, initialization
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 #include <conio.h>
23 #include <dos.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include "frotz.h"
28 #include "bcfrotz.h"
29 
30 #ifndef NO_BLORB
31 #include "bcblorb.h"
32 #endif
33 
34 #include "bchash.h"
35 
36 extern f_setup_t f_setup;
37 extern z_header_t z_header;
38 
39 static char information[] =
40     "An interpreter for all Infocom and other Z-Machine games.\n"
41     "Complies with standard 1.0 of Graham Nelson's specification.\n"
42     "\n"
43     "Syntax: frotz [options] story-file\n"
44     "  -a   watch attribute setting    \t -O   watch object locating\n"
45     "  -A   watch attribute testing    \t -p   alter piracy opcode\n"
46     "  -b # background colour          \t -r # right margin\n"
47     "  -B # reverse background colour  \t -R <path> restricted read/write\n"
48     "  -c # context lines              \t -s # random number seed value\n"
49     "  -d # display mode (see below)   \t -S # transcript width\n"
50     "  -e # emphasis colour [mode 1]   \t -t   set Tandy bit\n"
51     "  -f # foreground colour          \t -T   bold typing [modes 2+4+5]\n"
52     "  -F # reverse foreground colour  \t -u # slots for multiple undo\n"
53     "  -g # font [mode 5] (see below)  \t -v   show version information\n"
54     "  -h # screen height              \t -w # screen width\n"
55     "  -i   ignore runtime errors      \t -x   expand abbreviations g/x/z\n"
56     "  -l # left margin                \t -Z # error checking (see below)\n"
57     "  -o   watch object movement\n"
58     "\n"
59     "Fonts: 0 fixed, 1 sans serif, 2 comic, 3 times, 4 serif.\n"
60     "Display modes:  0 mono, 1 text, 2 CGA, 3 MCGA, 4 EGA, 5 Amiga.\n"
61     "Error reporting: 0 none, 1 first only (default), 2 all, 3 exit after any error.";
62 
63 /* in bcinit.c only.  What is its significance? */
64 extern unsigned cdecl _heaplen = 0x800 + 4 * BUFSIZ;
65 extern unsigned cdecl _stklen = 0x800;
66 
67 extern int zoptind;
68 
69 int cdecl zgetopt(int, char *[], const char *);
70 
71 static const char *progname = NULL;
72 
73 extern char script_name[];
74 extern char command_name[];
75 extern char save_name[];
76 extern char auxilary_name[];
77 
78 int display = -1;
79 
80 int user_background = -1;
81 int user_foreground = -1;
82 int user_emphasis = -1;
83 int user_bold_typing = -1;
84 int user_reverse_bg = -1;
85 int user_reverse_fg = -1;
86 int user_screen_height = -1;
87 int user_screen_width = -1;
88 int user_tandy_bit = -1;
89 int user_random_seed = -1;
90 int user_font = 1;
91 
92 /* Blorb-related things */
93 #ifndef NO_BLORB
94 char *blorb_name;
95 char *blorb_file;
96 bool use_blorb;
97 bool exec_in_blorb;
98 #endif
99 
100 static byte old_video_mode = 0;
101 
102 static void interrupt(*oldvect) () = NULL;
103 
104 bool at_keybrd;
105 
106 /* Test for the existance of Enhanced AT keyboard support in BIOS */
test_enhanced_keyboard(unsigned char b)107 static bool test_enhanced_keyboard(unsigned char b)
108 {
109 	union REGS regs;
110 	unsigned char far *shift_status;
111 	shift_status = MK_FP(0, 0x417);
112 	shift_status[0] = b;
113 	regs.h.ah = 0x12;
114 	int86(0x16, &regs, &regs);
115 	return b == regs.h.al;
116 }
117 
118 /*
119  * os_init_setup
120  *
121  * Set or reset various configuration variables.
122  *
123  */
os_init_setup(void)124 void os_init_setup(void)
125 {
126 	f_setup.attribute_assignment = 0;
127 	f_setup.attribute_testing = 0;
128 	f_setup.context_lines = 0;
129 	f_setup.object_locating = 0;
130 	f_setup.object_movement = 0;
131 	f_setup.left_margin = 0;
132 	f_setup.right_margin = 0;
133 	f_setup.ignore_errors = 0;
134 	f_setup.piracy = 0;
135 	f_setup.undo_slots = MAX_UNDO_SLOTS;
136 	f_setup.expand_abbreviations = 0;
137 	f_setup.script_cols = 80;
138 	f_setup.sound = 1;
139 	f_setup.err_report_mode = ERR_DEFAULT_REPORT_MODE;
140 	f_setup.restore_mode = 0;
141 
142 	at_keybrd = (test_enhanced_keyboard(0x40) && test_enhanced_keyboard(0x80));
143 	test_enhanced_keyboard(0x00);
144 } /* os_init_setup */
145 
146 
147 /*
148  * dectoi
149  *
150  * Convert a string containing a decimal number to integer. The string may
151  * be NULL, but it must not be empty.
152  *
153  */
dectoi(const char * s)154 int dectoi(const char *s)
155 {
156 	int n = 0;
157 
158 	if (s != NULL) {
159 		do {
160 			n = 10 * n + (*s & 15);
161 		} while (*++s > ' ');
162 	}
163 	return n;
164 } /* dectoi */
165 
166 
167 /*
168  * hextoi
169  *
170  * Convert a string containing a hex number to integer. The string may be
171  * NULL, but it must not be empty.
172  *
173  */
hextoi(const char * s)174 int hextoi(const char *s)
175 {
176 	int n = 0;
177 
178 	if (s != NULL) {
179 		do {
180 
181 			n = 16 * n + (*s & 15);
182 
183 			if (*s > '9')
184 				n += 9;
185 
186 		} while (*++s > ' ');
187 	}
188 	return n;
189 } /* hextoi */
190 
191 
192 /*
193  * basename
194  *
195  * A generic and spartan bit of code to extract the filename from a path.
196  * This one is so trivial that it's okay to steal.
197  */
basename(const char * path)198 char *basename(const char *path)
199 {
200 	const char *s;
201 	const char *p;
202 	p = s = path;
203 
204 	while (*s) {
205 		if (*s++ == '\\') {
206 			p = s;
207 		}
208 	}
209 	return (char *)p;
210 } /* basename */
211 
212 
213 /*
214  * cleanup
215  *
216  * Shut down the IO interface: free memory, close files, restore
217  * interrupt pointers and return to the previous video mode.
218  *
219  */
cleanup(void)220 static void cleanup(void)
221 {
222 #ifdef SOUND_SUPPORT
223 	dos_reset_sound();
224 #endif
225 	reset_pictures();
226 
227 	asm mov ah, 0
228 	asm mov al, old_video_mode
229 	asm int 0x10
230 
231 	setvect(0x1b, oldvect);
232 } /* cleanup */
233 
234 
235 /*
236  * fast_exit
237  *
238  * Handler routine to be called when the crtl-break key is pressed.
239  *
240  */
fast_exit()241 static void interrupt fast_exit()
242 {
243 	cleanup();
244 	exit(EXIT_FAILURE);
245 } /* fast_exit */
246 
247 
248 /*
249  * os_quit
250  *
251  * Immediately and cleanly exit, passing along exit status.
252  *
253  */
254 
os_quit(int status)255 void os_quit(int status)
256 {
257     cleanup();
258     exit(status);
259 }
260 
261 /*
262  * os_fatal
263  *
264  * Display error message and exit program.
265  *
266  */
os_fatal(const char * s,...)267 void os_fatal(const char *s, ...)
268 {
269 	if (z_header.interpreter_number)
270 		os_reset_screen();
271 
272 	/* Display error message */
273 	fputs("\nFatal error: ", stderr);
274 	fputs(s, stderr);
275 	fputs("\n", stderr);
276 
277 	/* Abort program */
278 	exit(EXIT_FAILURE);
279 } /* os_fatal */
280 
281 
282 /*
283  * parse_options
284  *
285  * Parse program options and set global flags accordingly.
286  *
287  */
parse_options(int argc,char ** argv)288 static void parse_options(int argc, char **argv)
289 {
290 	int c;
291 
292 	do {
293 		int num = 0;
294 
295 		c = zgetopt(argc, argv,
296 			   "aAb:B:c:d:e:f:F:g:h:il:oOpr:R:s:S:tTu:vw:xZ:");
297 
298 		if (zoptarg != NULL)
299 			num = dectoi(zoptarg);
300 
301 		if (c == 'a')
302 			f_setup.attribute_assignment = 1;
303 		if (c == 'A')
304 			f_setup.attribute_testing = 1;
305 		if (c == 'b')
306 			user_background = num;
307 		if (c == 'B')
308 			user_reverse_bg = num;
309 		if (c == 'c')
310 			f_setup.context_lines = num;
311 		if (c == 'd') {
312 			display = zoptarg[0] | 32;
313 			if ((display < '0' || display > '5')
314 			    && (display < 'a' || display > 'e')) {
315 				display = -1;
316 			}
317 		}
318 		if (c == 'e')
319 			user_emphasis = num;
320 		if (c == 'T')
321 			user_bold_typing = 1;
322 		if (c == 'f')
323 			user_foreground = num;
324 		if (c == 'F')
325 			user_reverse_fg = num;
326 		if (c == 'g') {
327 			if (num >= 0 && num <= 4)
328 				user_font = num;
329 		}
330 		if (c == 'h')
331 			user_screen_height = num;
332 		if (c == 'i')
333 			f_setup.ignore_errors = 1;
334 		if (c == 'l')
335 			f_setup.left_margin = num;
336 		if (c == 'o')
337 			f_setup.object_movement = 1;
338 		if (c == 'O')
339 			f_setup.object_locating = 1;
340 		if (c == 'p')
341 			f_setup.piracy = 1;
342 		if (c == 'r')
343 			f_setup.right_margin = num;
344 		if (c == 'R')
345 			f_setup.restricted_path = strdup(zoptarg);
346 		if (c == 's')
347 			user_random_seed = num;
348 		if (c == 'S')
349 			f_setup.script_cols = num;
350 		if (c == 't')
351 			user_tandy_bit = 1;
352 		if (c == 'u')
353 			f_setup.undo_slots = num;
354 		if (c == 'v') {
355 			printf("FROTZ V%s - MSDOS / PCDOS Edition\n", VERSION);
356 			printf("Commit date:\t%s\n", GIT_DATE);
357 			printf("Git commit:\t%s\n", GIT_HASH);
358 			printf("  Frotz was originally written by Stefan Jokisch.\n");
359 			printf("  It complies with standard 1.0 of Graham Nelson's specification.\n");
360 			printf("  It was ported to Unix by Galen Hazelwood.\n");
361 			printf("  The core and DOS port are maintained by David Griffith.\n");
362 			printf("  Frotz's homepage is https://661.org/proj/if/frotz/\n");
363 			exit(0);
364 		}
365 		if (c == 'w')
366 			user_screen_width = num;
367 		if (c == 'x')
368 			f_setup.expand_abbreviations = 1;
369 		if (c == 'Z') {
370 			if (num >= ERR_REPORT_NEVER && num <= ERR_REPORT_FATAL)
371 				f_setup.err_report_mode = num;
372 		}
373 		if (c == '?')
374 			zoptind = argc;
375 	} while (c != EOF && c != '?');
376 
377 } /* parse_options */
378 
malloc_filename(char * story_name,char * extension)379 static char *malloc_filename(char *story_name, char *extension)
380 {
381 	int length = strlen(story_name) + strlen(extension) + 2;
382 	char *filename = malloc(length);
383 	if (filename) {
384 		strcpy(filename, story_name);
385 		strcat(filename, ".");
386 		strcat(filename, extension);
387 	}
388 	return filename;
389 }
390 
391 /*
392  * os_process_arguments
393  *
394  * Handle command line switches. Some variables may be set to activate
395  * special features of Frotz:
396  *
397  *     option_attribute_assignment
398  *     option_attribute_testing
399  *     option_context_lines
400  *     option_object_locating
401  *     option_object_movement
402  *     option_left_margin
403  *     option_right_margin
404  *     option_ignore_errors
405  *     option_piracy
406  *     option_undo_slots
407  *     option_expand_abbreviations
408  *     option_script_cols
409  *
410  * The global pointer "story_name" is set to the story file name.
411  *
412  */
os_process_arguments(int argc,char * argv[])413 void os_process_arguments(int argc, char *argv[])
414 {
415 	const char *p;
416 	int i;
417 	char stripped_story_name[10];
418 
419 	/* Parse command line options */
420 	parse_options(argc, argv);
421 
422 	if (zoptind != argc - 1) {
423 		printf("FROTZ V%s - MSDOS / PCDOS Edition.  ", VERSION);
424 #ifndef NO_SOUND
425 		printf("Audio output enabled.\n");
426 #else
427 		printf("Audio output disabled.\n");
428 #endif
429 		puts(information);
430 		exit(EXIT_SUCCESS);
431 	}
432 
433 	/* Set the story file name */
434 	f_setup.story_file = strdup(argv[zoptind]);
435 
436 	/* Strip path and extension off the story file name */
437 	p = strdup(f_setup.story_file);
438 
439 	for (i = 0; f_setup.story_file[i] != 0; i++)
440 		if (f_setup.story_file[i] == '\\'
441 		    || f_setup.story_file[i] == '/'
442 		    || f_setup.story_file[i] == ':')
443 			p = f_setup.story_file + i + 1;
444 
445 	for (i = 0; p[i] != 0 && p[i] != '.'; i++)
446 		stripped_story_name[i] = p[i];
447 	stripped_story_name[i] = 0;
448 	f_setup.story_name = strdup(stripped_story_name);
449 
450 	/* Create nice default file names */
451 	f_setup.script_name = malloc_filename(f_setup.story_name, "scr");
452 	f_setup.command_name = malloc_filename(f_setup.story_name, "rec");
453 	f_setup.save_name = malloc_filename(f_setup.story_name, "sav");
454 	f_setup.aux_name = malloc_filename(f_setup.story_name, "aux");
455 
456 	/* Save the executable file name */
457 	progname = argv[0];
458 
459 #ifndef NO_BLORB
460 	blorb_file = malloc_filename(f_setup.story_name, "blb");
461 
462 	switch (dos_init_blorb()) {
463 	case bb_err_Format:
464 		printf("Blorb file loaded, but unable to build map.\n\n");
465 		break;
466 	default:
467 		break;
468 /* No problem.  Don't say anything. */
469 /*	    printf("Blorb error code %i\n\n"); */
470 	}
471 #endif
472 } /* os_process_arguments */
473 
474 
475 /*
476  * standard_palette
477  *
478  * Set palette registers to EGA default values and call VGA BIOS to
479  * use DAC registers #0 to #63.
480  *
481  */
standard_palette(void)482 static void standard_palette(void)
483 {
484 
485 	static byte palette[] = {
486 		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07,
487 		0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
488 		0x00	/* last one is the overscan register */
489 	};
490 
491 	if (display == _AMIGA_) {
492 		asm mov ax, 0x1002
493 		asm lea dx, palette
494 		asm push ds
495 		asm pop es
496 		asm int 0x10
497 		asm mov ax, 0x1013
498 		asm mov bx, 0x0001
499 		asm int 0x10
500 	}
501 } /* standard_palette */
502 
503 
504 /*
505  * special_palette
506  *
507  * Set palette register #i to value i and call VGA BIOS to use DAC
508  * registers #64 to #127.
509  *
special_palette(void)510  */ static void special_palette(void)
511 {
512 
513 	static byte palette[] = {
514 		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
515 		0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
516 		0x00	/* last one is the overscan register */
517 	};
518 
519 	if (display == _AMIGA_) {
520 		asm mov ax, 0x1002
521 		asm mov dx, offset palette
522 		asm push ds
523 		asm pop es
524 		asm int 0x10
525 		asm mov ax, 0x1013
526 		asm mov bx, 0x0101
527 		asm int 0x10
528 	}
529 } /* special_palette */
530 
531 
532 /*
533  * os_init_screen
534  *
535  * Initialise the IO interface. Prepare the screen and other devices
536  * (mouse, sound board). Set various OS depending story file header
537  * entries:
538  *
539  *     z_header.config (aka flags 1)
540  *     z_header.flags (aka flags 2)
541  *     z_header.screen_cols (aka screen width in characters)
542  *     z_header.screen_rows (aka screen height in lines)
543  *     z_header.screen_width
544  *     z_header.screen_height
545  *     z_header.font_height (defaults to 1)
546  *     z_header.font_width (defaults to 1)
547  *     z_header.default_foreground
548  *     z_header.default_background
549  *     z_header.interpreter_number
550  *     z_header.interpreter_version
551  *     z_header.user_name (optional; not used by any game)
552  *
553  * Finally, set reserve_mem to the amount of memory (in bytes) that
554  * should not be used for multiple undo and reserved for later use.
555  *
os_init_screen(void)556  */ void os_init_screen(void)
557 {
558 	static byte zcolour[] = {
559 		BLACK_COLOUR,
560 		BLUE_COLOUR,
561 		GREEN_COLOUR,
562 		CYAN_COLOUR,
563 		RED_COLOUR,
564 		MAGENTA_COLOUR,
565 		BROWN + 16,
566 		LIGHTGRAY + 16,
567 		GREY_COLOUR,
568 		LIGHTBLUE + 16,
569 		LIGHTGREEN + 16,
570 		LIGHTCYAN + 16,
571 		LIGHTRED + 16,
572 		LIGHTMAGENTA + 16,
573 		YELLOW_COLOUR,
574 		WHITE_COLOUR
575 	};
576 
577 	static struct {	/* information on modes 0 to 5 */
578 		byte vmode;
579 		word width;
580 		word height;
581 		byte font_width;
582 		byte font_height;
583 		byte fg;
584 		byte bg;
585 	} info[] = {
586 		{0x07, 80, 25, 1, 1, LIGHTGRAY + 16, BLACK_COLOUR},	/* MONO  */
587 		{0x03, 80, 25, 1, 1, LIGHTGRAY + 16, BLUE_COLOUR},	/* TEXT  */
588 		{0x06, 640, 200, 8, 8, WHITE_COLOUR, BLACK_COLOUR},	/* CGA   */
589 		{0x13, 320, 200, 5, 8, WHITE_COLOUR, GREY_COLOUR},	/* MCGA  */
590 		{0x0e, 640, 200, 8, 8, WHITE_COLOUR, BLUE_COLOUR},	/* EGA   */
591 		{0x12, 640, 400, 8, 16, WHITE_COLOUR, BLACK_COLOUR}	/* AMIGA */
592 	};
593 
594 	static struct {	/* information on modes A to E */
595 		word vesamode;
596 		word width;
597 		word height;
598 	} subinfo[] = {
599 		{0x001, 40, 25},
600 		{0x109, 132, 25},
601 		{0x10b, 132, 50},
602 		{0x108, 80, 60},
603 		{0x10c, 132, 60}
604 	};
605 
606 	int subdisplay;
607 
608 	/* Get the current video mode. This video mode will be selected
609 	   when the program terminates. It's also useful to auto-detect
610 	   monochrome boards. */
611 	asm mov ah, 15
612 	asm int 0x10
613 	asm mov old_video_mode, al
614 
615 	/* If the display mode has not already been set by the user then see
616 	   if this is a monochrome board. If so, set the display mode to 0.
617 	   Otherwise check the graphics flag of the story. Select a graphic
618 	   mode if it is set or if this is a V6 game. Select text mode if it
619 	   is not. */
620 	 if (display == -1) {
621 		if (old_video_mode == 7)
622 			display = '0';
623 		else if (z_header.version == V6 || (z_header.flags & GRAPHICS_FLAG))
624 			display = '5';
625 		else
626 			display = '1';
627 	}
628 
629 	/* Activate the desired display mode. All VESA text modes are very
630 	   similar to the standard text mode; in fact, only here we need to
631 	   know which VESA mode is used. */
632 	if (display >= '0' && display <= '5') {
633 		subdisplay = -1;
634 		display -= '0';
635 		_AL = info[display].vmode;
636 		_AH = 0;
637 	} else if (display == 'a') {
638 		subdisplay = 0;
639 		display = 1;
640 		_AL = 0x01;
641 		_AH = 0;
642 	} else if (display >= 'b' && display <= 'e') {
643 		subdisplay = display - 'a';
644 		display = 1;
645 		_BX = subinfo[subdisplay].vesamode;
646 		_AX = 0x4f02;
647 	}
648 
649 	geninterrupt(0x10);
650 
651 	/* Make various preparations */
652 	if (display <= _TEXT_) {
653 
654 		/* Enable bright background colours */
655 		asm mov ax, 0x1003
656 		asm mov bl, 0
657 		asm int 0x10
658 		/* Turn off hardware cursor */
659 		asm mov ah, 1
660 		asm mov cx, 0xffff
661 		asm int 0x10
662 	} else {
663 		load_fonts();
664 
665 		if (display == _AMIGA_) {
666 			scaler = 2;
667 
668 			/* Use resolution 640 x 400 instead of 640 x 480. BIOS doesn't
669 			   help us here since this is not a standard resolution. */
670 			outportb(0x03c2, 0x63);
671 
672 			outport(0x03d4, 0x0e11);
673 			outport(0x03d4, 0xbf06);
674 			outport(0x03d4, 0x1f07);
675 			outport(0x03d4, 0x9c10);
676 			outport(0x03d4, 0x8f12);
677 			outport(0x03d4, 0x9615);
678 			outport(0x03d4, 0xb916);
679 
680 		}
681 
682 	}
683 #if !defined(__SMALL__) && !defined (__TINY__) && !defined (__MEDIUM__)
684 	/* Set the amount of memory to reserve for later use. It takes
685 	   some memory to open command, script and game files. If Frotz
686 	   is compiled in a small memory model then memory for opening
687 	   files is allocated on the "near heap" while other allocations
688 	   are made on the "far heap", i.e. we need not reserve memory
689 	   in this case. */ reserve_mem = 4 * BUFSIZ;
690 
691 #endif
692 
693 	/* Amiga emulation under V6 needs special preparation. */
694 	if (display == _AMIGA_ && z_header.version == V6) {
695 		user_reverse_fg = -1;
696 		user_reverse_bg = -1;
697 		zcolour[LIGHTGRAY] = LIGHTGREY_COLOUR;
698 		zcolour[DARKGRAY] = DARKGREY_COLOUR;
699 
700 		special_palette();
701 	}
702 
703 	/* Set various bits in the configuration byte. These bits tell
704 	   the game which features are supported by the interpreter. */
705 	if (z_header.version == V3 && user_tandy_bit != -1)
706 		z_header.config |= CONFIG_TANDY;
707 	if (z_header.version == V3)
708 		z_header.config |= CONFIG_SPLITSCREEN;
709 	if (z_header.version == V3
710 	    && (display == _MCGA_ || (display == _AMIGA_ && user_font != 0)))
711 		z_header.config |= CONFIG_PROPORTIONAL;
712 	if (z_header.version >= V4 && display != _MCGA_
713 	    && (user_bold_typing != -1 || display <= _TEXT_))
714 		z_header.config |= CONFIG_BOLDFACE;
715 	if (z_header.version >= V4)
716 		z_header.config |= CONFIG_EMPHASIS | CONFIG_FIXED | CONFIG_TIMEDINPUT;
717 	if (z_header.version >= V5 && display != _MONO_ && display != _CGA_)
718 		z_header.config |= CONFIG_COLOUR;
719 	if (z_header.version >= V5 && display >= _CGA_ && init_pictures())
720 		z_header.config |= CONFIG_PICTURES;
721 
722 	/* Handle various game flags. These flags are set if the game wants
723 	   to use certain features. The flags must be cleared if the feature
724 	   is not available. */
725 	if (z_header.flags & GRAPHICS_FLAG)
726 		if (display <= _TEXT_)
727 			z_header.flags &= ~GRAPHICS_FLAG;
728 	if (z_header.version == V3 && (z_header.flags & OLD_SOUND_FLAG))
729 #ifdef SOUND_SUPPORT
730 		if (!dos_init_sound())
731 #endif
732 			z_header.flags &= ~OLD_SOUND_FLAG;
733 	if (z_header.flags & SOUND_FLAG)
734 #ifdef SOUND_SUPPORT
735 		if (!dos_init_sound())
736 #endif
737 			z_header.flags &= ~SOUND_FLAG;
738 	if (z_header.version >= V5 && (z_header.flags & UNDO_FLAG))
739 		if (!f_setup.undo_slots)
740 			z_header.flags &= ~UNDO_FLAG;
741 	if (z_header.flags & MOUSE_FLAG)
742 		if (subdisplay != -1 || !detect_mouse())
743 			z_header.flags &= ~MOUSE_FLAG;
744 	if (z_header.flags & COLOUR_FLAG)
745 		if (display == _MONO_ || display == _CGA_)
746 			z_header.flags &= ~COLOUR_FLAG;
747 	z_header.flags &= ~MENU_FLAG;
748 
749 	/* Set the screen dimensions, font size and default colour */
750 	z_header.screen_width = info[display].width;
751 	z_header.screen_height = info[display].height;
752 	z_header.font_height = info[display].font_height;
753 	z_header.font_width = info[display].font_width;
754 	z_header.default_foreground = info[display].fg;
755 	z_header.default_background = info[display].bg;
756 
757 	if (subdisplay != -1) {
758 		z_header.screen_width = subinfo[subdisplay].width;
759 		z_header.screen_height = subinfo[subdisplay].height;
760 	}
761 
762 	if (user_screen_width != -1)
763 		z_header.screen_width = user_screen_width;
764 	if (user_screen_height != -1)
765 		z_header.screen_height = user_screen_height;
766 
767 	z_header.screen_cols = z_header.screen_width / z_header.font_width;
768 	z_header.screen_rows = z_header.screen_height / z_header.font_height;
769 
770 	if (user_foreground != -1)
771 		z_header.default_foreground = zcolour[user_foreground];
772 	if (user_background != -1)
773 		z_header.default_background = zcolour[user_background];
774 
775 	/* Set the interpreter number (a constant telling the game which
776 	   operating system it runs on) and the interpreter version. The
777 	   interpreter number has effect on V6 games and "Beyond Zork". */
778 	z_header.interpreter_number = INTERP_MSDOS;
779 	z_header.interpreter_version = 'F';
780 
781 	if (display == _AMIGA_)
782 		z_header.interpreter_number = INTERP_AMIGA;
783 
784 	/* Install the fast_exit routine to handle the ctrl-break key */
785 	oldvect = getvect(0x1b);
786 	setvect(0x1b, fast_exit);
787 } /* os_init_screen */
788 
789 
790 /*
791  * os_reset_screen
792  *
793  * Reset the screen before the program stops.
794  *
795  */
os_reset_screen(void)796 void os_reset_screen(void)
797 {
798 	os_set_font(TEXT_FONT);
799 	os_set_text_style(0);
800 	os_display_string((zchar *) "[Hit any key to exit.]");
801 	os_read_key(0, TRUE);
802 
803 	cleanup();
804 } /* os_reset_screen */
805 
806 
807 /*
808  * os_restart_game
809  *
810  * This routine allows the interface to interfere with the process of
811  * restarting a game at various stages:
812  *
813  *     RESTART_BEGIN - restart has just begun
814  *     RESTART_WPROP_SET - window properties have been initialised
815  *     RESTART_END - restart is complete
816  *
817  */
os_restart_game(int stage)818 void os_restart_game(int stage)
819 {
820 	int x, y;
821 
822 	if (story_id == BEYOND_ZORK) {
823 		if (stage == RESTART_BEGIN) {
824 			if ((display == _MCGA_ || display == _AMIGA_)
825 			    && os_picture_data(1, &x, &y)) {
826 
827 				special_palette();
828 
829 				asm mov ax, 0x1010
830 				asm mov bx, 64
831 				asm mov dh, 0
832 				asm mov ch, 0
833 				asm mov cl, 0
834 				asm int 0x10
835 				asm mov ax, 0x1010
836 				asm mov bx, 79
837 				asm mov dh, 0xff
838 				asm mov ch, 0xff
839 				asm mov cl, 0xff
840 				asm int 0x10
841 				os_draw_picture(1, 1, 1);
842 				os_read_key(0, FALSE);
843 
844 				standard_palette();
845 			}
846 		}
847 	}
848 } /* os_restart_game */
849 
850 
851 /*
852  * os_random_seed
853  *
854  * Return an appropriate random seed value in the range from 0 to
855  * 32767, possibly by using the current system time.
856  *
857  */
os_random_seed(void)858 int os_random_seed(void)
859 {
860 	if (user_random_seed == -1) {
861 		/* Use the time of day as seed value */
862 		asm mov ah, 0
863 		asm int 0x1a
864 
865 		return _DX & 0x7fff;
866 	} else
867 		return user_random_seed;
868 } /* os_random_seed */
869 
870 
871 /*
872  * os_path_open
873  *
874  * Open a file in the current directory.  If this fails then
875  * search the directories in the ZCODE_PATH environment variable,
876  * if it is defined, otherwise search INFOCOM_PATH.
877  *
878  */
os_path_open(const char * name,const char * mode)879 FILE *os_path_open(const char *name, const char *mode)
880 {
881 	FILE *fp;
882 	char buf[MAX_FILE_NAME + 1];
883 	char *p, *bp, lastch;
884 
885 	if ((fp = fopen(name, mode)) != NULL)
886 		return fp;
887 	if ((p = getenv("ZCODE_PATH")) == NULL)
888 		p = getenv("INFOCOM_PATH");
889 	if (p != NULL) {
890 		while (*p) {
891 			bp = buf;
892 			while (*p && *p != OS_PATHSEP)
893 				lastch = *bp++ = *p++;
894 			if (lastch != '\\' && lastch != '/')
895 				*bp++ = '\\';
896 			strcpy(bp, name);
897 			if ((fp = fopen(buf, mode)) != NULL)
898 				return fp;
899 			if (*p)
900 				p++;
901 		}
902 	}
903 	return NULL;
904 } /* os_path_open */
905 
906 
907 /*
908  * os_load_story
909  *
910  * This is different from os_path_open() because we need to see if the
911  * story file is actually a chunk inside a blorb file.  Right now we're
912  * looking only at the exact path we're given on the command line.
913  *
914  * Open a file in the current directory.  If this fails, then search the
915  * directories in the ZCODE_PATH environmental variable.  If that's not
916  * defined, search INFOCOM_PATH.
917  *
918  */
os_load_story(void)919 FILE *os_load_story(void)
920 {
921 #ifndef NO_BLORB
922 	FILE *fp;
923 
924 	/* Did we build a valid blorb map? */
925 	if (exec_in_blorb) {
926 		fp = fopen(blorb_file, "rb");
927 		fseek(fp, blorb_res.data.startpos, SEEK_SET);
928 	} else {
929 		fp = fopen(f_setup.story_file, "rb");
930 	}
931 	return fp;
932 #else
933 	return fopen(f_setup.story_file, "rb");
934 #endif
935 }
936 
937 
938 #ifndef NO_BLORB
dos_init_blorb(void)939 int dos_init_blorb(void)
940 {
941 	FILE *blorbfile;
942 
943 	/* If the filename given on the command line is the same as our
944 	 * computed blorb filename, then we will assume the executable
945 	 * is contained in the blorb file.
946 	 */
947 	if (strncmp((char *)basename(f_setup.story_file),
948 		    (char *)basename(blorb_file), 55) == 0) {
949 		if ((blorbfile = fopen(blorb_file, "rb")) == NULL)
950 			return bb_err_Read;
951 		/* Under DOS, bb_create_map() returns bb_err_Format */
952 		blorb_err = bb_create_map(blorbfile, &blorb_map);
953 
954 		if (blorb_err != bb_err_None) {
955 			return blorb_err;
956 		}
957 
958 		/* Now we need to locate the EXEC chunk within the blorb file
959 		 * and present it to the rest of the program as a file stream.
960 		 */
961 		blorb_err = bb_load_chunk_by_type(blorb_map, bb_method_FilePos,
962 						  &blorb_res, bb_ID_ZCOD, 0);
963 
964 		if (blorb_err == bb_err_None) {
965 			exec_in_blorb = 1;
966 			use_blorb = 1;
967 		}
968 	}
969 	return 0;
970 }
971 #endif /* NO_BLORB */
972 
973 
974 /*
975  * Seek into a storyfile, either a standalone file or the
976  * ZCODE chunk of a blorb file
977  */
os_storyfile_seek(FILE * fp,long offset,int whence)978 int os_storyfile_seek(FILE * fp, long offset, int whence)
979 {
980 #ifndef NO_BLORB
981 	int retval;
982 	/* Is this a Blorb file containing Zcode? */
983 	if (exec_in_blorb) {
984 		switch (whence) {
985 		case SEEK_END:
986 			retval =
987 			    fseek(fp,
988 				  blorb_res.data.startpos + blorb_res.length +
989 				  offset, SEEK_SET);
990 			break;
991 		case SEEK_CUR:
992 			retval = fseek(fp, offset, SEEK_CUR);
993 			break;
994 		case SEEK_SET:
995 		default:
996 			retval =
997 			    fseek(fp, blorb_res.data.startpos + offset,
998 				  SEEK_SET);
999 			break;
1000 		}
1001 		return retval;
1002 	}
1003 #endif
1004 	return fseek(fp, offset, whence);
1005 }
1006 
1007 
1008 /*
1009  * Tell the position in a storyfile, either a standalone file
1010  * or the ZCODE chunk of a blorb file
1011  */
os_storyfile_tell(FILE * fp)1012 int os_storyfile_tell(FILE * fp)
1013 {
1014 #ifndef NO_BLORB
1015 	/* Is this a Blorb file containing Zcode? */
1016 	if (exec_in_blorb)
1017 		return ftell(fp) - blorb_res.data.startpos;
1018 #endif
1019 	return ftell(fp);
1020 }
1021