/* quetzal.c - Saving and restoring of Quetzal files. * Written by Martin Frost * * Changes for Rockbox copyright 2009 Torne Wuff * * This file is part of Frotz. * * Frotz is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Frotz is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "frotz.h" #define far #define get_c fgetc #define put_c fputc typedef unsigned long zlong; /* * This is used only by save_quetzal. It probably should be allocated * dynamically rather than statically. */ static zword frames[STACK_SIZE/4+1]; /* * ID types. */ #define makeid(a,b,c,d) ((zlong) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d))) #define ID_FORM makeid ('F','O','R','M') #define ID_IFZS makeid ('I','F','Z','S') #define ID_IFhd makeid ('I','F','h','d') #define ID_UMem makeid ('U','M','e','m') #define ID_CMem makeid ('C','M','e','m') #define ID_Stks makeid ('S','t','k','s') #define ID_ANNO makeid ('A','N','N','O') /* * Various parsing states within restoration. */ #define GOT_HEADER 0x01 #define GOT_STACK 0x02 #define GOT_MEMORY 0x04 #define GOT_NONE 0x00 #define GOT_ALL 0x07 #define GOT_ERROR 0x80 /* * Macros used to write the files. */ #define write_byte(fp,b) (put_c (b, fp) != EOF) #define write_bytx(fp,b) write_byte (fp, (b) & 0xFF) #define write_word(fp,w) \ (write_bytx (fp, (w) >> 8) && write_bytx (fp, (w))) #define write_long(fp,l) \ (write_bytx (fp, (l) >> 24) && write_bytx (fp, (l) >> 16) && \ write_bytx (fp, (l) >> 8) && write_bytx (fp, (l))) #define write_chnk(fp,id,len) \ (write_long (fp, (id)) && write_long (fp, (len))) #define write_run(fp,run) \ (write_byte (fp, 0) && write_byte (fp, (run))) /* Read one word from file; return TRUE if OK. */ static bool read_word (int f, zword *result) { int a, b; if ((a = get_c (f)) == EOF) return FALSE; if ((b = get_c (f)) == EOF) return FALSE; *result = ((zword) a << 8) | (zword) b; return TRUE; } /* Read one long from file; return TRUE if OK. */ static bool read_long (int f, zlong *result) { int a, b, c, d; if ((a = get_c (f)) == EOF) return FALSE; if ((b = get_c (f)) == EOF) return FALSE; if ((c = get_c (f)) == EOF) return FALSE; if ((d = get_c (f)) == EOF) return FALSE; *result = ((zlong) a << 24) | ((zlong) b << 16) | ((zlong) c << 8) | (zlong) d; return TRUE; } /* * Restore a saved game using Quetzal format. Return 2 if OK, 0 if an error * occurred before any damage was done, -1 on a fatal error. */ zword restore_quetzal (int svf, int stf) { zlong ifzslen, currlen, tmpl; zlong pc; zword i, tmpw; zword fatal = 0; /* Set to -1 when errors must be fatal. */ zbyte skip, progress = GOT_NONE; int x, y; /* Check it's really an `IFZS' file. */ if (!read_long (svf, &tmpl) || !read_long (svf, &ifzslen) || !read_long (svf, &currlen)) return 0; if (tmpl != ID_FORM || currlen != ID_IFZS) { print_string ("This is not a saved game file!\n"); return 0; } if ((ifzslen & 1) || ifzslen<4) /* Sanity checks. */ return 0; ifzslen -= 4; /* Read each chunk and process it. */ while (ifzslen > 0) { /* Read chunk header. */ if (ifzslen < 8) /* Couldn't contain a chunk. */ return 0; if (!read_long (svf, &tmpl) || !read_long (svf, &currlen)) return 0; ifzslen -= 8; /* Reduce remaining by size of header. */ /* Handle chunk body. */ if (ifzslen < currlen) /* Chunk goes past EOF?! */ return 0; skip = currlen & 1; ifzslen -= currlen + (zlong) skip; switch (tmpl) { /* `IFhd' header chunk; must be first in file. */ case ID_IFhd: if (progress & GOT_HEADER) { print_string ("Save file has two IFZS chunks!\n"); return fatal; } progress |= GOT_HEADER; if (currlen < 13 || !read_word (svf, &tmpw)) return fatal; if (tmpw != h_release) progress = GOT_ERROR; for (i=H_SERIAL; i STACK_SIZE) { print_string ("Save-file has too much stack (and I can't cope).\n"); return fatal; } currlen -= 8; if ((signed)currlen < tmpw*2) return fatal; for (i=0; i 0; currlen -= 8, ++frame_count) { if (currlen < 8) return fatal; if (sp - stack < 4) /* No space for frame. */ { print_string ("Save-file has too much stack (and I can't cope).\n"); return fatal; } /* Read PC, procedure flag and formal param count. */ if (!read_long (svf, &tmpl)) return fatal; y = (int) (tmpl & 0x0F); /* Number of formals. */ tmpw = y << 8; /* Read result variable. */ if ((x = get_c (svf)) == EOF) return fatal; /* Check the procedure flag... */ if (tmpl & 0x10) { tmpw |= 0x1000; /* It's a procedure. */ tmpl >>= 8; /* Shift to get PC value. */ } else { /* Functions have type 0, so no need to or anything. */ tmpl >>= 8; /* Shift to get PC value. */ --tmpl; /* Point at result byte. */ /* Sanity check on result variable... */ if (zmp[tmpl] != (zbyte) x) { print_string ("Save-file has wrong variable number on stack (possibly wrong game version?)\n"); return fatal; } } *--sp = (zword) (tmpl >> 9); /* High part of PC */ *--sp = (zword) (tmpl & 0x1FF); /* Low part of PC */ *--sp = (zword) (fp - stack - 1); /* FP */ /* Read and process argument mask. */ if ((x = get_c (svf)) == EOF) return fatal; ++x; /* Should now be a power of 2 */ for (i=0; i<8; ++i) if (x & (1< 0; --currlen) { if ((x = get_c (svf)) == EOF) return fatal; if (x == 0) /* Start run. */ { /* Check for bogus run. */ if (currlen < 2) { print_string ("File contains bogus `CMem' chunk.\n"); for (; currlen > 0; --currlen) (void) get_c (svf); /* Skip rest. */ currlen = 1; i = 0xFFFF; break; /* Keep going; may be a `UMem' too. */ } /* Copy story file to memory during the run. */ --currlen; if ((x = get_c (svf)) == EOF) return fatal; for (; x >= 0 && i h_dynamic_size) { print_string ("warning: `CMem' chunk too long!\n"); for (; currlen > 1; --currlen) (void) get_c (svf); /* Skip rest. */ break; /* Keep going; there may be a `UMem' too. */ } } /* If chunk is short, assume a run. */ for (; i 0) { for (; j > 0x100; j -= 0x100) { if (!write_run (svf, 0xFF)) return 0; cmemlen += 2; } if (!write_run (svf, j-1)) return 0; cmemlen += 2; j = 0; } /* Any runs are now written. Write this (nonzero) byte. */ if (!write_byte (svf, (zbyte) c)) return 0; ++cmemlen; } } /* * Reached end of dynamic memory. We ignore any unwritten run there may be * at this point. */ if (cmemlen & 1) /* Chunk length must be even. */ if (!write_byte (svf, 0)) return 0; /* Write `Stks' chunk. You are not expected to understand this. ;) */ if ((stkspos = ftell (svf)) < 0) return 0; if (!write_chnk (svf, ID_Stks, 0)) return 0; /* * We construct a list of frame indices, most recent first, in `frames'. * These indices are the offsets into the `stack' array of the word before * the first word pushed in each frame. */ frames[0] = sp - stack; /* The frame we'd get by doing a call now. */ for (i = fp - stack + 4, n=0; i < STACK_SIZE+4; i = stack[i-3] + 5) frames[++n] = i; /* * All versions other than V6 can use evaluation stack outside a function * context. We write a faked stack frame (most fields zero) to cater for * this. */ if (h_version != V6) { for (i=0; i<6; ++i) if (!write_byte (svf, 0)) return 0; nstk = STACK_SIZE - frames[n]; if (!write_word (svf, nstk)) return 0; for (j=STACK_SIZE-1; j >= frames[n]; --j) if (!write_word (svf, stack[j])) return 0; stkslen = 8 + 2*nstk; } /* Write out the rest of the stack frames. */ for (i=n; i>0; --i) { p = stack + frames[i] - 4; /* Points to call frame. */ nvars = (p[0] & 0x0F00) >> 8; nargs = p[0] & 0x00FF; nstk = frames[i] - frames[i-1] - nvars - 4; pc = ((zlong) p[3] << 9) | p[2]; switch (p[0] & 0xF000) /* Check type of call. */ { case 0x0000: /* Function. */ var = zmp[pc]; pc = ((pc + 1) << 8) | nvars; break; case 0x1000: /* Procedure. */ var = 0; pc = (pc << 8) | 0x10 | nvars; /* Set procedure flag. */ break; /* case 0x2000: */ default: runtime_error (ERR_SAVE_IN_INTER); return 0; } if (nargs != 0) nargs = (1 << nargs) - 1; /* Make args into bitmap. */ /* Write the main part of the frame... */ if (!write_long (svf, pc) || !write_byte (svf, var) || !write_byte (svf, nargs) || !write_word (svf, nstk)) return 0; /* Write the variables and eval stack. */ for (j=0, ++p; j