1 /* quetzal.c
2  *
3  * routines to handle QUETZAL save format
4  */
5 
6 #include <stdio.h>
7 #include <unistd.h>
8 #include "ztypes.h"
9 
10 /* You may want to define these as getc and putc, but then the code gets
11  * quite big (especially for put_c).
12  */
13 #define get_c fgetc
14 #define put_c fputc
15 
16 #ifndef SEEK_CUR
17 #define SEEK_CUR 1
18 #endif
19 
20 #if defined(USE_QUETZAL)    /* don't compile anything otherwise */
21 
22 typedef unsigned long ul_t;
23 
24 #define makeid(a,b,c,d) ((ul_t)(((a)<<24) | ((b)<<16) | ((c)<<8) | (d)))
25 
26 /* IDs of chunks we understand */
27 #define ID_FORM makeid('F','O','R','M')
28 #define ID_IFZS makeid('I','F','Z','S')
29 #define ID_IFhd makeid('I','F','h','d')
30 #define ID_UMem makeid('U','M','e','m')
31 #define ID_CMem makeid('C','M','e','m')
32 #define ID_Stks makeid('S','t','k','s')
33 #define ID_ANNO makeid('A','N','N','O')
34 
35 /* macros to write QUETZAL files */
36 #define write_byte(fp,b) (put_c (b,fp) != EOF)
37 #define write_bytx(fp,b) write_byte(fp,(b) & 0xFF)
38 #define write_word(fp,w) \
39     (write_bytx(fp,(w)>> 8) && write_bytx(fp,(w)))
40 #define write_long(fp,l) \
41     (write_bytx(fp,(l)>>24) && write_bytx(fp,(l)>>16) && \
42      write_bytx(fp,(l)>> 8) && write_bytx(fp,(l)))
43 #define write_chnk(fp,id,len) \
44     (write_long(fp,id)      && write_long(fp,len))
45 #define write_run(fp,run) \
46     (write_byte(fp,0)       && write_byte(fp,(run)))
47 
48 /* save_quetzal
49  *
50  * attempt to save game in QUETZAL format; return TRUE on success
51  */
52 
53 #if defined(__STDC__)
save_quetzal(FILE * sfp,FILE * gfp,long zoffset)54 int save_quetzal (FILE *sfp, FILE *gfp, long zoffset)
55 #else
56 int save_quetzal (sfp,gfp,zoffset)
57     FILE *sfp,*gfp;
58     long zoffset;
59 #endif
60 {
61     ul_t ifzslen = 0, cmemlen = 0, stkslen = 0, tmp_pc;
62     int c;
63     zword_t i, j, n, init_fp, tmp_fp, nstk, nvars, args;
64     zword_t frames[STACK_SIZE/4+1];
65     zbyte_t var;
66     long cmempos,stkspos;
67 
68     /* write IFZS header */
69     if (!write_chnk(sfp,ID_FORM,0))            return FALSE;
70     if (!write_long(sfp,ID_IFZS))            return FALSE;
71 
72     /* write IFhd chunk */
73     if (!write_chnk(sfp,ID_IFhd,13))            return FALSE;
74     if (!write_word(sfp,h_version))            return FALSE;
75     for (i=0; i<6; ++i)
76         if(!write_byte(sfp,get_byte(H_RELEASE_DATE+i)))    return FALSE;
77     if (!write_word(sfp,h_checksum))            return FALSE;
78     if (!write_long(sfp,pc<<8)) /* includes pad byte */    return FALSE;
79 
80     /* write CMem chunk */
81     /* j is current run length */
82     if ((cmempos = ftell(sfp)) < 0)            return FALSE;
83     if (!write_chnk(sfp,ID_CMem,0))            return FALSE;
84     fseek (gfp, zoffset, SEEK_SET);
85     for (i=0, j=0, cmemlen=0; i < h_restart_size; ++i) {
86         if ((c = get_c (gfp)) == EOF)            return FALSE;
87         c ^= get_byte (i);
88         if(c == 0)
89             ++j;
90         else {
91             /* write any run there may be */
92             if (j > 0) {
93                 for (; j > 0x100; j -= 0x100) {
94                     if (!write_run(sfp,0xFF))        return FALSE;
95                     cmemlen += 2;
96                 }
97                 if (!write_run(sfp,j-1))        return FALSE;
98                 cmemlen += 2;
99                 j = 0;
100             }
101             /* write this byte */
102             if (!write_byte(sfp,c))            return FALSE;
103             ++cmemlen;
104         }
105     }
106     /* there may be a run here, which we ignore */
107     if (cmemlen & 1)    /* chunk length must be even */
108         if (!write_byte(sfp,0))                return FALSE;
109 
110     /* write Stks chunk */
111     if ((stkspos = ftell(sfp)) < 0)            return FALSE;
112     if (!write_chnk(sfp,ID_Stks,0))            return FALSE;
113 
114     /* frames is a list of FPs, most recent first */
115     frames[0] = sp-5;    /* what FP would be if we did a call now */
116     for (init_fp=fp, n=0; init_fp <= STACK_SIZE-5; init_fp=stack[init_fp+2]) {
117 	frames[++n] = init_fp;
118     }
119     init_fp = frames[n] + 4;
120 
121     if (h_type != 6) {    /* write a dummy frame for stack used before first call */
122         for (i = 0; i < 6; ++i)
123             if (!write_byte(sfp,0))            return FALSE;
124         nstk = STACK_SIZE - 1 - init_fp;
125         if (!write_word(sfp,nstk))            return FALSE;
126         for(i = STACK_SIZE-1; i > init_fp; --i)
127             if (!write_word(sfp,stack[i]))        return FALSE;
128         stkslen = 8 + 2*nstk;
129         }
130     for (i=n; i>0; --i) {
131         /* write out one stack frame.
132          *
133          *  tmp_fp : FP when this frame was current
134          *  tmp_pc : PC on return from this frame, plus 000pvvvv
135          *  nvars  : number of local vars for this frame
136          *  args   : argument mask for this frame
137          *  nstk   : words of evaluation stack used for this frame
138          *  var    : variable to store result
139          */
140         tmp_fp = frames[i];
141         nvars  = (stack[tmp_fp+1] & VARS_MASK) >> VAR_SHIFT;
142         args   = stack[tmp_fp+1] & ARGS_MASK;
143         nstk   = tmp_fp - frames[i-1] - nvars - 4;
144         tmp_pc = stack[tmp_fp+3] + (ul_t) stack[tmp_fp+4]*PAGE_SIZE;
145         switch(stack[tmp_fp+1] & TYPE_MASK) {
146             case FUNCTION:
147                 var = read_data_byte (&tmp_pc);    /* also increments tmp_pc */
148                 tmp_pc = (tmp_pc << 8) | nvars;
149                 break;
150             case PROCEDURE:
151                 var = 0;
152                 tmp_pc = (tmp_pc << 8) | 0x10 | nvars; /* set procedure flag */
153                 break;
154          /* case ASYNC: */
155             default:
156                 output_line ("Illegal Z-machine operation: can't save while in interrupt.");
157                 return FALSE;
158             }
159         if (args != 0)
160             args = (1 << args)-1;    /* make args into bitmap */
161         if (!write_long(sfp,tmp_pc))            return FALSE;
162         if (!write_byte(sfp,var))            return FALSE;
163         if (!write_byte(sfp,args))            return FALSE;
164         if (!write_word(sfp,nstk))            return FALSE;
165         for (j=0; j<nvars+nstk; ++j, --tmp_fp)
166             if (!write_word (sfp, stack[tmp_fp]))       return FALSE;
167         stkslen += 8 + 2 * (nvars + nstk);
168     }
169 
170     /* fill in lengths for variable-sized chunks */
171     ifzslen = 3*8 + 4 + 14 + cmemlen + stkslen;
172     if (cmemlen & 1)
173         ++ifzslen;
174     (void) fseek (sfp,   (long)4, SEEK_SET);
175     if (!write_long(sfp,ifzslen))            return FALSE;
176     (void) fseek (sfp, cmempos+4, SEEK_SET);
177     if (!write_long(sfp,cmemlen))            return FALSE;
178     (void) fseek (sfp, stkspos+4, SEEK_SET);
179     if (!write_long(sfp,stkslen))            return FALSE;
180 
181     return TRUE;
182 }
183 
184 /* read_word
185  *
186  * attempt to read a word; return TRUE on success
187  */
188 
189 #if defined(__STDC__)
read_word(FILE * fp,zword_t * result)190 static int read_word (FILE *fp, zword_t *result)
191 #else
192 static int read_word (fp,result)
193     FILE *fp;
194     zword_t *result;
195 #endif
196 {
197     int a,b;
198     if ((a = get_c (fp)) == EOF) return FALSE;
199     if ((b = get_c (fp)) == EOF) return FALSE;
200     *result = ((zword_t) a << 8) | b;
201     return TRUE;
202 }
203 
204 /* read_long
205  *
206  * attempt to read a longword; return TRUE on success
207  */
208 
209 #if defined(__STDC__)
read_long(FILE * fp,ul_t * result)210 static int read_long(FILE *fp, ul_t *result)
211 #else
212 static int read_long(fp,result)
213     FILE *fp;
214     ul_t *result;
215 #endif
216 {
217     int a,b,c,d;
218     if ((a = get_c (fp)) == EOF) return FALSE;
219     if ((b = get_c (fp)) == EOF) return FALSE;
220     if ((c = get_c (fp)) == EOF) return FALSE;
221     if ((d = get_c (fp)) == EOF) return FALSE;
222     *result = ((ul_t) a << 24) | ((ul_t) b << 16) | ((ul_t) c << 8) | d;
223     return TRUE;
224 }
225 
226 /* restore_quetzal
227  *
228  * attempt to restore game in QUETZAL format; return TRUE on success
229  */
230 
231 #define GOT_HEADER    0x01
232 #define GOT_STACK    0x02
233 #define GOT_MEMORY    0x04
234 #define GOT_NONE    0x00
235 #define GOT_ALL        0x07
236 #define GOT_ERROR    0x80
237 
238 #if defined(__STDC__)
restore_quetzal(FILE * sfp,FILE * gfp,long zoffset)239 int restore_quetzal (FILE *sfp, FILE *gfp, long zoffset)
240 #else
241 int restore_quetzal (sfp,gfp,zoffset)
242     FILE *sfp,*gfp;
243     long zoffset;
244 #endif
245 {
246     ul_t ifzslen, currlen, tmpl;
247     zword_t i, tmpw;
248     zbyte_t skip, progress = GOT_NONE;
249     int x, y;
250 
251     /* check for IFZS file */
252     if (!read_long (sfp,&tmpl) || !read_long (sfp,&ifzslen))    return FALSE;
253     if (!read_long (sfp,&currlen))                return FALSE;
254     if (tmpl != ID_FORM || currlen != ID_IFZS) {
255         output_line("This is not a saved game file!");
256         return FALSE;
257     }
258     if ((ifzslen & 1) || ifzslen<4)                return FALSE;
259     ifzslen -= 4;
260 
261     /* read a chunk and process it */
262     while (ifzslen > 0) {
263         /* read chunk header */
264         if (ifzslen < 8)                    return FALSE;
265         if (!read_long(sfp,&tmpl) || !read_long(sfp,&currlen))    return FALSE;
266         ifzslen -= 8;
267 
268         /* body of chunk */
269         if (ifzslen < currlen)                    return FALSE;
270         skip = currlen & 1;
271         ifzslen -= currlen+skip;
272         switch (tmpl) {
273             case ID_IFhd:
274                 if (progress & GOT_HEADER) {
275                     output_line ("Save file has two IFhd chunks!");
276                     return FALSE;
277                 }
278                 progress |= GOT_HEADER;
279                 if (currlen < 13 || !read_word (sfp,&i))    return FALSE;
280                 if (i != h_version)
281                 progress = GOT_ERROR;
282                 for (i=H_RELEASE_DATE; i<H_RELEASE_DATE+6; ++i) {
283                     if ((x = get_c (sfp)) == EOF)        return FALSE;
284                     if (x != get_byte (i))
285                         progress = GOT_ERROR;
286                 }
287                 if (!read_word (sfp,&i))            return FALSE;
288                 if (i != h_checksum)
289                     progress = GOT_ERROR;
290                 if (progress == GOT_ERROR) {
291                     output_line ("File was not saved from this story!");
292                     return FALSE;
293                 }
294                 if ((x = get_c (sfp)) == EOF)            return FALSE;
295                 pc = x << 16;
296                 if ((x = get_c (sfp)) == EOF)            return FALSE;
297                 pc |= x << 8;
298                 if ((x = get_c (sfp)) == EOF)            return FALSE;
299                 pc |= x;
300                 for (i=13; i<currlen; ++i)
301                     (void) get_c (sfp);    /* skip rest of chunk */
302                 break;
303             case ID_Stks:
304                 if (progress & GOT_STACK) {
305                     output_line ("File contains two stack chunks!");
306                     break;
307                 }
308                 progress |= GOT_STACK;
309                 sp = STACK_SIZE;
310                 if (h_type != 6) {
311                     /* dummy stack frame for stack used before call */
312                     if (currlen < 8)                return FALSE;
313                     for (i=0; i<6; ++i)
314                         if (get_c (sfp) != 0)            return FALSE;
315                     if (!read_word (sfp,&tmpw))            return FALSE;
316                     currlen -= 8;
317                     if (currlen < tmpw*2)            return FALSE;
318                     for (i=0; i<tmpw; ++i)
319                         if (!read_word(sfp,stack+(--sp)))    return FALSE;
320                     currlen -= tmpw*2;
321                     }
322                 for (fp=STACK_SIZE-1, frame_count=0; currlen > 0; currlen-=8) {
323                     if (currlen < 8)                return FALSE;
324                     if (sp<4) {
325                         output_line ("error: this save-file has too much stack, and I can't cope.");
326                         return FALSE;
327                     }
328                     /* read PC, procedure flag, and arg count */
329                     if (!read_long(sfp,&tmpl))            return FALSE;
330                     y = (zword_t) tmpl & 0x0F;
331                     tmpw = y << VAR_SHIFT;    /* number of variables */
332                     /* read result variable  */
333                     if ((x = get_c (sfp)) == EOF)        return FALSE;
334 
335                     if (tmpl & 0x10) {
336                         tmpw |= PROCEDURE;
337                         tmpl >>= 8;
338                         }
339                     else {
340                         tmpw |= FUNCTION;
341                         tmpl >>= 8;
342                         --tmpl;
343                         /* sanity check on result variable */
344                         if (read_data_byte (&tmpl) != (zbyte_t) x) {
345                             output_line ("error: wrong variable number on stack (wrong story file?).");
346                             return FALSE;
347                         }
348                         --tmpl;    /* read_data_byte increments it */
349                     }
350                     stack[--sp] = (zword_t) (tmpl / PAGE_SIZE);
351                     stack[--sp] = (zword_t) (tmpl % PAGE_SIZE);
352                     stack[--sp] = fp;
353 
354                     if ((x = get_c (sfp)) == EOF)        return FALSE;
355                     ++x;    /* hopefully x now contains a single set bit */
356 
357                     for (i=0; i<8; ++i)
358                         if (x & (1<<i)) break;
359                     if (x ^ (1<<i))    /* if more than 1 bit set */ {
360                         output_line ("error: this game uses incomplete argument lists (which I can't handle).");
361                         return FALSE;
362                     }
363                     tmpw |= i;
364                     stack[--sp] = tmpw;
365                     fp = sp - 1;    /* FP for next frame */
366                     if (!read_word(sfp,&tmpw))            return FALSE;
367                     tmpw += y;    /* local vars plus eval stack used */
368                     if (tmpw>=sp) {
369                         output_line ("error: this save-file uses more stack than I can cope with.");
370                         return FALSE;
371                     }
372                     if (currlen < tmpw*2)            return FALSE;
373                     for (i=0; i<tmpw; ++i)
374                         if (!read_word(sfp,stack+(--sp)))    return FALSE;
375                     currlen -= tmpw*2;
376                     }
377                 break;
378 	    /* case ID_ANNO:
379                 set_format_mode (ON);
380                 for (; currlen > 0; --currlen) {
381 		    if ((x = get_c (sfp)) == EOF)        return FALSE;
382 		    else write_char (x);
383 		}
384 		write_char ((char) 13);
385                 break; */
386             case ID_CMem:
387                 if (!(progress & GOT_MEMORY)) {
388 		    fseek (gfp, zoffset, SEEK_SET);
389                     i=0;    /* bytes written to data area */
390                     for (; currlen > 0; --currlen) {
391                         if ((x = get_c (sfp)) == EOF)        return FALSE;
392                         if (x == 0) /* start run */ {
393                             if (currlen < 2) {
394                                 output_line ("File contains bogus CMem chunk");
395                                 for (; currlen > 0; --currlen)
396                                     (void) get_c (sfp);    /* skip rest */
397                                 currlen = 1;
398                                 i = 0xFFFF;
399                                 break;    /* keep going in case there's a UMem */
400                             }
401                             --currlen;
402                             if ((x = get_c (sfp)) == EOF)    return FALSE;
403                             for (; x>=0 && i<h_restart_size; --x, ++i)
404                                 if ((y = get_c (gfp)) == EOF)    return FALSE;
405                                 else set_byte (i,y);
406                         }
407                         else        /* not a run */ {
408                             if ((y = get_c (gfp)) == EOF)        return FALSE;
409                             set_byte (i,x^y);
410                             ++i;
411                         }
412                         if (i>h_restart_size) {
413                             output_line ("warning: CMem chunk too long!");
414                             for (; currlen > 1; --currlen)
415                                 (void) get_c (sfp);        /* skip rest */
416                             break;        /* keep going in case there's a UMem */
417                         }
418                     }
419                     /* if chunk is short, assume a run */
420                     for (; i<h_restart_size; ++i)
421                         if ((y = get_c (gfp)) == EOF)        return FALSE;
422                         else set_byte (i,y);
423                     if (currlen == 0)
424                         progress |= GOT_MEMORY;    /* only if succeeded */
425                     break;
426                     }
427                 /* Fall thru (to default) if already got memory */
428             case ID_UMem:
429                 if (!(progress & GOT_MEMORY)) {
430                     if (currlen == h_restart_size) {
431                         if (fread(datap,h_restart_size,1,sfp)==1) {
432                             progress |= GOT_MEMORY;    /* only if succeeded */
433                             break;
434                         }
435                     }
436                     else output_line ("warning: UMem chunk wrong size!");
437                         /* and fall thru into default */
438                 }
439                 /* Fall thru (to default) if already got memory */
440             default:
441                 (void) fseek (sfp, currlen, SEEK_CUR);    /* skip chunk */
442                 break;
443         }
444         if (skip)
445             (void) get_c (sfp);    /* skip pad byte */
446     }
447 
448     if (!(progress & GOT_HEADER))
449         output_line ("error: no header chunk in file.");
450     if (!(progress & GOT_STACK))
451         output_line ("error: no stack chunk in file.");
452     if (!(progress & GOT_MEMORY))
453         output_line ("error: no memory chunk in file.");
454     return (progress == GOT_ALL);
455 }
456 
457 #endif    /* defined(USE_QUETZAL) */
458