Rebase to SVN, and do a bit of cleanup as well as minor fixes.
[kugel-rb.git] / apps / plugins / frotz / quetzal.c
bloba75ade856e70deba2646415079c0dc9cce7f8795
1 /* quetzal.c - Saving and restoring of Quetzal files.
2 * Written by Martin Frost <mdf@doc.ic.ac.uk>
4 * Changes for Rockbox copyright 2009 Torne Wuff
6 * This file is part of Frotz.
8 * Frotz is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * Frotz is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
23 #include "frotz.h"
25 #define far
27 #define get_c fgetc
28 #define put_c fputc
30 typedef unsigned long zlong;
33 * This is used only by save_quetzal. It probably should be allocated
34 * dynamically rather than statically.
37 static zword frames[STACK_SIZE/4+1];
40 * ID types.
43 #define makeid(a,b,c,d) ((zlong) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d)))
45 #define ID_FORM makeid ('F','O','R','M')
46 #define ID_IFZS makeid ('I','F','Z','S')
47 #define ID_IFhd makeid ('I','F','h','d')
48 #define ID_UMem makeid ('U','M','e','m')
49 #define ID_CMem makeid ('C','M','e','m')
50 #define ID_Stks makeid ('S','t','k','s')
51 #define ID_ANNO makeid ('A','N','N','O')
54 * Various parsing states within restoration.
57 #define GOT_HEADER 0x01
58 #define GOT_STACK 0x02
59 #define GOT_MEMORY 0x04
60 #define GOT_NONE 0x00
61 #define GOT_ALL 0x07
62 #define GOT_ERROR 0x80
65 * Macros used to write the files.
68 #define write_byte(fp,b) (put_c (b, fp) != EOF)
69 #define write_bytx(fp,b) write_byte (fp, (b) & 0xFF)
70 #define write_word(fp,w) \
71 (write_bytx (fp, (w) >> 8) && write_bytx (fp, (w)))
72 #define write_long(fp,l) \
73 (write_bytx (fp, (l) >> 24) && write_bytx (fp, (l) >> 16) && \
74 write_bytx (fp, (l) >> 8) && write_bytx (fp, (l)))
75 #define write_chnk(fp,id,len) \
76 (write_long (fp, (id)) && write_long (fp, (len)))
77 #define write_run(fp,run) \
78 (write_byte (fp, 0) && write_byte (fp, (run)))
80 /* Read one word from file; return TRUE if OK. */
81 static bool read_word (int f, zword *result)
83 int a, b;
85 if ((a = get_c (f)) == EOF) return FALSE;
86 if ((b = get_c (f)) == EOF) return FALSE;
88 *result = ((zword) a << 8) | (zword) b;
89 return TRUE;
92 /* Read one long from file; return TRUE if OK. */
93 static bool read_long (int f, zlong *result)
95 int a, b, c, d;
97 if ((a = get_c (f)) == EOF) return FALSE;
98 if ((b = get_c (f)) == EOF) return FALSE;
99 if ((c = get_c (f)) == EOF) return FALSE;
100 if ((d = get_c (f)) == EOF) return FALSE;
102 *result = ((zlong) a << 24) | ((zlong) b << 16) |
103 ((zlong) c << 8) | (zlong) d;
104 return TRUE;
108 * Restore a saved game using Quetzal format. Return 2 if OK, 0 if an error
109 * occurred before any damage was done, -1 on a fatal error.
112 zword restore_quetzal (int svf, int stf)
114 zlong ifzslen, currlen, tmpl;
115 zlong pc;
116 zword i, tmpw;
117 zword fatal = 0; /* Set to -1 when errors must be fatal. */
118 zbyte skip, progress = GOT_NONE;
119 int x, y;
121 /* Check it's really an `IFZS' file. */
122 if (!read_long (svf, &tmpl)
123 || !read_long (svf, &ifzslen)
124 || !read_long (svf, &currlen)) return 0;
125 if (tmpl != ID_FORM || currlen != ID_IFZS)
127 print_string ("This is not a saved game file!\n");
128 return 0;
130 if ((ifzslen & 1) || ifzslen<4) /* Sanity checks. */ return 0;
131 ifzslen -= 4;
133 /* Read each chunk and process it. */
134 while (ifzslen > 0)
136 /* Read chunk header. */
137 if (ifzslen < 8) /* Couldn't contain a chunk. */ return 0;
138 if (!read_long (svf, &tmpl)
139 || !read_long (svf, &currlen)) return 0;
140 ifzslen -= 8; /* Reduce remaining by size of header. */
142 /* Handle chunk body. */
143 if (ifzslen < currlen) /* Chunk goes past EOF?! */ return 0;
144 skip = currlen & 1;
145 ifzslen -= currlen + (zlong) skip;
147 switch (tmpl)
149 /* `IFhd' header chunk; must be first in file. */
150 case ID_IFhd:
151 if (progress & GOT_HEADER)
153 print_string ("Save file has two IFZS chunks!\n");
154 return fatal;
156 progress |= GOT_HEADER;
157 if (currlen < 13
158 || !read_word (svf, &tmpw)) return fatal;
159 if (tmpw != h_release)
160 progress = GOT_ERROR;
162 for (i=H_SERIAL; i<H_SERIAL+6; ++i)
164 if ((x = get_c (svf)) == EOF) return fatal;
165 if (x != zmp[i])
166 progress = GOT_ERROR;
169 if (!read_word (svf, &tmpw)) return fatal;
170 if (tmpw != h_checksum)
171 progress = GOT_ERROR;
173 if (progress & GOT_ERROR)
175 print_string ("File was not saved from this story!\n");
176 return fatal;
178 if ((x = get_c (svf)) == EOF) return fatal;
179 pc = (zlong) x << 16;
180 if ((x = get_c (svf)) == EOF) return fatal;
181 pc |= (zlong) x << 8;
182 if ((x = get_c (svf)) == EOF) return fatal;
183 pc |= (zlong) x;
184 fatal = -1; /* Setting PC means errors must be fatal. */
185 SET_PC (pc);
187 for (i=13; i<currlen; ++i)
188 (void) get_c (svf); /* Skip rest of chunk. */
189 break;
190 /* `Stks' stacks chunk; restoring this is quite complex. ;) */
191 case ID_Stks:
192 if (progress & GOT_STACK)
194 print_string ("File contains two stack chunks!\n");
195 break;
197 progress |= GOT_STACK;
199 fatal = -1; /* Setting SP means errors must be fatal. */
200 sp = stack + STACK_SIZE;
203 * All versions other than V6 may use evaluation stack outside
204 * any function context. As a result a faked function context
205 * will be present in the file here. We skip this context, but
206 * load the associated stack onto the stack proper...
208 if (h_version != V6)
210 if (currlen < 8) return fatal;
211 for (i=0; i<6; ++i)
212 if (get_c (svf) != 0) return fatal;
213 if (!read_word (svf, &tmpw)) return fatal;
214 if (tmpw > STACK_SIZE)
216 print_string ("Save-file has too much stack (and I can't cope).\n");
217 return fatal;
219 currlen -= 8;
220 if ((signed)currlen < tmpw*2) return fatal;
221 for (i=0; i<tmpw; ++i)
222 if (!read_word (svf, --sp)) return fatal;
223 currlen -= tmpw*2;
226 /* We now proceed to load the main block of stack frames. */
227 for (fp = stack+STACK_SIZE, frame_count = 0;
228 currlen > 0;
229 currlen -= 8, ++frame_count)
231 if (currlen < 8) return fatal;
232 if (sp - stack < 4) /* No space for frame. */
234 print_string ("Save-file has too much stack (and I can't cope).\n");
235 return fatal;
238 /* Read PC, procedure flag and formal param count. */
239 if (!read_long (svf, &tmpl)) return fatal;
240 y = (int) (tmpl & 0x0F); /* Number of formals. */
241 tmpw = y << 8;
243 /* Read result variable. */
244 if ((x = get_c (svf)) == EOF) return fatal;
246 /* Check the procedure flag... */
247 if (tmpl & 0x10)
249 tmpw |= 0x1000; /* It's a procedure. */
250 tmpl >>= 8; /* Shift to get PC value. */
252 else
254 /* Functions have type 0, so no need to or anything. */
255 tmpl >>= 8; /* Shift to get PC value. */
256 --tmpl; /* Point at result byte. */
257 /* Sanity check on result variable... */
258 if (zmp[tmpl] != (zbyte) x)
260 print_string ("Save-file has wrong variable number on stack (possibly wrong game version?)\n");
261 return fatal;
264 *--sp = (zword) (tmpl >> 9); /* High part of PC */
265 *--sp = (zword) (tmpl & 0x1FF); /* Low part of PC */
266 *--sp = (zword) (fp - stack - 1); /* FP */
268 /* Read and process argument mask. */
269 if ((x = get_c (svf)) == EOF) return fatal;
270 ++x; /* Should now be a power of 2 */
271 for (i=0; i<8; ++i)
272 if (x & (1<<i))
273 break;
274 if (x ^ (1<<i)) /* Not a power of 2 */
276 print_string ("Save-file uses incomplete argument lists (which I can't handle)\n");
277 return fatal;
279 *--sp = tmpw | i;
280 fp = sp; /* FP for next frame. */
282 /* Read amount of eval stack used. */
283 if (!read_word (svf, &tmpw)) return fatal;
285 tmpw += y; /* Amount of stack + number of locals. */
286 if (sp - stack <= tmpw)
288 print_string ("Save-file has too much stack (and I can't cope).\n");
289 return fatal;
291 if ((signed)currlen < tmpw*2) return fatal;
292 for (i=0; i<tmpw; ++i)
293 if (!read_word (svf, --sp)) return fatal;
294 currlen -= tmpw*2;
296 /* End of `Stks' processing... */
297 break;
298 /* Any more special chunk types must go in HERE or ABOVE. */
299 /* `CMem' compressed memory chunk; uncompress it. */
300 case ID_CMem:
301 if (!(progress & GOT_MEMORY)) /* Don't complain if two. */
303 (void) fseek (stf, 0, SEEK_SET);
304 i=0; /* Bytes written to data area. */
305 for (; currlen > 0; --currlen)
307 if ((x = get_c (svf)) == EOF) return fatal;
308 if (x == 0) /* Start run. */
310 /* Check for bogus run. */
311 if (currlen < 2)
313 print_string ("File contains bogus `CMem' chunk.\n");
314 for (; currlen > 0; --currlen)
315 (void) get_c (svf); /* Skip rest. */
316 currlen = 1;
317 i = 0xFFFF;
318 break; /* Keep going; may be a `UMem' too. */
320 /* Copy story file to memory during the run. */
321 --currlen;
322 if ((x = get_c (svf)) == EOF) return fatal;
323 for (; x >= 0 && i<h_dynamic_size; --x, ++i)
324 if ((y = get_c (stf)) == EOF) return fatal;
325 else
326 zmp[i] = (zbyte) y;
328 else /* Not a run. */
330 if ((y = get_c (stf)) == EOF) return fatal;
331 zmp[i] = (zbyte) (x ^ y);
332 ++i;
334 /* Make sure we don't load too much. */
335 if (i > h_dynamic_size)
337 print_string ("warning: `CMem' chunk too long!\n");
338 for (; currlen > 1; --currlen)
339 (void) get_c (svf); /* Skip rest. */
340 break; /* Keep going; there may be a `UMem' too. */
343 /* If chunk is short, assume a run. */
344 for (; i<h_dynamic_size; ++i)
345 if ((y = get_c (stf)) == EOF) return fatal;
346 else
347 zmp[i] = (zbyte) y;
348 if (currlen == 0)
349 progress |= GOT_MEMORY; /* Only if succeeded. */
350 break;
352 /* Fall right thru (to default) if already GOT_MEMORY */
353 /* `UMem' uncompressed memory chunk; load it. */
354 case ID_UMem:
355 if (!(progress & GOT_MEMORY)) /* Don't complain if two. */
357 /* Must be exactly the right size. */
358 if (currlen == h_dynamic_size)
360 if (fread (zmp, currlen, 1, svf) == 1)
362 progress |= GOT_MEMORY; /* Only on success. */
363 break;
366 else
367 print_string ("`UMem' chunk wrong size!\n");
368 /* Fall into default action (skip chunk) on errors. */
370 /* Fall thru (to default) if already GOT_MEMORY */
371 /* Unrecognised chunk type; skip it. */
372 default:
373 (void) fseek (svf, currlen, SEEK_CUR); /* Skip chunk. */
374 break;
376 if (skip)
377 (void) get_c (svf); /* Skip pad byte. */
381 * We've reached the end of the file. For the restoration to have been a
382 * success, we must have had one of each of the required chunks.
384 if (!(progress & GOT_HEADER))
385 print_string ("error: no valid header (`IFhd') chunk in file.\n");
386 if (!(progress & GOT_STACK))
387 print_string ("error: no valid stack (`Stks') chunk in file.\n");
388 if (!(progress & GOT_MEMORY))
389 print_string ("error: no valid memory (`CMem' or `UMem') chunk in file.\n");
391 return (progress == GOT_ALL ? 2 : fatal);
395 * Save a game using Quetzal format. Return 1 if OK, 0 if failed.
398 zword save_quetzal (int svf, int stf)
400 zlong ifzslen = 0, cmemlen = 0, stkslen = 0;
401 zlong pc;
402 zword i, j, n;
403 zword nvars, nargs, nstk, *p;
404 zbyte var;
405 long cmempos, stkspos;
406 int c;
408 /* Write `IFZS' header. */
409 if (!write_chnk (svf, ID_FORM, 0)) return 0;
410 if (!write_long (svf, ID_IFZS)) return 0;
412 /* Write `IFhd' chunk. */
413 GET_PC (pc);
414 if (!write_chnk (svf, ID_IFhd, 13)) return 0;
415 if (!write_word (svf, h_release)) return 0;
416 for (i=H_SERIAL; i<H_SERIAL+6; ++i)
417 if (!write_byte (svf, zmp[i])) return 0;
418 if (!write_word (svf, h_checksum)) return 0;
419 if (!write_long (svf, pc << 8)) /* Includes pad. */ return 0;
421 /* Write `CMem' chunk. */
422 if ((cmempos = ftell (svf)) < 0) return 0;
423 if (!write_chnk (svf, ID_CMem, 0)) return 0;
424 (void) fseek (stf, 0, SEEK_SET);
425 /* j holds current run length. */
426 for (i=0, j=0, cmemlen=0; i < h_dynamic_size; ++i)
428 if ((c = get_c (stf)) == EOF) return 0;
429 c ^= (int) zmp[i];
430 if (c == 0)
431 ++j; /* It's a run of equal bytes. */
432 else
434 /* Write out any run there may be. */
435 if (j > 0)
437 for (; j > 0x100; j -= 0x100)
439 if (!write_run (svf, 0xFF)) return 0;
440 cmemlen += 2;
442 if (!write_run (svf, j-1)) return 0;
443 cmemlen += 2;
444 j = 0;
446 /* Any runs are now written. Write this (nonzero) byte. */
447 if (!write_byte (svf, (zbyte) c)) return 0;
448 ++cmemlen;
452 * Reached end of dynamic memory. We ignore any unwritten run there may be
453 * at this point.
455 if (cmemlen & 1) /* Chunk length must be even. */
456 if (!write_byte (svf, 0)) return 0;
458 /* Write `Stks' chunk. You are not expected to understand this. ;) */
459 if ((stkspos = ftell (svf)) < 0) return 0;
460 if (!write_chnk (svf, ID_Stks, 0)) return 0;
463 * We construct a list of frame indices, most recent first, in `frames'.
464 * These indices are the offsets into the `stack' array of the word before
465 * the first word pushed in each frame.
467 frames[0] = sp - stack; /* The frame we'd get by doing a call now. */
468 for (i = fp - stack + 4, n=0; i < STACK_SIZE+4; i = stack[i-3] + 5)
469 frames[++n] = i;
472 * All versions other than V6 can use evaluation stack outside a function
473 * context. We write a faked stack frame (most fields zero) to cater for
474 * this.
476 if (h_version != V6)
478 for (i=0; i<6; ++i)
479 if (!write_byte (svf, 0)) return 0;
480 nstk = STACK_SIZE - frames[n];
481 if (!write_word (svf, nstk)) return 0;
482 for (j=STACK_SIZE-1; j >= frames[n]; --j)
483 if (!write_word (svf, stack[j])) return 0;
484 stkslen = 8 + 2*nstk;
487 /* Write out the rest of the stack frames. */
488 for (i=n; i>0; --i)
490 p = stack + frames[i] - 4; /* Points to call frame. */
491 nvars = (p[0] & 0x0F00) >> 8;
492 nargs = p[0] & 0x00FF;
493 nstk = frames[i] - frames[i-1] - nvars - 4;
494 pc = ((zlong) p[3] << 9) | p[2];
496 switch (p[0] & 0xF000) /* Check type of call. */
498 case 0x0000: /* Function. */
499 var = zmp[pc];
500 pc = ((pc + 1) << 8) | nvars;
501 break;
502 case 0x1000: /* Procedure. */
503 var = 0;
504 pc = (pc << 8) | 0x10 | nvars; /* Set procedure flag. */
505 break;
506 /* case 0x2000: */
507 default:
508 runtime_error (ERR_SAVE_IN_INTER);
509 return 0;
511 if (nargs != 0)
512 nargs = (1 << nargs) - 1; /* Make args into bitmap. */
514 /* Write the main part of the frame... */
515 if (!write_long (svf, pc)
516 || !write_byte (svf, var)
517 || !write_byte (svf, nargs)
518 || !write_word (svf, nstk)) return 0;
520 /* Write the variables and eval stack. */
521 for (j=0, ++p; j<nvars+nstk; ++j, --p)
522 if (!write_word (svf, *p)) return 0;
524 /* Calculate length written thus far. */
525 stkslen += 8 + 2 * (nvars + nstk);
528 /* Fill in variable chunk lengths. */
529 ifzslen = 3*8 + 4 + 14 + cmemlen + stkslen;
530 if (cmemlen & 1)
531 ++ifzslen;
532 (void) fseek (svf, 4, SEEK_SET);
533 if (!write_long (svf, ifzslen)) return 0;
534 (void) fseek (svf, cmempos+4, SEEK_SET);
535 if (!write_long (svf, cmemlen)) return 0;
536 (void) fseek (svf, stkspos+4, SEEK_SET);
537 if (!write_long (svf, stkslen)) return 0;
539 /* After all that, still nothing went wrong! */
540 return 1;