1 /*
2  * generic.c - SDL interface, generic useful user interface utility functions.
3  *
4  * The functions here are identical or nearly so in most ports.  They
5  * should really be put somewhere so that they can be shared.  For now,
6  * this.
7  *
8  * This file is part of Frotz.
9  *
10  * Frotz is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * Frotz is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23  * Or visit http://www.fsf.org/
24  */
25 
26 #include "../common/frotz.h"
27 #include "sf_frotz.h"
28 
29 #include <stddef.h>
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 
35 #include "../blorb/blorb.h"
36 #include "generic.h"
37 
38 FILE *blorb_fp;
39 bb_result_t blorb_res;
40 bb_map_t *blorb_map;
41 
42 
43 /*
44  * isblorb
45  *
46  * Returns 1 if this file is a Blorb file, 0 if not.
47  */
isblorb(FILE * fp)48 static int isblorb(FILE * fp)
49 {
50 	char mybuf[4];
51 
52 	if (fp == NULL)
53 		return 0;
54 
55 	fread(mybuf, 1, 4, fp);
56 	if (strncmp(mybuf, "FORM", 4))
57 		return 0;
58 
59 	fseek(fp, 4, SEEK_CUR);
60 	fread(mybuf, 1, 4, fp);
61 
62 	if (strncmp(mybuf, "IFRS", 4))
63 		return 0;
64 
65 	return 1;
66 }
67 
68 
69 /*
70  * gen_blorb_init
71  *
72  * Check if we're opening a Blorb file directly.  If not, check
73  * to see if there's a separate Blorb file that looks like it goes
74  * along with this Zcode file.  If we have a Blorb file one way or the
75  * other, make a Blorb map.  If we opened a Blorb file directly, that
76  * means that our executable is in that file and therefore we will look
77  * for a ZCOD chunk and record its location so os_load_story() can find it.
78  * Make sure the Blorb file is opened and with the file pointer blorb_fp.
79  */
gen_blorb_init(char * filename)80 bb_err_t gen_blorb_init(char *filename)
81 {
82 	FILE *fp;
83 	char *p;
84 	char *mystring;
85 	int len1;
86 	int len2;
87 
88 	bb_err_t blorb_err;
89 
90 	blorb_map = NULL;
91 
92 	if ((fp = fopen(filename, "rb")) == NULL)
93 		return bb_err_Read;
94 
95 	/* Is this really a Blorb file?  If not, maybe we're loading a naked
96 	 * zcode file and our resources are in a separate blorb file.
97 	 */
98 	if (isblorb(fp)) { /* Now we know to look */
99 		f_setup.exec_in_blorb = 1;	/* for zcode in the blorb */
100 		blorb_fp = fp;
101 	} else {
102 		fclose(fp);
103 		len1 = strlen(filename) + strlen(EXT_BLORB);
104 		len2 = strlen(filename) + strlen(EXT_BLORB3);
105 		if (f_setup.blorb_file != NULL)
106 			mystring = strdup(f_setup.blorb_file);
107 		else {
108 			mystring = malloc(MAX(len1, len2) * sizeof(char) + 1);
109 			memcpy(mystring, filename, MAX(len1, len2) * sizeof(char));
110 			p = strrchr(mystring, '.');
111 			if (p != NULL)
112 				*p = '\0';
113 			strncat(mystring, EXT_BLORB, len1);
114 		}
115 
116 		/* Check if foo.blb is there. */
117 		if ((fp = fopen(mystring, "rb")) == NULL) {
118 			p = strrchr(mystring, '.');
119 			if (p != NULL)
120 				*p = '\0';
121 			strncat(mystring, EXT_BLORB3, len2 * sizeof(char));
122 			if (!(fp = fopen(mystring, "rb")))
123 				return bb_err_NoBlorb;
124 		}
125 		if (!isblorb(fp)) {
126 			fclose(fp);
127 			return bb_err_NoBlorb;
128 		}
129 
130 		/* At this point we know that we're using a naked zcode file */
131 		/* with resources in a separate Blorb file. */
132 		blorb_fp = fp;
133 		f_setup.use_blorb = 1;
134 	}
135 
136 	/* Create a Blorb map from this file.
137 	 * This will fail if the file is not a valid Blorb file.
138 	 * From this map, we can now pick out any resource we need.
139 	 */
140 	blorb_err = bb_create_map(blorb_fp, &blorb_map);
141 	if (blorb_err != bb_err_None)
142 		return bb_err_Format;
143 
144 	/* Locate the EXEC chunk within the blorb file and record its
145 	 * location so os_load_story() can find it.
146 	 */
147 	if (f_setup.exec_in_blorb) {
148 		blorb_err = bb_load_chunk_by_type(blorb_map, bb_method_FilePos,
149 						  &blorb_res, bb_ID_ZCOD, 0);
150 		f_setup.exec_in_blorb = 1;
151 	}
152 
153 	return blorb_err;
154 }
155 
156 
157 /*
158  * os_load_story
159  *
160  * This is different from os_path_open() because we need to see if the
161  * story file is actually a chunk inside a blorb file.  Right now we're
162  * looking only at the exact path we're given on the command line.
163  *
164  * Open a file in the current directory.  If this fails, then search the
165  * directories in the ZCODE_PATH environmental variable.  If that's not
166  * defined, search INFOCOM_PATH.
167  *
168  */
os_load_story(void)169 FILE *os_load_story(void)
170 {
171 	FILE *fp;
172 
173 	switch (gen_blorb_init(f_setup.story_file)) {
174 	case bb_err_NoBlorb:
175 	/* printf("No blorb file found.\n\n"); */
176 		break;
177 	case bb_err_Format:
178 		printf("Blorb file loaded, but unable to build map.\n\n");
179 		break;
180 	case bb_err_NotFound:
181 		printf("Blorb file loaded, but lacks executable chunk.\n\n");
182 		break;
183 	case bb_err_None:
184 	/* printf("No blorb errors.\n\n"); */
185 		break;
186 	}
187 
188 	fp = fopen(f_setup.story_file, "rb");
189 
190 	/* Is this a Blorb file containing Zcode? */
191 	if (f_setup.exec_in_blorb)
192 		fseek(fp, blorb_res.data.startpos, SEEK_SET);
193 
194 	return fp;
195 }
196 
197 
198 /*
199  * os_storyfile_seek
200  *
201  * Seek into a storyfile, either a standalone file or the
202  * ZCODE chunk of a blorb file.
203  *
204  */
os_storyfile_seek(FILE * fp,long offset,int whence)205 int os_storyfile_seek(FILE * fp, long offset, int whence)
206 {
207 	/* Is this a Blorb file containing Zcode? */
208 	if (f_setup.exec_in_blorb) {
209 		switch (whence) {
210 		case SEEK_END:
211 			return fseek(fp,
212 				     blorb_res.data.startpos +
213 				     blorb_res.length + offset, SEEK_SET);
214 			break;
215 		case SEEK_CUR:
216 			return fseek(fp, offset, SEEK_CUR);
217 			break;
218 		case SEEK_SET:
219 		default:
220 			return fseek(fp, blorb_res.data.startpos + offset,
221 				     SEEK_SET);
222 			break;
223 		}
224 	} else {
225 		return fseek(fp, offset, whence);
226 	}
227 }
228 
229 
230 /*
231  * os_storyfile_tell
232  *
233  * Tell the position in a storyfile, either a standalone file
234  * or the ZCODE chunk of a blorb file.
235  *
236  */
os_storyfile_tell(FILE * fp)237 int os_storyfile_tell(FILE * fp)
238 {
239 	/* Is this a Blorb file containing Zcode? */
240 	if (f_setup.exec_in_blorb) {
241 		return ftell(fp) - blorb_res.data.startpos;
242 	} else {
243 		return ftell(fp);
244 	}
245 }
246 
247 
print_c_string(const char * s)248 static void print_c_string(const char *s)
249 {
250 	zchar c;
251 
252 	while ((c = *s++) != 0) {
253 		os_display_char(c);
254 	}
255 }
256 
257 
258 /*
259  * os_warn
260  *
261  * Display a warning message and continue with the game.
262  *
263  */
os_warn(const char * s,...)264 void os_warn(const char *s, ...)
265 {
266 	va_list va;
267 	char buf[1024];
268 	int len;
269 
270 	/* XXX Too lazy to do this right
271 	 * (try again with a bigger buf if necessary). */
272 	va_start(va, s);
273 	len = vsnprintf(buf, sizeof(buf), s, va);
274 	va_end(va);
275 	/* Solaris 2.6's cc complains if the below cast is missing */
276 	print_c_string("\n\n");
277 	os_beep(BEEP_HIGH);
278 	os_set_text_style(BOLDFACE_STYLE);
279 	print_c_string("Warning: ");
280 	os_set_text_style(0);
281 	print_c_string((len < 0 ? s : buf));
282 	print_c_string("\n");
283 	if (len < 0)
284 		print_c_string("(formatting error)\n");
285 	else if (len >= sizeof(buf))
286 		print_c_string("(truncated)\n");
287 	new_line();
288 }
289 
290 
zcharstrlen(zchar * str)291 size_t zcharstrlen(zchar * str)
292 {
293 	size_t ret = 0;
294 
295 	while (str[ret] != 0) {
296 		ret++;
297 	}
298 	return ret;
299 }
300 
301 
zcharstrcpy(zchar * dest,zchar * src)302 zchar *zcharstrcpy(zchar * dest, zchar * src)
303 {
304 	size_t i;
305 
306 	for (i = 0; src[i] != '\0'; i++)
307 		dest[i] = src[i];
308 	dest[i] = 0;
309 
310 	return dest;
311 }
312 
313 
zcharstrncmp(zchar * s1,zchar * s2,size_t n)314 int zcharstrncmp(zchar * s1, zchar * s2, size_t n)
315 {
316 	zchar u1, u2;
317 	while (n-- > 0) {
318 		u1 = *s1++;
319 		u2 = *s2++;
320 		if (u1 != u2)
321 			return u1 - u2;
322 		if (u1 == 0)
323 			return 0;
324 	}
325 	return 0;
326 }
327 
328 
zcharstrdup(zchar * src)329 zchar *zcharstrdup(zchar * src)
330 {
331 	zchar *dest = malloc((zcharstrlen(src) + 1) * sizeof(zchar));
332 	if (dest)
333 		zcharstrcpy(dest, src);
334 	return dest;
335 }
336 
337 
338 /* These are useful for circular buffers.
339  */
340 #define RING_DEC( ptr, beg, end) (ptr > (beg) ? --ptr : (ptr = (end)))
341 #define RING_INC( ptr, beg, end) (ptr < (end) ? ++ptr : (ptr = (beg)))
342 
343 #define MAX_HISTORY 256
344 static zchar *history_buffer[MAX_HISTORY];
345 static zchar **history_next = history_buffer;	/* Next available slot. */
346 static zchar **history_view = history_buffer;	/* What the user is looking at. */
347 #define history_end (history_buffer + MAX_HISTORY - 1)
348 
349 
350 /**
351  * Add the given string to the next available history buffer slot.
352  */
gen_add_to_history(zchar * str)353 void gen_add_to_history(zchar * str)
354 {
355 	if (*history_next != NULL)
356 		free(*history_next);
357 	*history_next = zcharstrdup(str);
358 	RING_INC(history_next, history_buffer, history_end);
359 	history_view = history_next;	/* Reset user frame after each line */
360 
361 	return;
362 }
363 
364 
365 /**
366  * Reset the view to the end of history.
367  */
gen_history_reset()368 void gen_history_reset()
369 {
370 	history_view = history_next;
371 }
372 
373 
374 /**
375  * Copy last available string to str, if possible.  Return 1 if successful.
376  * Only lines of at most maxlen characters will be considered.  In addition
377  * the first searchlen characters of the history entry must match those of str.
378  */
gen_history_back(zchar * str,int searchlen,int maxlen)379 int gen_history_back(zchar * str, int searchlen, int maxlen)
380 {
381 	zchar **prev = history_view;
382 
383 	do {
384 		RING_DEC(history_view, history_buffer, history_end);
385 		if ((history_view == history_next)
386 		    || (*history_view == NULL)) {
387 			os_beep(BEEP_HIGH);
388 			history_view = prev;
389 			return 0;
390 		}
391 	} while (zcharstrlen(*history_view) > (size_t)maxlen
392 		 || (searchlen != 0
393 		     && zcharstrncmp(str, *history_view, searchlen)));
394 	zcharstrcpy(str + searchlen, *history_view + searchlen);
395 	return 1;
396 }
397 
398 
399 /**
400  * Opposite of gen_history_back, and works in the same way.
401  */
gen_history_forward(zchar * str,int searchlen,int maxlen)402 int gen_history_forward(zchar * str, int searchlen, int maxlen)
403 {
404 	zchar **prev = history_view;
405 
406 	do {
407 		RING_INC(history_view, history_buffer, history_end);
408 		if ((history_view == history_next)
409 		    || (*history_view == NULL)) {
410 
411 			os_beep(BEEP_HIGH);
412 			history_view = prev;
413 			return 0;
414 		}
415 	} while (zcharstrlen(*history_view) > (size_t)maxlen
416 		 || (searchlen != 0
417 		     && zcharstrncmp(str, *history_view, searchlen)));
418 	zcharstrcpy(str + searchlen, *history_view + searchlen);
419 	return 1;
420 }
421