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
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];
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
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
)
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
;
92 /* Read one long from file; return TRUE if OK. */
93 static bool read_long (int f
, zlong
*result
)
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
;
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
;
117 zword fatal
= 0; /* Set to -1 when errors must be fatal. */
118 zbyte skip
, progress
= GOT_NONE
;
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");
130 if ((ifzslen
& 1) || ifzslen
<4) /* Sanity checks. */ return 0;
133 /* Read each chunk and process it. */
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;
145 ifzslen
-= currlen
+ (zlong
) skip
;
149 /* `IFhd' header chunk; must be first in file. */
151 if (progress
& GOT_HEADER
)
153 print_string ("Save file has two IFZS chunks!\n");
156 progress
|= GOT_HEADER
;
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
;
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");
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
;
184 fatal
= -1; /* Setting PC means errors must be fatal. */
187 for (i
=13; i
<currlen
; ++i
)
188 (void) get_c (svf
); /* Skip rest of chunk. */
190 /* `Stks' stacks chunk; restoring this is quite complex. ;) */
192 if (progress
& GOT_STACK
)
194 print_string ("File contains two stack chunks!\n");
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...
210 if (currlen
< 8) return fatal
;
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");
220 if ((signed)currlen
< tmpw
*2) return fatal
;
221 for (i
=0; i
<tmpw
; ++i
)
222 if (!read_word (svf
, --sp
)) return fatal
;
226 /* We now proceed to load the main block of stack frames. */
227 for (fp
= stack
+STACK_SIZE
, frame_count
= 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");
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. */
243 /* Read result variable. */
244 if ((x
= get_c (svf
)) == EOF
) return fatal
;
246 /* Check the procedure flag... */
249 tmpw
|= 0x1000; /* It's a procedure. */
250 tmpl
>>= 8; /* Shift to get PC value. */
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");
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 */
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");
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");
291 if ((signed)currlen
< tmpw
*2) return fatal
;
292 for (i
=0; i
<tmpw
; ++i
)
293 if (!read_word (svf
, --sp
)) return fatal
;
296 /* End of `Stks' processing... */
298 /* Any more special chunk types must go in HERE or ABOVE. */
299 /* `CMem' compressed memory chunk; uncompress it. */
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. */
313 print_string ("File contains bogus `CMem' chunk.\n");
314 for (; currlen
> 0; --currlen
)
315 (void) get_c (svf
); /* Skip rest. */
318 break; /* Keep going; may be a `UMem' too. */
320 /* Copy story file to memory during the run. */
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
;
328 else /* Not a run. */
330 if ((y
= get_c (stf
)) == EOF
) return fatal
;
331 zmp
[i
] = (zbyte
) (x
^ y
);
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
;
349 progress
|= GOT_MEMORY
; /* Only if succeeded. */
352 /* Fall right thru (to default) if already GOT_MEMORY */
353 /* `UMem' uncompressed memory chunk; load it. */
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. */
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. */
373 (void) fseek (svf
, currlen
, SEEK_CUR
); /* Skip chunk. */
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;
403 zword nvars
, nargs
, nstk
, *p
;
405 long cmempos
, stkspos
;
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. */
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;
431 ++j
; /* It's a run of equal bytes. */
434 /* Write out any run there may be. */
437 for (; j
> 0x100; j
-= 0x100)
439 if (!write_run (svf
, 0xFF)) return 0;
442 if (!write_run (svf
, j
-1)) return 0;
446 /* Any runs are now written. Write this (nonzero) byte. */
447 if (!write_byte (svf
, (zbyte
) c
)) return 0;
452 * Reached end of dynamic memory. We ignore any unwritten run there may be
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)
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
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. */
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. */
500 pc
= ((pc
+ 1) << 8) | nvars
;
502 case 0x1000: /* Procedure. */
504 pc
= (pc
<< 8) | 0x10 | nvars
; /* Set procedure flag. */
508 runtime_error (ERR_SAVE_IN_INTER
);
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
;
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! */