/* * 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. */ internal static zword RestoreQuetzal(FileStream svf, System.IO.Stream stf) { 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 (!TryReadLong(svf, out zlong tmpl) || !TryReadLong(svf, out zlong ifzslen) || !TryReadLong(svf, out zlong currlen)) { return(0); } if (tmpl != ID_FORM || currlen != ID_IFZS) { Text.PrintString("This is not a saved game file!\n"); return(0); } if (((ifzslen & 1) > 0) || 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 (!TryReadLong(svf, out tmpl) || !TryReadLong(svf, out currlen)) { return(0); } ifzslen -= 8; /* Reduce remaining by size of header. */ /* Handle chunk body. */ if (ifzslen < currlen) /* Chunk goes past EOF?! */ return { (0); } skip = (byte)(currlen & 1); ifzslen -= currlen + skip; switch (tmpl) { /* `IFhd' header chunk; must be first in file. */ case 1229351012: // IFhd if ((progress & GOT_HEADER) > 0) { Text.PrintString("Save file has two IFZS chunks!\n"); return(fatal); } progress |= GOT_HEADER; if (currlen < 13 || !TryReadWord(svf, out tmpw)) { return(fatal); } if (tmpw != Main.h_release) { progress = GOT_ERROR; } for (i = ZMachine.H_SERIAL; i < ZMachine.H_SERIAL + 6; ++i) { if ((x = svf.ReadByte()) == -1) { return(fatal); } if (x != FastMem.ZMData[FastMem.Zmp + i]) { progress = GOT_ERROR; } } if (!TryReadWord(svf, out tmpw)) { return(fatal); } if (tmpw != Main.h_checksum) { progress = GOT_ERROR; } if ((progress & GOT_ERROR) > 0) { Text.PrintString("File was not saved from this story!\n"); return(fatal); } if ((x = svf.ReadByte()) == -1) { return(fatal); } pc = (zlong)x << 16; if ((x = svf.ReadByte()) == -1) { return(fatal); } pc |= (zlong)x << 8; if ((x = svf.ReadByte()) == -1) { return(fatal); } pc |= (zlong)x; fatal = zword.MaxValue; /* Setting PC means errors must be fatal. */ // TODO make sure this works FastMem.SetPc(pc); for (i = 13; i < currlen; ++i) { svf.ReadByte(); /* Skip rest of chunk. */ } break; /* `Stks' stacks chunk; restoring this is quite complex. ;) */ case 1400138611: // ID_Stks: if ((progress & GOT_STACK) > 0) { Text.PrintString("File contains two stack chunks!\n"); break; } progress |= GOT_STACK; fatal = zword.MaxValue;; /* Setting SP means errors must be fatal. */ // sp = stack + General.STACK_SIZE; Main.sp = Main.Stack.Length; /* * All versions other than V6 may use evaluation stack outside * any function context. As a result a faked function context * will be present in the file here. We skip this context, but * load the associated stack onto the stack proper... */ if (Main.h_version != ZMachine.V6) { if (currlen < 8) { return(fatal); } for (i = 0; i < 6; ++i) { if (svf.ReadByte() != 0) { return(fatal); } } if (!TryReadWord(svf, out tmpw)) { return(fatal); } if (tmpw > General.STACK_SIZE) { Text.PrintString("Save-file has too much stack (and I can't cope).\n"); return(fatal); } currlen -= 8; if (currlen < tmpw * 2) { return(fatal); } for (i = 0; i < tmpw; ++i) { // if (!read_word(svf, --sp)) return fatal; if (!TryReadWord(svf, out Main.Stack[--Main.sp])) { return(fatal); } } currlen -= (zword)(tmpw * 2); } /* We now proceed to load the main block of stack frames. */ for (Main.fp = Main.Stack.Length, Main.frame_count = 0; currlen > 0; currlen -= 8, ++Main.frame_count) { if (currlen < 8) { return(fatal); } if (Main.sp < 4) /* No space for frame. */ { Text.PrintString("Save-file has too much stack (and I can't cope).\n"); return(fatal); } /* Read PC, procedure flag and formal param count. */ if (!TryReadLong(svf, out tmpl)) { return(fatal); } y = (int)(tmpl & 0x0F); /* Number of formals. */ tmpw = (zword)(y << 8); /* Read result variable. */ if ((x = svf.ReadByte()) == -1) { return(fatal); } /* Check the procedure flag... */ if ((tmpl & 0x10) > 0) { 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 (FastMem.ZMData[FastMem.Zmp + tmpl] != (zbyte)x) { Text.PrintString("Save-file has wrong variable number on stack (possibly wrong game version?)\n"); return(fatal); } } Main.Stack[--Main.sp] = (zword)(tmpl >> 9); /* High part of PC */ Main.Stack[--Main.sp] = (zword)(tmpl & 0x1FF); /* Low part of PC */ Main.Stack[--Main.sp] = (zword)(Main.fp - 1); /* FP */ /* Read and process argument mask. */ if ((x = svf.ReadByte()) == -1) { return(fatal); } ++x; /* Should now be a power of 2 */ for (i = 0; i < 8; ++i) { if ((x & (1 << i)) > 0) { break; } } if ((x ^ (1 << i)) > 0) /* Not a power of 2 */ { Text.PrintString("Save-file uses incomplete argument lists (which I can't handle)\n"); return(fatal); } // *--sp = tmpw | i; Main.Stack[--Main.sp] = (zword)(tmpw | i); Main.fp = Main.sp; /* FP for next frame. */ /* Read amount of eval stack used. */ if (!TryReadWord(svf, out tmpw)) { return(fatal); } tmpw += (zword)y; /* Amount of stack + number of locals. */ // if (sp - stack <= tmpw) { if (Main.sp <= tmpw) { Text.PrintString("Save-file has too much stack (and I can't cope).\n"); return(fatal); } if (currlen < tmpw * 2) { return(fatal); } for (i = 0; i < tmpw; ++i) { if (!TryReadWord(svf, out Main.Stack[--Main.sp])) { return(fatal); } } currlen -= (zword)(tmpw * 2); } /* End of `Stks' processing... */ break; /* Any more special chunk types must go in HERE or ABOVE. */ /* `CMem' compressed memory chunk; uncompress it. */ case 1129145709: // CMem if ((progress & GOT_MEMORY) == 0) /* Don't complain if two. */ { stf.Position = 0; // (void) fseek (stf, 0, SEEK_SET); i = 0; /* Bytes written to data area. */ for (; currlen > 0; --currlen) { if ((x = svf.ReadByte()) == -1) { return(fatal); } if (x == 0) /* Start run. */ { /* Check for bogus run. */ if (currlen < 2) { Text.PrintString("File contains bogus `CMem' chunk.\n"); for (; currlen > 0; --currlen) { svf.ReadByte(); /* 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 = svf.ReadByte()) == -1) { return(fatal); } for (; x >= 0 && i < Main.h_dynamic_size; --x, ++i) { if ((y = stf.ReadByte()) == -1) { return(fatal); } else { FastMem.ZMData[FastMem.Zmp + i] = (zbyte)y; } } } else /* Not a run. */ { if ((y = stf.ReadByte()) == -1) { return(fatal); } FastMem.ZMData[FastMem.Zmp + i] = (zbyte)(x ^ y); ++i; } /* Make sure we don't load too much. */ if (i > Main.h_dynamic_size) { Text.PrintString("warning: `CMem' chunk too long!\n"); for (; currlen > 1; --currlen) { svf.ReadByte(); /* Skip rest. */ } break; /* Keep going; there may be a `UMem' too. */ } } /* If chunk is short, assume a run. */ for (; i < Main.h_dynamic_size; ++i) { if ((y = stf.ReadByte()) == -1) { return(fatal); } else { FastMem.ZMData[FastMem.Zmp + i] = (zbyte)y; } } if (currlen == 0) { progress |= GOT_MEMORY; /* Only if succeeded. */ } break; } goto default; /* Fall right thru (to default) if already GOT_MEMORY */ /* `UMem' uncompressed memory chunk; load it. */ case 1431135597: // ID_UMem: if ((progress & GOT_MEMORY) == 0) /* Don't complain if two. */ { /* Must be exactly the right size. */ if (currlen == Main.h_dynamic_size) { int read = svf.Read(FastMem.ZMData, (int)FastMem.Zmp, (int)currlen); if (read == currlen) { progress |= GOT_MEMORY; /* Only on success. */ break; } //if (fread(zmp, currlen, 1, svf) == 1) { //} } else { Text.PrintString("`UMem' chunk wrong size!\n"); } /* Fall into default action (skip chunk) on errors. */ } goto default; /* Fall thru (to default) if already GOT_MEMORY */ /* Unrecognised chunk type; skip it. */ default: // (void) fseek (svf, currlen, SEEK_CUR); /* Skip chunk. */ svf.Position += currlen; break; } if (skip > 0) { svf.ReadByte(); /* Skip pad byte. */ } } /* * We've reached the end of the file. For the restoration to have been a * success, we must have had one of each of the required chunks. */ if ((progress & GOT_HEADER) == 0) { Text.PrintString("error: no valid header (`IFhd') chunk in file.\n"); } if ((progress & GOT_STACK) == 0) { Text.PrintString("error: no valid stack (`Stks') chunk in file.\n"); } if ((progress & GOT_MEMORY) == 0) { Text.PrintString("error: no valid memory (`CMem' or `UMem') chunk in file.\n"); } return((ushort)(progress == GOT_ALL ? 2 : fatal)); }