1 /* quetzal.c  - Saving and restoring of Quetzal files.
2  *	Written by Martin Frost <mdf@doc.ic.ac.uk>
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  */
20 
21 #include <stdio.h>
22 #include <string.h>
23 #include "frotz.h"
24 
25 #ifdef MSDOS_16BIT
26 
27 #include <alloc.h>
28 #include <dos.h>
29 
30 #define malloc(size)	farmalloc (size)
31 #define realloc(size,p)	farrealloc (size,p)
32 #define free(size)	farfree (size)
33 
34 #else
35 
36 #include <stdlib.h>
37 
38 #ifndef SEEK_SET
39 #define SEEK_SET 0
40 #define SEEK_CUR 1
41 #define SEEK_END 2
42 #endif
43 
44 #define far
45 
46 #endif
47 
48 #define get_c fgetc
49 #define put_c fputc
50 
51 typedef unsigned long zlong;
52 
53 /*
54  * This is used only by save_quetzal. It probably should be allocated
55  * dynamically rather than statically.
56  */
57 static zword frames[STACK_SIZE / 4 + 1];
58 
59 /*
60  * ID types.
61  */
62 #define makeid(a,b,c,d) ((zlong) (((zlong)(a)<<24) | ((zlong)(b)<<16) | ((zlong)(c)<<8) | (zlong)(d)))
63 
64 #define ID_FORM makeid ('F','O','R','M')
65 #define ID_IFZS makeid ('I','F','Z','S')
66 #define ID_IFhd makeid ('I','F','h','d')
67 #define ID_UMem makeid ('U','M','e','m')
68 #define ID_CMem makeid ('C','M','e','m')
69 #define ID_Stks makeid ('S','t','k','s')
70 #define ID_ANNO makeid ('A','N','N','O')
71 
72 /*
73  * Various parsing states within restoration.
74  */
75 #define GOT_HEADER	0x01
76 #define GOT_STACK	0x02
77 #define GOT_MEMORY	0x04
78 #define GOT_NONE	0x00
79 #define GOT_ALL		0x07
80 #define GOT_ERROR	0x80
81 
82 /*
83  * Macros used to write the files.
84  */
85 #define write_byte(fp,b) (put_c (b, fp) != EOF)
86 #define write_bytx(fp,b) write_byte (fp, (b) & 0xFF)
87 #define write_word(fp,w) \
88 	(write_bytx (fp, (w) >>  8) && write_bytx (fp, (w)))
89 #define write_long(fp,l) \
90 	(write_bytx (fp, (l) >> 24) && write_bytx (fp, (l) >> 16) && \
91 	write_bytx (fp, (l) >>  8) && write_bytx (fp, (l)))
92 #define write_chnk(fp,id,len) \
93 	(write_long (fp, (id))      && write_long (fp, (len)))
94 #define write_run(fp,run) \
95 	(write_byte (fp, 0)         && write_byte (fp, (run)))
96 
97 
98 /* Read one word from file; return TRUE if OK. */
read_word(FILE * f,zword * result)99 static bool read_word(FILE * f, zword * result)
100 {
101 	int a, b;
102 
103 	if ((a = get_c(f)) == EOF)
104 		return FALSE;
105 	if ((b = get_c(f)) == EOF)
106 		return FALSE;
107 
108 	*result = ((zword) a << 8) | (zword) b;
109 	return TRUE;
110 }
111 
112 
113 /* Read one long from file; return TRUE if OK. */
read_long(FILE * f,zlong * result)114 static bool read_long(FILE * f, zlong * result)
115 {
116 	int a, b, c, d;
117 
118 	if ((a = get_c(f)) == EOF)
119 		return FALSE;
120 	if ((b = get_c(f)) == EOF)
121 		return FALSE;
122 	if ((c = get_c(f)) == EOF)
123 		return FALSE;
124 	if ((d = get_c(f)) == EOF)
125 		return FALSE;
126 
127 	/*
128 	 * When optimization is turned on in TurboC, the next to most
129 	 * significant byte seems to leak into the least significant byte.
130 	 * Use a mask to prevent this from happening.
131 	 * There's no harm in letting all ports see this.
132 	 */
133 	*result = ((zlong) a << 24) | (0x00ff0000UL &((zlong) b << 16)) |
134 	    ((zlong) c << 8) | (zlong) d;
135 
136 	return TRUE;
137 }
138 
139 
140 /*
141  * Restore a saved game using Quetzal format. Return 2 if OK, 0 if an error
142  * occurred before any damage was done, -1 on a fatal error.
143  */
restore_quetzal(FILE * svf,FILE * stf)144 zword restore_quetzal(FILE * svf, FILE * stf)
145 {
146 	zlong ifzslen, currlen, tmpl;
147 	zlong pc;
148 	zword i, tmpw;
149 	zword fatal = 0;	/* Set to -1 when errors must be fatal. */
150 	zbyte skip, progress = GOT_NONE;
151 	int x, y;
152 
153 	/* Check it's really an `IFZS' file. */
154 	if (!read_long(svf, &tmpl)
155 	    || !read_long(svf, &ifzslen)
156 	    || !read_long(svf, &currlen))
157 		return 0;
158 	if (tmpl != ID_FORM || currlen != ID_IFZS) {
159 		print_string("This is not a saved game file!\n");
160 		return 0;
161 	}
162 	if ((ifzslen & 1) || ifzslen < 4)	/* Sanity checks. */
163 		return 0;
164 	ifzslen -= 4;
165 
166 	/* Read each chunk and process it. */
167 	while (ifzslen > 0) {
168 		/* Read chunk header. */
169 		if (ifzslen < 8)	/* Couldn't contain a chunk. */
170 			return 0;
171 		if (!read_long(svf, &tmpl)
172 		    || !read_long(svf, &currlen))
173 			return 0;
174 		ifzslen -= 8;	/* Reduce remaining by size of header. */
175 
176 		/* Handle chunk body. */
177 		if (ifzslen < currlen)	/* Chunk goes past EOF?! */
178 			return 0;
179 		skip = currlen & 1;
180 		ifzslen -= currlen + (zlong) skip;
181 
182 		switch (tmpl) {
183 			/* `IFhd' header chunk; must be first in file. */
184 		case ID_IFhd:
185 			if (progress & GOT_HEADER) {
186 				print_string
187 				    ("Save file has two IFZS chunks!\n");
188 				return fatal;
189 			}
190 			progress |= GOT_HEADER;
191 			if (currlen < 13 || !read_word(svf, &tmpw))
192 				return fatal;
193 			if (tmpw != z_header.release)
194 				progress = GOT_ERROR;
195 
196 			for (i = H_SERIAL; i < H_SERIAL + 6; ++i) {
197 				if ((x = get_c(svf)) == EOF)
198 					return fatal;
199 				if (x != zmp[i])
200 					progress = GOT_ERROR;
201 			}
202 
203 			if (!read_word(svf, &tmpw))
204 				return fatal;
205 			if (tmpw != z_header.checksum)
206 				progress = GOT_ERROR;
207 
208 			if (progress & GOT_ERROR) {
209 				print_string
210 				    ("File was not saved from this story!\n");
211 				return fatal;
212 			}
213 			if ((x = get_c(svf)) == EOF)
214 				return fatal;
215 			pc = (zlong) x << 16;
216 			if ((x = get_c(svf)) == EOF)
217 				return fatal;
218 			pc |= (zlong) x << 8;
219 			if ((x = get_c(svf)) == EOF)
220 				return fatal;
221 			pc |= (zlong) x;
222 			fatal = -1;	/* Setting PC means errors must be fatal. */
223 			SET_PC(pc);
224 
225 			for (i = 13; i < currlen; ++i)
226 				(void)get_c(svf);	/* Skip rest of chunk. */
227 			break;
228 			/* `Stks' stacks chunk; restoring this is quite complex. ;) */
229 		case ID_Stks:
230 			if (progress & GOT_STACK) {
231 				print_string
232 				    ("File contains two stack chunks!\n");
233 				break;
234 			}
235 			progress |= GOT_STACK;
236 
237 			fatal = -1;	/* Setting SP means errors must be fatal. */
238 			sp = stack + STACK_SIZE;
239 
240 			/*
241 			 * All versions other than V6 may use evaluation stack outside
242 			 * any function context. As a result a faked function context
243 			 * will be present in the file here. We skip this context, but
244 			 * load the associated stack onto the stack proper...
245 			 */
246 			if (z_header.version != V6) {
247 				if (currlen < 8)
248 					return fatal;
249 				for (i = 0; i < 6; ++i)
250 					if (get_c(svf) != 0)
251 						return fatal;
252 				if (!read_word(svf, &tmpw))
253 					return fatal;
254 				if (tmpw > STACK_SIZE) {
255 					print_string
256 					    ("Save-file has too much stack (and I can't cope).\n");
257 					return fatal;
258 				}
259 				currlen -= 8;
260 				if (currlen < tmpw * 2)
261 					return fatal;
262 				for (i = 0; i < tmpw; ++i)
263 					if (!read_word(svf, --sp))
264 						return fatal;
265 				currlen -= tmpw * 2;
266 			}
267 
268 			/* We now proceed to load the main block of stack frames. */
269 			for (fp = stack + STACK_SIZE, frame_count = 0;
270 			     currlen > 0; currlen -= 8, ++frame_count) {
271 				if (currlen < 8)
272 					return fatal;
273 				if (sp - stack < 4) {	/* No space for frame. */
274 					print_string
275 					    ("Save-file has too much stack (and I can't cope).\n");
276 					return fatal;
277 				}
278 
279 				/* Read PC, procedure flag and formal param count. */
280 				if (!read_long(svf, &tmpl))
281 					return fatal;
282 				y = (int)(tmpl & 0x0F);	/* Number of formals. */
283 				tmpw = y << 8;
284 
285 				/* Read result variable. */
286 				if ((x = get_c(svf)) == EOF)
287 					return fatal;
288 
289 				/* Check the procedure flag... */
290 				if (tmpl & 0x10) {
291 					tmpw |= 0x1000;	/* It's a procedure. */
292 					tmpl >>= 8;	/* Shift to get PC value. */
293 				} else {
294 					/* Functions have type 0, so no need to or anything. */
295 					tmpl >>= 8;	/* Shift to get PC value. */
296 					--tmpl;	/* Point at result byte. */
297 					/* Sanity check on result variable... */
298 #ifdef MSDOS_16BIT
299 					if (tmpl > 0xffffL) {
300 					    zbyte far *zmp2;
301 					    zmp2 = MK_FP(FP_SEG(zmp) + (unsigned)(tmpl >> 16) * 0x1000,
302 							 FP_OFF(zmp));
303 					    x -= zmp2[tmpl & 0xffff];
304 					} else {
305 					    x -= zmp[tmpl];
306 					}
307 					if (x != 0) {
308 #else
309 					if (zmp[tmpl] != (zbyte) x) {
310 #endif
311 						print_string
312 						    ("Save-file has wrong variable number on stack (possibly wrong game version?)\n");
313 						return fatal;
314 					}
315 				}
316 				*--sp = (zword) (tmpl >> 9);	/* High part of PC */
317 				*--sp = (zword) (tmpl & 0x1FF);	/* Low part of PC */
318 				*--sp = (zword) (fp - stack - 1);	/* FP */
319 
320 				/* Read and process argument mask. */
321 				if ((x = get_c(svf)) == EOF)
322 					return fatal;
323 				++x;	/* Should now be a power of 2 */
324 				for (i = 0; i < 8; ++i)
325 					if (x & (1 << i))
326 						break;
327 				if (x ^ (1 << i)) {	/* Not a power of 2 */
328 					print_string
329 					    ("Save-file uses incomplete argument lists (which I can't handle)\n");
330 					return fatal;
331 				}
332 				*--sp = tmpw | i;
333 				fp = sp;	/* FP for next frame. */
334 
335 				/* Read amount of eval stack used. */
336 				if (!read_word(svf, &tmpw))
337 					return fatal;
338 
339 				tmpw += y;	/* Amount of stack + number of locals. */
340 				if (sp - stack <= tmpw) {
341 					print_string
342 					    ("Save-file has too much stack (and I can't cope).\n");
343 					return fatal;
344 				}
345 				if (currlen < tmpw * 2)
346 					return fatal;
347 				for (i = 0; i < tmpw; ++i)
348 					if (!read_word(svf, --sp))
349 						return fatal;
350 				currlen -= tmpw * 2;
351 			}
352 			/* End of `Stks' processing... */
353 			break;
354 			/* Any more special chunk types must go in HERE or ABOVE. */
355 			/* `CMem' compressed memory chunk; uncompress it. */
356 		case ID_CMem:
357 			if (!(progress & GOT_MEMORY)) {	/* Don't complain if two. */
358 				(void)os_storyfile_seek(stf, 0, SEEK_SET);
359 				i = 0;	/* Bytes written to data area. */
360 				for (; currlen > 0; --currlen) {
361 					if ((x = get_c(svf)) == EOF)
362 						return fatal;
363 					if (x == 0) {	/* Start run. */
364 						/* Check for bogus run. */
365 						if (currlen < 2) {
366 							print_string
367 							    ("File contains bogus `CMem' chunk.\n");
368 							for (; currlen > 0;
369 							     --currlen)
370 								(void)get_c(svf);	/* Skip rest. */
371 							currlen = 1;
372 							i = 0xFFFF;
373 							break;	/* Keep going; may be a `UMem' too. */
374 						}
375 						/* Copy story file to memory during the run. */
376 						--currlen;
377 						if ((x = get_c(svf)) == EOF)
378 							return fatal;
379 						for (;
380 						     x >= 0
381 						     && i < z_header.dynamic_size;
382 						     --x, ++i)
383 							if ((y =
384 							     get_c(stf)) == EOF)
385 								return fatal;
386 							else
387 								zmp[i] =
388 								    (zbyte) y;
389 					} else {	/* Not a run. */
390 					if ((y = get_c(stf)) == EOF)
391 						return fatal;
392 					zmp[i] = (zbyte) (x ^ y);
393 					++i;
394 					}
395 					/* Make sure we don't load too much. */
396 					if (i > z_header.dynamic_size) {
397 						print_string
398 						    ("warning: `CMem' chunk too long!\n");
399 						for (; currlen > 1; --currlen)
400 							(void)get_c(svf);	/* Skip rest. */
401 						break;	/* Keep going; there may be a `UMem' too. */
402 					}
403 				}
404 				/* If chunk is short, assume a run. */
405 				for (; i < z_header.dynamic_size; ++i)
406 					if ((y = get_c(stf)) == EOF)
407 						return fatal;
408 					else
409 						zmp[i] = (zbyte) y;
410 				if (currlen == 0)
411 					progress |= GOT_MEMORY;	/* Only if succeeded. */
412 				break;
413 			}
414 			/* Already GOT_MEMORY */
415 			(void)fseek(svf, currlen, SEEK_CUR);	/* Skip chunk. */
416 			break;
417 			/* `UMem' uncompressed memory chunk; load it. */
418 		case ID_UMem:
419 			if (!(progress & GOT_MEMORY)) {	/* Don't complain if two. */
420 				/* Must be exactly the right size. */
421 				if (currlen == z_header.dynamic_size) {
422 					if (fread(zmp, currlen, 1, svf) == 1) {
423 						progress |= GOT_MEMORY;	/* Only on success. */
424 						break;
425 					}
426 				} else
427 					/* actually handle the problem outside if statement by skipping chunk. */
428 					print_string
429 					    ("`UMem' chunk wrong size!\n");
430 			}
431 			/* Already GOT_MEMORY */
432 			(void)fseek(svf, currlen, SEEK_CUR);	/* Skip chunk. */
433 			break;
434 			/* Unrecognised chunk type; skip it. */
435 		default:
436 			(void)fseek(svf, currlen, SEEK_CUR);	/* Skip chunk. */
437 			break;
438 		}
439 		if (skip)
440 			(void)get_c(svf);	/* Skip pad byte. */
441 	}
442 
443 	/*
444 	 * We've reached the end of the file. For the restoration to have been a
445 	 * success, we must have had one of each of the required chunks.
446 	 */
447 	if (!(progress & GOT_HEADER))
448 		print_string
449 		    ("error: no valid header (`IFhd') chunk in file.\n");
450 	if (!(progress & GOT_STACK))
451 		print_string("error: no valid stack (`Stks') chunk in file.\n");
452 	if (!(progress & GOT_MEMORY))
453 		print_string
454 		    ("error: no valid memory (`CMem' or `UMem') chunk in file.\n");
455 
456 	return (progress == GOT_ALL ? 2 : fatal);
457 }
458 
459 
460 /*
461  * Save a game using Quetzal format. Return 1 if OK, 0 if failed.
462  */
463 zword save_quetzal(FILE * svf, FILE * stf)
464 {
465 	zlong ifzslen = 0, cmemlen = 0, stkslen = 0;
466 	zlong pc;
467 	zword i, j, n;
468 	zword nvars, nargs, nstk, *p;
469 	zbyte var;
470 	long cmempos, stkspos;
471 	int c;
472 
473 	/* Write `IFZS' header. */
474 	if (!write_chnk(svf, ID_FORM, 0))
475 		return 0;
476 	if (!write_long(svf, ID_IFZS))
477 		return 0;
478 
479 	/* Write `IFhd' chunk. */
480 	GET_PC(pc);
481 	if (!write_chnk(svf, ID_IFhd, 13))
482 		return 0;
483 	if (!write_word(svf, z_header.release))
484 		return 0;
485 	for (i = H_SERIAL; i < H_SERIAL + 6; ++i)
486 		if (!write_byte(svf, zmp[i]))
487 			return 0;
488 	if (!write_word(svf, z_header.checksum))
489 		return 0;
490 	if (!write_long(svf, pc << 8))	/* Includes pad. */
491 		return 0;
492 
493 	/* Write `CMem' chunk. */
494 	if ((cmempos = ftell(svf)) < 0)
495 		return 0;
496 	if (!write_chnk(svf, ID_CMem, 0))
497 		return 0;
498 	(void)os_storyfile_seek(stf, 0, SEEK_SET);
499 	/* j holds current run length. */
500 	for (i = 0, j = 0, cmemlen = 0; i < z_header.dynamic_size; ++i) {
501 		if ((c = get_c(stf)) == EOF)
502 			return 0;
503 		c ^= (int)zmp[i];
504 		if (c == 0)
505 			++j;	/* It's a run of equal bytes. */
506 		else {
507 			/* Write out any run there may be. */
508 			if (j > 0) {
509 				for (; j > 0x100; j -= 0x100) {
510 					if (!write_run(svf, 0xFF))
511 						return 0;
512 					cmemlen += 2;
513 				}
514 				if (!write_run(svf, j - 1))
515 					return 0;
516 				cmemlen += 2;
517 				j = 0;
518 			}
519 			/* Any runs are now written. Write this (nonzero) byte. */
520 			if (!write_byte(svf, (zbyte) c))
521 				return 0;
522 			++cmemlen;
523 		}
524 	}
525 
526 	/*
527 	 * Reached end of dynamic memory. We ignore any unwritten run there may be
528 	 * at this point.
529 	 */
530 	if (cmemlen & 1)	/* Chunk length must be even. */
531 		if (!write_byte(svf, 0))
532 			return 0;
533 
534 	/* Write `Stks' chunk. You are not expected to understand this. ;) */
535 	if ((stkspos = ftell(svf)) < 0)
536 		return 0;
537 	if (!write_chnk(svf, ID_Stks, 0))
538 		return 0;
539 
540 	/*
541 	 * We construct a list of frame indices, most recent first, in `frames'.
542 	 * These indices are the offsets into the `stack' array of the word before
543 	 * the first word pushed in each frame.
544 	 */
545 	frames[0] = sp - stack;	/* The frame we'd get by doing a call now. */
546 	for (i = fp - stack + 4, n = 0; i < STACK_SIZE + 4;
547 	     i = stack[i - 3] + 5)
548 		frames[++n] = i;
549 
550 	/*
551 	 * All versions other than V6 can use evaluation stack outside a function
552 	 * context. We write a faked stack frame (most fields zero) to cater for
553 	 * this.
554 	 */
555 	if (z_header.version != V6) {
556 		for (i = 0; i < 6; ++i)
557 			if (!write_byte(svf, 0))
558 				return 0;
559 		nstk = STACK_SIZE - frames[n];
560 		if (!write_word(svf, nstk))
561 			return 0;
562 		for (j = STACK_SIZE - 1; j >= frames[n]; --j)
563 			if (!write_word(svf, stack[j]))
564 				return 0;
565 		stkslen = 8 + 2 * nstk;
566 	}
567 
568 	/* Write out the rest of the stack frames. */
569 	for (i = n; i > 0; --i) {
570 		p = stack + frames[i] - 4;	/* Points to call frame. */
571 		nvars = (p[0] & 0x0F00) >> 8;
572 		nargs = p[0] & 0x00FF;
573 		nstk = frames[i] - frames[i - 1] - nvars - 4;
574 		pc = ((zlong) p[3] << 9) | p[2];
575 
576 		switch (p[0] & 0xF000) {	/* Check type of call. */
577 		case 0x0000:	/* Function. */
578 #ifdef MSDOS_16BIT
579 			if (pc > 0xffffL) {
580 			    zbyte far *zmp2;
581 			    zmp2 = MK_FP(FP_SEG(zmp) + (unsigned)(pc >> 16) * 0x1000,
582 				         FP_OFF(zmp));
583 			    var = zmp2[pc & 0xffff];
584 			} else {
585 			    var = zmp[pc];
586 			}
587 #else
588 			var = zmp[pc];
589 #endif
590 			pc = ((pc + 1) << 8) | nvars;
591 			break;
592 		case 0x1000:	/* Procedure. */
593 			var = 0;
594 			pc = (pc << 8) | 0x10 | nvars;	/* Set procedure flag. */
595 			break;
596 			/* case 0x2000: */
597 		default:
598 			runtime_error(ERR_SAVE_IN_INTER);
599 			return 0;
600 		}
601 		if (nargs != 0)
602 			nargs = (1 << nargs) - 1;	/* Make args into bitmap. */
603 
604 		/* Write the main part of the frame... */
605 		if (!write_long(svf, pc)
606 		    || !write_byte(svf, var)
607 		    || !write_byte(svf, nargs)
608 		    || !write_word(svf, nstk))
609 			return 0;
610 
611 		/* Write the variables and eval stack. */
612 		for (j = 0, --p; j < nvars + nstk; ++j, --p)
613 			if (!write_word(svf, *p))
614 				return 0;
615 
616 		/* Calculate length written thus far. */
617 		stkslen += 8 + 2 * (nvars + nstk);
618 	}
619 	/* Fill in variable chunk lengths. */
620 	ifzslen = 3 * 8 + 4 + 14 + cmemlen + stkslen;
621 	if (cmemlen & 1)
622 		++ifzslen;
623 	(void)fseek(svf, 4, SEEK_SET);
624 	if (!write_long(svf, ifzslen))
625 		return 0;
626 	(void)fseek(svf, cmempos + 4, SEEK_SET);
627 	if (!write_long(svf, cmemlen))
628 		return 0;
629 	(void)fseek(svf, stkspos + 4, SEEK_SET);
630 	if (!write_long(svf, stkslen))
631 		return 0;
632 
633 	/* After all that, still nothing went wrong! */
634 	return 1;
635 }
636