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