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