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