/* quetzal.c * * routines to handle QUETZAL save format */ #include #include #include "ztypes.h" /* You may want to define these as getc and putc, but then the code gets * quite big (especially for put_c). */ #define get_c fgetc #define put_c fputc #ifndef SEEK_CUR #define SEEK_CUR 1 #endif #if defined(USE_QUETZAL) /* don't compile anything otherwise */ typedef unsigned long ul_t; #define makeid(a,b,c,d) ((ul_t)(((a)<<24) | ((b)<<16) | ((c)<<8) | (d))) /* IDs of chunks we understand */ #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') /* macros to write QUETZAL 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))) /* save_quetzal * * attempt to save game in QUETZAL format; return TRUE on success */ #if defined(__STDC__) int save_quetzal (FILE *sfp, FILE *gfp, long zoffset) #else int save_quetzal (sfp,gfp,zoffset) FILE *sfp,*gfp; long zoffset; #endif { ul_t ifzslen = 0, cmemlen = 0, stkslen = 0, tmp_pc; int c; zword_t i, j, n, init_fp, tmp_fp, nstk, nvars, args; zword_t frames[STACK_SIZE/4+1]; zbyte_t var; long cmempos,stkspos; /* write IFZS header */ if (!write_chnk(sfp,ID_FORM,0)) return FALSE; if (!write_long(sfp,ID_IFZS)) return FALSE; /* write IFhd chunk */ if (!write_chnk(sfp,ID_IFhd,13)) return FALSE; if (!write_word(sfp,h_version)) return FALSE; for (i=0; i<6; ++i) if(!write_byte(sfp,get_byte(H_RELEASE_DATE+i))) return FALSE; if (!write_word(sfp,h_checksum)) return FALSE; if (!write_long(sfp,pc<<8)) /* includes pad byte */ return FALSE; /* write CMem chunk */ /* j is current run length */ if ((cmempos = ftell(sfp)) < 0) return FALSE; if (!write_chnk(sfp,ID_CMem,0)) return FALSE; fseek (gfp, zoffset, SEEK_SET); for (i=0, j=0, cmemlen=0; i < h_restart_size; ++i) { if ((c = get_c (gfp)) == EOF) return FALSE; c ^= get_byte (i); if(c == 0) ++j; else { /* write any run there may be */ if (j > 0) { for (; j > 0x100; j -= 0x100) { if (!write_run(sfp,0xFF)) return FALSE; cmemlen += 2; } if (!write_run(sfp,j-1)) return FALSE; cmemlen += 2; j = 0; } /* write this byte */ if (!write_byte(sfp,c)) return FALSE; ++cmemlen; } } /* there may be a run here, which we ignore */ if (cmemlen & 1) /* chunk length must be even */ if (!write_byte(sfp,0)) return FALSE; /* write Stks chunk */ if ((stkspos = ftell(sfp)) < 0) return FALSE; if (!write_chnk(sfp,ID_Stks,0)) return FALSE; /* frames is a list of FPs, most recent first */ frames[0] = sp-5; /* what FP would be if we did a call now */ for (init_fp=fp, n=0; init_fp <= STACK_SIZE-5; init_fp=stack[init_fp+2]) { frames[++n] = init_fp; } init_fp = frames[n] + 4; if (h_type != 6) { /* write a dummy frame for stack used before first call */ for (i = 0; i < 6; ++i) if (!write_byte(sfp,0)) return FALSE; nstk = STACK_SIZE - 1 - init_fp; if (!write_word(sfp,nstk)) return FALSE; for(i = STACK_SIZE-1; i > init_fp; --i) if (!write_word(sfp,stack[i])) return FALSE; stkslen = 8 + 2*nstk; } for (i=n; i>0; --i) { /* write out one stack frame. * * tmp_fp : FP when this frame was current * tmp_pc : PC on return from this frame, plus 000pvvvv * nvars : number of local vars for this frame * args : argument mask for this frame * nstk : words of evaluation stack used for this frame * var : variable to store result */ tmp_fp = frames[i]; nvars = (stack[tmp_fp+1] & VARS_MASK) >> VAR_SHIFT; args = stack[tmp_fp+1] & ARGS_MASK; nstk = tmp_fp - frames[i-1] - nvars - 4; tmp_pc = stack[tmp_fp+3] + (ul_t) stack[tmp_fp+4]*PAGE_SIZE; switch(stack[tmp_fp+1] & TYPE_MASK) { case FUNCTION: var = read_data_byte (&tmp_pc); /* also increments tmp_pc */ tmp_pc = (tmp_pc << 8) | nvars; break; case PROCEDURE: var = 0; tmp_pc = (tmp_pc << 8) | 0x10 | nvars; /* set procedure flag */ break; /* case ASYNC: */ default: output_line ("Illegal Z-machine operation: can't save while in interrupt."); return FALSE; } if (args != 0) args = (1 << args)-1; /* make args into bitmap */ if (!write_long(sfp,tmp_pc)) return FALSE; if (!write_byte(sfp,var)) return FALSE; if (!write_byte(sfp,args)) return FALSE; if (!write_word(sfp,nstk)) return FALSE; for (j=0; j 0) { /* read chunk header */ if (ifzslen < 8) return FALSE; if (!read_long(sfp,&tmpl) || !read_long(sfp,&currlen)) return FALSE; ifzslen -= 8; /* body of chunk */ if (ifzslen < currlen) return FALSE; skip = currlen & 1; ifzslen -= currlen+skip; switch (tmpl) { case ID_IFhd: if (progress & GOT_HEADER) { output_line ("Save file has two IFhd chunks!"); return FALSE; } progress |= GOT_HEADER; if (currlen < 13 || !read_word (sfp,&i)) return FALSE; if (i != h_version) progress = GOT_ERROR; for (i=H_RELEASE_DATE; i 0; currlen-=8) { if (currlen < 8) return FALSE; if (sp<4) { output_line ("error: this save-file has too much stack, and I can't cope."); return FALSE; } /* read PC, procedure flag, and arg count */ if (!read_long(sfp,&tmpl)) return FALSE; y = (zword_t) tmpl & 0x0F; tmpw = y << VAR_SHIFT; /* number of variables */ /* read result variable */ if ((x = get_c (sfp)) == EOF) return FALSE; if (tmpl & 0x10) { tmpw |= PROCEDURE; tmpl >>= 8; } else { tmpw |= FUNCTION; tmpl >>= 8; --tmpl; /* sanity check on result variable */ if (read_data_byte (&tmpl) != (zbyte_t) x) { output_line ("error: wrong variable number on stack (wrong story file?)."); return FALSE; } --tmpl; /* read_data_byte increments it */ } stack[--sp] = (zword_t) (tmpl / PAGE_SIZE); stack[--sp] = (zword_t) (tmpl % PAGE_SIZE); stack[--sp] = fp; if ((x = get_c (sfp)) == EOF) return FALSE; ++x; /* hopefully x now contains a single set bit */ for (i=0; i<8; ++i) if (x & (1<=sp) { output_line ("error: this save-file uses more stack than I can cope with."); return FALSE; } if (currlen < tmpw*2) return FALSE; for (i=0; i 0; --currlen) { if ((x = get_c (sfp)) == EOF) return FALSE; else write_char (x); } write_char ((char) 13); break; */ case ID_CMem: if (!(progress & GOT_MEMORY)) { fseek (gfp, zoffset, SEEK_SET); i=0; /* bytes written to data area */ for (; currlen > 0; --currlen) { if ((x = get_c (sfp)) == EOF) return FALSE; if (x == 0) /* start run */ { if (currlen < 2) { output_line ("File contains bogus CMem chunk"); for (; currlen > 0; --currlen) (void) get_c (sfp); /* skip rest */ currlen = 1; i = 0xFFFF; break; /* keep going in case there's a UMem */ } --currlen; if ((x = get_c (sfp)) == EOF) return FALSE; for (; x>=0 && ih_restart_size) { output_line ("warning: CMem chunk too long!"); for (; currlen > 1; --currlen) (void) get_c (sfp); /* skip rest */ break; /* keep going in case there's a UMem */ } } /* if chunk is short, assume a run */ for (; i