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