1 /*
2  * sf_util.c - SDL interface, startup functions
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 "sf_frotz.h"
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28 
29 #include <libgen.h>
30 
31 #include <SDL.h>
32 #include <zlib.h>
33 
34 #ifdef __WIN32__
35 #include <io.h>
36 #endif
37 
38 #ifdef UNIX
39 #include <unistd.h>
40 #endif
41 
42 extern f_setup_t f_setup;
43 
44 typedef void (*CLEANFUNC)();
45 
46 typedef struct cfstruct cfrec;
47 
48 static void print_version(void);
49 
50 enum { USAGE_NORMAL, USAGE_EXTENDED };
51 
52 struct cfstruct {
53 	CLEANFUNC func;
54 	cfrec *next;
55 	const char *name;
56 };
57 
58 static cfrec *cflist = NULL;
59 
60 static int getcolor(char *);
61 
62 
sf_regcleanfunc(void * f,const char * p)63 void sf_regcleanfunc(void *f, const char *p)
64 {
65 	cfrec *n = calloc(1, sizeof(cfrec));
66 	if (n) {
67 		if (!p)
68 			p = "";
69 		n->func = (CLEANFUNC) f;
70 		n->name = p;
71 		n->next = cflist;
72 		cflist = n;
73 	}
74 }
75 
76 
sf_cleanup_all()77 void sf_cleanup_all()
78 {
79 	while (cflist) {
80 		cfrec *n = cflist->next;
81 		if (cflist->func)
82 			cflist->func();
83 		free(cflist);
84 		cflist = n;
85 	}
86 }
87 
88 
89 /*
90  * os_reset_screen
91  *
92  * Reset the screen before the program ends.
93  *
94  */
os_reset_screen(void)95 void os_reset_screen(void)
96 {
97 	sf_flushdisplay();
98 
99 	if (m_exitPause) {
100 		const char *hit = sf_msgstring(IDS_HIT_KEY_EXIT);
101 		os_set_font(TEXT_FONT);
102 		os_set_text_style(0);
103 		screen_new_line();
104 
105 		while (*hit)
106 			os_display_char((*hit++));
107 		sf_read_key(0, true, false, false);
108 	}
109 
110 	sf_cleanup_all();
111 }
112 /*
113  * os_quit
114  *
115  * Immediately and cleanly exit, passing along exit status.
116  *
117  */
os_quit(int status)118 void os_quit(int status)
119 {
120 	exit(status);
121 } /* os_quit */
122 
123 int user_background = -1;
124 int user_foreground = -1;
125 int user_emphasis = -1;
126 int user_bold_typing = -1;
127 int user_reverse_bg = -1;
128 int user_reverse_fg = -1;
129 int user_screen_height = -1;
130 int user_screen_width = -1;
131 int user_tandy_bit = -1;
132 int user_font = 1;
133 int m_random_seed = -1;
134 int m_fullscreen = -1;
135 int m_reqW = 0, m_reqH = 0;
136 int m_vga_fonts = 0;
137 extern char *m_setupfile;
138 extern char m_names_format;
139 static char user_names_format = 0;
140 extern char *m_reslist_file;
141 extern int option_scrollback_buffer;
142 
143 static char *info_header =
144     "FROTZ V%s - SDL graphics and audio interface.\n"
145     "An interpreter for all Infocom and other Z-Machine games.\n\n"
146     "Syntax: sfrotz [options] story-file\n";
147 
148 static char *info[] = {
149 	"-a   watch attribute setting",
150 	"-A   watch attribute testing",
151 	"-b <colourname> background colour",
152 	"-c # context lines",
153 	"-f <colorname> foreground colour",
154 	"-F   fullscreen mode",
155 	"-H # screen height",
156 	"-i   ignore runtime errors",
157 	"-I # interpreter number",
158 	"-l # left margin",
159 	"-L <file> load this save file",
160 	"-o   watch object movement",
161 	"-O   watch object locating",
162 	"-P   alter piracy opcode",
163 	"-q   quiet (disable sound)",
164 	"-r # right margin",
165 	"-s # random number seed value",
166 	"-S # transcript width",
167 	"-t   set Tandy bit",
168 	"-u # slots for multiple undo",
169 	"-W # screen width",
170 	"-x   expand abbreviations g/x/z",
171 	"-X   show extended options",
172 	"-v   show version information",
173 	"-Z # error checking (see below)",
174 	NULL
175 };
176 
177 static char *info_footer =
178     "\nError checking: 0 none, 1 first only (default), 2 all, 3 exit after any error.";
179 
180 static char *extended_header = "\nExtended Options\n";
181 
182 static char *extended_options[] = {
183 	"-@ <file> use resources in <file>",
184 	"-%        use local resources",
185 	"-F        run in fullscreen mode.",
186 	"-m #      set timer interrupt cycle to # milliseconds",
187 	"-N <mode> add date or number to save filename",
188 	"-T        use in-game requests for filenames",
189 	"-V        force VGA fonts",
190 	NULL
191 };
192 
193 static char *footer =
194     "More options and information are in the manual page.  Type \"man sfrotz\".\n";
195 
196 
197 #define WIDCOL 40
usage(int type)198 static void usage(int type)
199 {
200 	char **p;
201 	int i = 0, len = 0;
202 
203 	printf(info_header, VERSION);
204 
205 	if (type == USAGE_NORMAL)
206 		p = info;
207 	else {
208 		p = extended_options;
209 		puts(extended_header);
210 	}
211 
212 	while (*p) {
213 		if (i && type == USAGE_NORMAL) {
214 			while (len > 0) {
215 				fputc(' ', stdout);
216 				len--;
217 			}
218 			puts(*p);
219 		} else {
220 			fputs("  ", stdout);
221 			fputs(*p, stdout);
222 			if (type == USAGE_NORMAL)
223 				len = WIDCOL - strlen(*p) - 2;
224 			else
225 				fputs("\n", stdout);
226 		}
227 		i = 1 - i;
228 		p++;
229 	}
230 	if (i)
231 		fputc('\n', stdout);
232 
233 	if (type == USAGE_NORMAL)
234 		puts(info_footer);
235 	puts(footer);
236 
237 }
238 
239 
240 /*
241  * parse_options
242  *
243  * Parse program options and set global flags accordingly.
244  *
245  */
246 
247 static const char *progname = NULL;
248 
249 /*
250 extern char *optarg;
251 extern int optind;
252 */
253 
254 extern int m_timerinterval;
255 
256 static char *options = "@:%aAb:B:c:f:FH:iI:l:L:m:N:oOPqr:s:S:tTu:vVW:xXZ:";
257 
limit(int v,int m,int M)258 static int limit(int v, int m, int M)
259 {
260 	if (v < m)
261 		return m;
262 	if (v > M)
263 		return M;
264 	return v;
265 }
266 
267 
parse_options(int argc,char ** argv)268 static void parse_options(int argc, char **argv)
269 {
270 	int c;
271 
272 	do {
273 		int num = 0, copt = 0;;
274 
275 		c = zgetopt(argc, argv, options);
276 
277 		if (zoptarg != NULL) {
278 			num = atoi(zoptarg);
279 			copt = zoptarg[0];
280 		}
281 
282 		if (c == '%')
283 			m_localfiles = true;
284 		if (c == 'a')
285 			f_setup.attribute_assignment = 1;
286 		if (c == 'A')
287 			f_setup.attribute_testing = 1;
288 		if (c == 'b')
289 			user_background = getcolor(zoptarg);
290 		if (c == 'B')
291 			option_scrollback_buffer = num;
292 		if (c == 'c')
293 			f_setup.context_lines = num;
294 		if (c == 'm')
295 			m_timerinterval = limit(num, 10, 1000000);
296 		if (c == 'N')
297 			user_names_format = copt;
298 		if (c == '@')
299 			m_reslist_file = zoptarg;
300 		if (c == 'f')
301 			user_foreground = getcolor(zoptarg);
302 		if (c == 'F')
303 			m_fullscreen = 1;
304 		if (c == 'H')
305 			user_screen_height = num;
306 		if (c == 'i')
307 			f_setup.ignore_errors = 1;
308 		if (c == 'I')
309 			f_setup.interpreter_number = num;
310 		if (c == 'l')
311 			f_setup.left_margin = num;
312 		if (c == 'L') {
313 			f_setup.restore_mode = TRUE;
314 			f_setup.tmp_save_name = strdup(zoptarg);
315 		}
316 		if (c == 'q')
317 			m_no_sound = 1;
318 		if (c == 'o')
319 			f_setup.object_movement = 1;
320 		if (c == 'O')
321 			f_setup.object_locating = 1;
322 		if (c == 'P')
323 			f_setup.piracy = 1;
324 		if (c == 'r')
325 			f_setup.right_margin = num;
326 		if (c == 's')
327 			m_random_seed = num;
328 		if (c == 'S')
329 			f_setup.script_cols = num;
330 		if (c == 't')
331 			user_tandy_bit = 1;
332 		if (c == 'T')
333 			sf_osdialog = NULL;
334 		if (c == 'u')
335 			f_setup.undo_slots = num;
336 		if (c == 'v')
337 			print_version();
338 		if (c == 'V')
339 			m_vga_fonts = 1;
340 		if (c == 'W')
341 			user_screen_width = num;
342 		if (c == 'x')
343 			f_setup.expand_abbreviations = 1;
344 		if (c == 'X') {
345 			usage(USAGE_EXTENDED);
346 			os_quit(EXIT_SUCCESS);
347 		}
348 		if (c == 'Z')
349 			if (num >= ERR_REPORT_NEVER && num <= ERR_REPORT_FATAL)
350 				f_setup.err_report_mode = num;
351 		if (c == '?')
352 			zoptind = argc;
353 	} while (c != EOF && c != '?');
354 
355 } /* parse_options */
356 
357 
print_version(void)358 static void print_version(void)
359 {
360 	printf("FROTZ V%s\tSDL interface.\n", VERSION);
361 	printf("Commit date:\t%s\n", GIT_DATE);
362 	printf("Git commit:\t%s\n", GIT_HASH);
363 	printf("  Frotz was originally written by Stefan Jokisch.\n");
364 	printf
365 	    ("  It complies with standard 1.0 of Graham Nelson's specification.\n");
366 	printf("  It was ported to Unix by Galen Hazelwood.\n");
367 	printf
368 	    ("  The core and SDL port are maintained by David Griffith.\n");
369 	printf("  Frotz's homepage is https://661.org/proj/if/frotz/\n\n");
370 	os_quit(EXIT_SUCCESS);
371 }
372 
373 
374 /**
375  * Like dirname except well defined.
376  * Does not modify path.  Always returns a new string (caller must free).
377  */
new_dirname(const char * path)378 static char *new_dirname(const char *path)
379 {
380 	char *p = strdup(path), *p2 = strdup(dirname(p));
381 	free(p);
382 	return p2;
383 }
384 
385 
386 /**
387  * Like basename except well defined.
388  * Does not modify path.  Always returns a new string (caller must free).
389  */
new_basename(const char * path)390 static char *new_basename(const char *path)
391 {
392 	char *p = strdup(path), *p2 = strdup(basename(p));
393 	free(p);
394 	return p2;
395 }
396 
397 
398 /*
399  * os_process_arguments
400  *
401  * Handle command line switches.
402  * Some variables may be set to activate special features of Frotz.
403  *
404  */
os_process_arguments(int argc,char * argv[])405 void os_process_arguments(int argc, char *argv[])
406 {
407 	char *p;
408 
409 	zoptarg = NULL;
410 
411 	sf_installhandlers();
412 	sf_readsettings();
413 	parse_options(argc, argv);
414 
415 	if (argv[zoptind] == NULL) {
416 		usage(USAGE_NORMAL);
417 		os_quit(EXIT_SUCCESS);
418 	}
419 	f_setup.story_file = strdup(argv[zoptind]);
420 
421 	if (argv[zoptind + 1] != NULL)
422 		f_setup.blorb_file = argv[zoptind + 1];
423 
424 	/* Strip path and extension off the story file name */
425 	f_setup.story_name = new_basename(f_setup.story_file);
426 
427 	/* Now strip off the extension. */
428 	p = strrchr(f_setup.story_name, '.');
429 	if ((p != NULL) &&
430 	    ((strcmp(p, EXT_BLORB2) == 0) ||
431 	     (strcmp(p, EXT_BLORB3) == 0) || (strcmp(p, EXT_BLORB4) == 0))) {
432 		/*  blorb_ext = strdup(p); */
433 	} else
434 		/*  blorb_ext = strdup(EXT_BLORB); */
435 
436 		/* Get rid of extensions with 1 to 6 character extensions. */
437 		/* This will take care of an extension like ".zblorb". */
438 		/* More than that, there might be something weird going on */
439 		/* which is not our concern. */
440 	if (p != NULL) {
441 		if (strlen(p) >= 2 && strlen(p) <= 7) {
442 			*p = '\0';	/* extension removed */
443 		}
444 	}
445 	f_setup.story_path = new_dirname(argv[zoptind]);
446 
447 	/* Create nice default file names */
448 	f_setup.script_name =
449 	    malloc((strlen(f_setup.story_name) +
450 		    strlen(EXT_SCRIPT)) * sizeof(char) + 1);
451 	memcpy(f_setup.script_name, f_setup.story_name, strlen(f_setup.story_name) * sizeof(char));
452 	strncat(f_setup.script_name, EXT_SCRIPT, strlen(EXT_SCRIPT) + 1);
453 
454 	f_setup.command_name =
455 	    malloc((strlen(f_setup.story_name) +
456 		    strlen(EXT_COMMAND)) * sizeof(char) + 1);
457 	memcpy(f_setup.command_name, f_setup.story_name, strlen(f_setup.story_name) * sizeof(char));
458 	strncat(f_setup.command_name, EXT_COMMAND, strlen(EXT_COMMAND) + 1);
459 
460 	if (!f_setup.restore_mode) {
461 		f_setup.save_name =
462 		    malloc((strlen(f_setup.story_name) +
463 			    strlen(EXT_SAVE)) * sizeof(char) + 1);
464 		memcpy(f_setup.save_name, f_setup.story_name, strlen(f_setup.story_name) * sizeof(char));
465 		strncat(f_setup.save_name, EXT_SAVE, strlen(EXT_SAVE) + 1);
466 	} else {	/* Set our auto load save as the name_save */
467 		f_setup.save_name =
468 		    malloc((strlen(f_setup.tmp_save_name) +
469 			    strlen(EXT_SAVE)) * sizeof(char) + 1);
470 		memcpy(f_setup.save_name, f_setup.tmp_save_name, strlen(f_setup.tmp_save_name) * sizeof(char));
471 		free(f_setup.tmp_save_name);
472 	}
473 
474 	f_setup.aux_name =
475 	    malloc((strlen(f_setup.story_name) +
476 		    strlen(EXT_AUX)) * sizeof(char) + 1);
477 	memcpy(f_setup.aux_name, f_setup.story_name, strlen(f_setup.story_name) * sizeof(char));
478 	strncat(f_setup.aux_name, EXT_AUX, strlen(EXT_AUX) + 1);
479 
480 	/* Save the executable file name */
481 	progname = argv[0];
482 
483 	if (user_screen_width > 0)
484 		AcWidth = user_screen_width;
485 	if (user_screen_height > 0)
486 		AcHeight = user_screen_height;
487 
488 	if (user_names_format)
489 		m_names_format = user_names_format;
490 
491 	if (user_background != -1)
492 		m_defaultBack = sf_GetColour(user_background);
493 	if (user_foreground != -1)
494 		m_defaultFore = sf_GetColour(user_foreground);
495 	if (user_tandy_bit != -1)
496 		m_tandy = user_tandy_bit;
497 
498 	sf_initfonts();
499 } /* os_process_arguments */
500 
501 
502 #ifdef WIN32
503 #include <windows.h>
504 #else
505 #include <time.h>
506 #include <sys/time.h>
507 #endif
508 
sf_sleep(int msecs)509 void sf_sleep(int msecs)
510 {
511 	SDL_Delay(msecs);
512 }
513 
514 #ifdef WIN32
sf_ticks(void)515 unsigned long sf_ticks(void)
516 {
517 	return (GetTickCount());
518 }
519 #else
sf_ticks(void)520 unsigned long sf_ticks(void)
521 {
522 	struct timeval now;
523 	static struct timeval start;
524 	static int started = 0;
525 	unsigned long ticks;
526 	now.tv_sec = now.tv_usec = 0;
527 	gettimeofday(&now, NULL);
528 	if (!started) {
529 		started = 1;
530 		start = now;
531 	}
532 	ticks =
533 	    (now.tv_sec - start.tv_sec) * 1000 + (now.tv_usec -
534 						  start.tv_usec) / 1000;
535 	return ticks;
536 }
537 #endif
538 
539 
getextension(int flag)540 static char *getextension(int flag)
541 {
542 	char *ext = EXT_AUX;
543 
544 	if (flag == FILE_SAVE || flag == FILE_RESTORE)
545 		ext = EXT_SAVE;
546 	else if (flag == FILE_SCRIPT)
547 		ext = EXT_SCRIPT;
548 	else if (flag == FILE_RECORD || flag == FILE_PLAYBACK)
549 		ext = EXT_COMMAND;
550 
551 	return ext;
552 }
553 
554 
newfile(int flag)555 static bool newfile(int flag)
556 {
557 	if (flag == FILE_SAVE || flag == FILE_SAVE_AUX || flag == FILE_RECORD)
558 		return true;
559 	return false;
560 }
561 
562 
563 static char buf[FILENAME_MAX];
564 
getnumbername(const char * def,char * ext)565 static const char *getnumbername(const char *def, char *ext)
566 {
567 	int len, number = 0;
568 	strcpy(buf, f_setup.story_name);
569 	len = strlen(buf);
570 	for (;;) {
571 		sprintf(buf + len, "%03d%s", number++, ext);
572 		if (access(buf, F_OK))
573 			break;
574 	}
575 	return buf;
576 }
577 
578 
getdatename(const char * def,char * ext)579 static const char *getdatename(const char *def, char *ext)
580 {
581 	int len;
582 
583 	time_t t;
584 	struct tm *tm;
585 	time(&t);
586 	tm = localtime(&t);
587 
588 	strcpy(buf, f_setup.story_name);
589 	len = strlen(buf);
590 	sprintf(buf + len, "%04d%02d%02d%02d%02d%s",
591 		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
592 		tm->tm_hour, tm->tm_min, ext);
593 	return buf;
594 }
595 
596 
597 static int ingame_read_file_name(char *file_name, const char *default_name,
598 				 int flag);
599 static int dialog_read_file_name(char *file_name, const char *default_name,
600 				 int flag);
601 
602 /*
603  * os_read_file_name
604  *
605  * Return the name of a file. Flag can be one of:
606  *
607  *    FILE_SAVE     - Save game file
608  *    FILE_RESTORE  - Restore game file
609  *    FILE_SCRIPT   - Transscript file
610  *    FILE_RECORD   - Command file for recording
611  *    FILE_PLAYBACK - Command file for playback
612  *    FILE_SAVE_AUX - Save auxilary ("preferred settings") file
613  *    FILE_LOAD_AUX - Load auxilary ("preferred settings") file
614  *
615  * The length of the file name is limited by MAX_FILE_NAME. Ideally
616  * an interpreter should open a file requester to ask for the file
617  * name. If it is unable to do that then this function should call
618  * print_string and read_string to ask for a file name.
619  *
620  * Return value is NULL is there was a problem
621  */
os_read_file_name(const char * default_name,int flag)622 char *os_read_file_name(const char *default_name, int flag)
623 {
624 	int st;
625 	const char *initname = default_name;
626 	char file_name[FILENAME_MAX + 1];
627 
628 	if (newfile(flag)) {
629 		char *ext = getextension(flag);
630 		if (m_names_format == 'd')
631 			initname = getdatename(initname, ext);
632 		else if (m_names_format == 'n')
633 			initname = getnumbername(initname, ext);
634 	}
635 
636 	/* If we're restoring a game before the interpreter starts,
637 	 * and our filename is already provided with the -L flag,
638 	 * just go ahead silently.
639 	 */
640 	if (f_setup.restore_mode) {
641 		strncpy(file_name, f_setup.save_name, FILENAME_MAX);
642 	} else {
643 		st = dialog_read_file_name(file_name, initname, flag);
644 		if (st == SF_NOTIMP)
645 			st = ingame_read_file_name(file_name, initname, flag);
646 
647 		if (!st)
648 			return NULL;
649 	}
650 
651 	return strdup(file_name);
652 }
653 
654 
ingame_read_file_name(char * file_name,const char * default_name,int flag)655 static int ingame_read_file_name(char *file_name, const char *default_name,
656 				 int flag)
657 {
658 	char *extension;
659 	FILE *fp;
660 	bool terminal;
661 	bool result;
662 
663 	bool saved_replay = istream_replay;
664 	bool saved_record = ostream_record;
665 
666 	/* Turn off playback and recording temporarily */
667 	istream_replay = FALSE;
668 	ostream_record = FALSE;
669 
670 	/* Select appropriate extension */
671 	extension = getextension(flag);
672 
673 	/* Input file name (reserve four bytes for a file name extension) */
674 	print_string("Enter file name (\"");
675 	print_string(extension);
676 	print_string("\" will be added).\nDefault is \"");
677 	print_string(default_name);
678 	print_string("\": ");
679 
680 #ifdef USE_UTF8
681 	{
682 		zchar z_name[FILENAME_MAX + 1];
683 		zchar *zp;
684 		int i = 0;
685 		read_string(FILENAME_MAX - 4, z_name);
686 		zp = z_name;
687 		while (*zp) {
688 			if (*zp <= 0x7f) {
689 				if (i > FILENAME_MAX - 4)
690 					break;
691 				file_name[i++] = *zp;
692 			} else if (*zp > 0x7ff) {
693 				if (i > FILENAME_MAX - 6)
694 					break;
695 				file_name[i++] = 0xe0 | ((*zp >> 12) & 0xf);
696 				file_name[i++] = 0x80 | ((*zp >> 6) & 0x3f);
697 				file_name[i++] = 0x80 | (*zp & 0x3f);
698 			} else {
699 				if (i > FILENAME_MAX - 5)
700 					break;
701 				file_name[i++] = 0xc0 | ((*zp >> 6) & 0x1f);
702 				file_name[i++] = 0x80 | (*zp & 0x3f);
703 			}
704 			zp++;
705 		}
706 		file_name[i] = 0;
707 	}
708 #else
709 	read_string(MAX_FILE_NAME - 4, (zchar *) file_name);
710 #endif
711 
712 	/* Use the default name if nothing was typed */
713 	if (file_name[0] == 0)
714 		strcpy(file_name, default_name);
715 	if (strchr(file_name, '.') == NULL)
716 		strcat(file_name, extension);
717 
718 	/* Make sure it is safe to use this file name */
719 	result = TRUE;
720 
721 	/* OK if the file is opened for reading */
722 	if (!newfile(flag))
723 		goto finished;
724 
725 	/* OK if the file does not exist */
726 	if ((fp = fopen(file_name, "rb")) == NULL)
727 		goto finished;
728 
729 	/* OK if this is a pseudo-file (like PRN, CON, NUL) */
730 	terminal = isatty(fileno(fp));
731 
732 	fclose(fp);
733 
734 	if (terminal)
735 		goto finished;
736 
737 	/* OK if user wants to overwrite */
738 	result = read_yes_or_no("Overwrite existing file");
739 
740  finished:
741 
742 	/* Restore state of playback and recording */
743 	istream_replay = saved_replay;
744 	ostream_record = saved_record;
745 
746 	return result;
747 } /* os_read_file_name */
748 
749 
dialog_read_file_name(char * file_name,const char * default_name,int flag)750 static int dialog_read_file_name(char *file_name, const char *default_name,
751 				 int flag)
752 {
753 	int filter = 0;
754 	int title = 0, st;
755 	char *res;
756 
757 	sf_flushdisplay();
758 
759 	switch (flag) {
760 	case FILE_SAVE:
761 		filter = IDS_SAVE_FILTER;
762 		title = IDS_SAVE_TITLE;
763 		break;
764 	case FILE_RESTORE:
765 		filter = IDS_SAVE_FILTER;
766 		title = IDS_RESTORE_TITLE;
767 		break;
768 	case FILE_SCRIPT:
769 		filter = IDS_SCRIPT_FILTER;
770 		title = IDS_SCRIPT_TITLE;
771 		break;
772 	case FILE_RECORD:
773 		filter = IDS_RECORD_FILTER;
774 		title = IDS_RECORD_TITLE;
775 		break;
776 	case FILE_PLAYBACK:
777 		filter = IDS_RECORD_FILTER;
778 		title = IDS_PLAYBACK_TITLE;
779 		break;
780 	case FILE_SAVE_AUX:
781 		filter = IDS_AUX_FILTER;
782 		title = IDS_SAVE_AUX_TITLE;
783 		break;
784 	case FILE_LOAD_AUX:
785 		filter = IDS_AUX_FILTER;
786 		title = IDS_LOAD_AUX_TITLE;
787 		break;
788 	default:
789 		return 0;
790 	}
791 
792 	st = sf_user_fdialog(!newfile(flag), default_name, sf_msgstring(filter),
793 			     sf_msgstring(title), &res);
794 	if (st == SF_NOTIMP)
795 		return st;
796 	if (st == 0) {
797 		strncpy(file_name, res, MAX_FILE_NAME);
798 		file_name[MAX_FILE_NAME - 1] = 0;
799 		return 1;
800 	}
801 	return 0;
802 }
803 
804 static char *rc = NULL;
805 
sf_FinishProfile()806 void sf_FinishProfile()
807 {
808 	if (!rc)
809 		return;
810 	free(rc);
811 	rc = NULL;
812 }
813 
814 
sf_InitProfile(const char * fn)815 void sf_InitProfile(const char *fn)
816 {
817 	FILE *f;
818 	int size;
819 	char *s, *d;
820 	char *my_fn;
821 	char *homedir;
822 	int len;
823 
824 	if (!fn)
825 		return;
826 
827 	homedir = strdup(getenv(HOMEDIR));
828 	len = ((strlen(homedir) + strlen(fn) + 1) * sizeof(char)) + 1;
829 	my_fn = malloc(len);
830 	snprintf(my_fn, len, "%s/%s", homedir, fn);
831 
832 	f = fopen(fn, "rb");
833 	if (!f) {
834 		f = fopen(my_fn, "rb");
835 		if (!f)
836 			return;
837 	}
838 	fseek(f, 0, SEEK_END);
839 	size = ftell(f);
840 	if (!size) {
841 		fclose(f);
842 		return;
843 	}
844 	rc = malloc(size + 1);
845 	if (!rc) {
846 		fclose(f);
847 		return;
848 	}
849 	fseek(f, 0, 0);
850 	fread(rc, 1, size, f);
851 	fclose(f);
852 	rc[size] = 0;
853 
854 	s = d = rc;
855 
856 	while (*s) {
857 		if (*s == '#') {
858 			while ((*s) && (*s != '\n'))
859 				s++;
860 			if (!*s)
861 				break;
862 		} else
863 			*d++ = *s++;
864 	}
865 	*d = 0;
866 
867 	CLEANREG(sf_FinishProfile);
868 }
869 
870 
findsect(const char * sect)871 static char *findsect(const char *sect)
872 {
873 	int ns = strlen(sect);
874 	char *r = rc;
875 	while (r) {
876 		r = strchr(r, '[');
877 		if (!r)
878 			return NULL;
879 		r++;
880 		if (strncmp(r, sect, ns))
881 			continue;
882 		return (r + ns);
883 	}
884 	return NULL;
885 }
886 
887 
findid(const char * sect,const char * id)888 static char *findid(const char *sect, const char *id)
889 {
890 	int nid = strlen(id);
891 	char *r, *sav, *rq, *fnd = NULL;
892 	r = findsect(sect);
893 	if (!r)
894 		return NULL;
895 	sav = strchr(r, '[');
896 	if (sav)
897 		*sav = 0;
898 	while (r) {
899 		r = strstr(r, id);
900 		if (!r)
901 			break;
902 		rq = r + nid;
903 		if ((*(byte *) (r - 1) <= ' ')
904 		    && ((*rq == ' ') || (*rq == '='))) {
905 			while (*rq)
906 				if (*rq++ == '=')
907 					break;
908 			if (*rq) {
909 				fnd = rq;
910 				break;
911 			}
912 		}
913 		r = rq;
914 	}
915 	if (sav)
916 		*sav = '[';
917 	return fnd;
918 }
919 
920 
sf_GetProfileInt(const char * sect,const char * id,int def)921 int sf_GetProfileInt(const char *sect, const char *id, int def)
922 {
923 	if (rc) {
924 		char *p = findid(sect, id);
925 		if (p)
926 			def = atoi(p);
927 	}
928 	return def;
929 }
930 
931 
sf_GetProfileDouble(const char * sect,const char * id,double def)932 double sf_GetProfileDouble(const char *sect, const char *id, double def)
933 {
934 	if (rc) {
935 		char *p = findid(sect, id);
936 		if (p)
937 			def = atof(p);
938 	}
939 	return def;
940 }
941 
942 
sf_GetProfileString(const char * sect,const char * id,char * def)943 char *sf_GetProfileString(const char *sect, const char *id, char *def)
944 {
945 	char *q = NULL, sav = 0;
946 	if (rc) {
947 		char *p = findid(sect, id);
948 		if (p) {
949 			int quoted = 0;
950 			for (; *p; p++) {
951 				if (*p == '\"') {
952 					quoted = 1;
953 					p++;
954 					break;
955 				}
956 				if ((byte) (*p) > ' ')
957 					break;
958 			}
959 			if (*p) {
960 				if (quoted)
961 					q = strchr(p, '\"');
962 				if (!q) {
963 					q = p;
964 					while (*q > ' ')
965 						q++;
966 					sav = *q;
967 					*q = 0;
968 				}
969 			}
970 			def = p;
971 		}
972 	}
973 	if (def)
974 		def = strdup(def);
975 	if (sav)
976 		*q = sav;
977 	return def;
978 }
979 
980 
981 /*  A.  Local file header:
982  *
983  *         local file header signature   0  4 bytes  (0x04034b50)
984  *         version needed to extract     4  2 bytes
985  *         general purpose bit flag      6  2 bytes
986  *         compression method            8  2 bytes
987  *         last mod file time           10  2 bytes
988  *         last mod file date           12  2 bytes
989  *         crc-32                       14  4 bytes
990  *         compressed size              18  4 bytes
991  *         uncompressed size            22  4 bytes
992  *         file name length             26  2 bytes
993  *         extra field length           28  2 bytes
994  *
995  *         file name (variable size)
996  *         extra field (variable size)
997  */
998 
999 #define plong( b) (((int)((b)[3]) << 24) + ((int)((b)[2]) << 16) +\
1000 	((int)((b)[1]) << 8) + (int)((b)[0]))
1001 
1002 #define pshort( b) (((int)((b)[1]) << 8) + (int)((b)[0]))
1003 
1004 
myin(void * d,byte ** b)1005 static unsigned myin(void *d, byte ** b)
1006 {
1007 	return 0;
1008 }
1009 
1010 
myout(void * udata,byte * b,unsigned n)1011 static int myout(void *udata, byte * b, unsigned n)
1012 {
1013 	memmove(udata, b, n);
1014 	udata += n;
1015 	return 0;
1016 }
1017 
1018 
myunzip(int csize,byte * cdata,byte * udata)1019 static int myunzip(int csize, byte * cdata, byte * udata)
1020 {
1021 	byte window[32768];
1022 	z_stream z;
1023 	int st;
1024 
1025 	memset(&z, 0, sizeof(z));
1026 
1027 	st = inflateBackInit(&z, 15, window);
1028 	if (st)
1029 		return st;
1030 
1031 	z.next_in = cdata;
1032 	z.avail_in = csize;
1033 
1034 	for (;;) {
1035 		st = inflateBack(&z, myin, NULL, myout, udata);
1036 		if (st == Z_STREAM_END)
1037 			break;
1038 		if (st)
1039 			return st;
1040 	}
1041 
1042 	st = inflateBackEnd(&z);
1043 	return st;
1044 }
1045 
1046 
sf_pkread(FILE * f,int foffs,void ** out,int * size)1047 int sf_pkread(FILE * f, int foffs, void **out, int *size)
1048 {
1049 	byte hd[30];
1050 	byte *data, *cdata;
1051 	int csize, usize, cmet, skip, st;
1052 
1053 	fseek(f, foffs, SEEK_SET);
1054 	fread(hd, 1, 30, f);
1055 	cmet = pshort(hd + 8);
1056 	if (cmet != 8)
1057 		return -10;
1058 	csize = plong(hd + 18);
1059 	usize = plong(hd + 22);
1060 	if (csize <= 0)
1061 		return -11;
1062 	if (usize <= 0)
1063 		return -12;
1064 	data = malloc(usize);
1065 	if (!data)
1066 		return -13;
1067 	cdata = malloc(csize);
1068 	if (!cdata) {
1069 		free(data);
1070 		return -14;
1071 	}
1072 	skip = pshort(hd + 26) + pshort(hd + 28);
1073 	fseek(f, foffs + 30 + skip, SEEK_SET);
1074 	fread(cdata, 1, csize, f);
1075 
1076 	st = myunzip(csize, cdata, data);
1077 
1078 	free(cdata);
1079 	if (st) {
1080 		free(data);
1081 		return st;
1082 	}
1083 	*out = (void *)data;
1084 	*size = usize;
1085 	return st;
1086 }
1087 
1088 
1089 /*
1090  * getcolor
1091  *
1092  * Figure out what color this string might indicate and returns an integer
1093  * corresponding to the color macros defined in frotz.h.
1094  *
1095  */
getcolor(char * value)1096 static int getcolor(char *value)
1097 {
1098 	int num;
1099 
1100 	/* Be case-insensitive */
1101 	for (num = 0; value[num] !=0; num++)
1102 		value[num] = tolower((int) value[num]);
1103 
1104 	if (strcmp(value, "black") == 0)
1105 		return BLACK_COLOUR;
1106 	if (strcmp(value, "red") == 0)
1107 		return RED_COLOUR;
1108 	if (strcmp(value, "green") == 0)
1109 		return GREEN_COLOUR;
1110 	if (strcmp(value, "blue") == 0)
1111 		return BLUE_COLOUR;
1112 	if (strcmp(value, "magenta") == 0)
1113 		return MAGENTA_COLOUR;
1114 	if (strcmp(value, "cyan") == 0)
1115 		return CYAN_COLOUR;
1116 	if (strcmp(value, "white") == 0)
1117 		return WHITE_COLOUR;
1118 	if (strcmp(value, "yellow") == 0)
1119 		return YELLOW_COLOUR;
1120 
1121 	if (strcmp(value, "purple") == 0)
1122 		return MAGENTA_COLOUR;
1123 	if (strcmp(value, "violet") == 0)
1124 		return MAGENTA_COLOUR;
1125 	if (strcmp(value, "aqua") == 0)
1126 		return CYAN_COLOUR;
1127 
1128 	/* If we can't tell what that string means,
1129 	 * we tell caller to use the default.
1130 	 */
1131 
1132 	return -1;
1133 } /* getcolor */
1134 
1135 
1136 /************************/
1137 
1138 #include <stdio.h>
1139 #include <stdlib.h>
1140 #include <string.h>
1141 #include <time.h>
1142 #include <sys/stat.h>
1143 #ifdef WIN32
1144 #include <windows.h>
1145 #else
1146 #include <unistd.h>
1147 #endif
1148 
1149 #ifndef WIN32
1150 #define _stat stat
1151 #endif
1152