}/* z_check_arg_count */ /* * z_jump, jump unconditionally to the given address. * * zargs[0] = PC relative address * */ internal static void z_jump() { long pc; FastMem.GET_PC(out pc); pc += (short)zargs[0] - 2; // TODO This actually counts on an overflow to work if (pc >= main.story_size) { Err.runtime_error(ErrorCodes.ERR_ILL_JUMP_ADDR); } FastMem.SET_PC(pc); }/* z_jump */
}/* ret */ /* * branch * * Take a jump after an instruction based on the flag, either true or * false. The branch can be short or long; it is encoded in one or two * bytes respectively. When bit 7 of the first byte is set, the jump * takes place if the flag is true; otherwise it is taken if the flag * is false. When bit 6 of the first byte is set, the branch is short; * otherwise it is long. The offset occupies the bottom 6 bits of the * first byte plus all the bits in the second byte for long branches. * Uniquely, an offset of 0 means return false, and an offset of 1 is * return true. * */ internal static void branch(bool flag) { long pc; zword offset; zbyte specifier; zbyte off1; zbyte off2; FastMem.CODE_BYTE(out specifier); off1 = (zbyte)(specifier & 0x3f); if (!flag) { specifier ^= 0x80; } if ((specifier & 0x40) == 0) { // if (!(specifier & 0x40)) { /* it's a long branch */ if ((off1 & 0x20) > 0) /* propagate sign bit */ { off1 |= 0xc0; } FastMem.CODE_BYTE(out off2); offset = (zword)((off1 << 8) | off2); } else { offset = off1; /* it's a short branch */ } if ((specifier & 0x80) > 0) { if (offset > 1) { /* normal branch */ FastMem.GET_PC(out pc); pc += (short)offset - 2; FastMem.SET_PC(pc); } else { ret(offset); /* special case, return 0 or 1 */ } } }/* branch */
}/* storew */ /* * z_restart, re-load dynamic area, clear the stack and set the PC. * * no zargs used * */ internal static void z_restart() { Buffer.flush_buffer(); os_.restart_game(ZMachine.RESTART_BEGIN); Random.seed_random(0); if (!first_restart) { story_fp.Position = init_fp_pos; int read = story_fp.Read(ZMData, 0, main.h_dynamic_size); if (read != main.h_dynamic_size) { os_.fatal("Story file read error"); } } else { first_restart = false; } restart_header(); Screen.restart_screen(); main.sp = main.fp = General.STACK_SIZE; // TODO Critical to make sure this logic works; sp = fp = stack + STACK_SIZE; main.frame_count = 0; if (main.h_version != ZMachine.V6) { zword pc = main.h_start_pc; FastMem.SET_PC((int)pc); } else { Process.call(main.h_start_pc, 0, 0, 0); } os_.restart_game(ZMachine.RESTART_END); }/* z_restart */
}/* call */ /* * ret * * Return from the current subroutine and restore the previous stack * frame. The result may be stored (0), thrown away (1) or pushed on * the stack (2). In the latter case a direct call has been finished * and we must exit the interpreter loop. * */ internal static void ret(zword value) { long pc; int ct; if (main.sp > main.fp) { Err.runtime_error(ErrorCodes.ERR_STK_UNDF); } main.sp = main.fp; DebugState.Output("Removing Frame: {0}", main.frame_count); ct = main.stack[main.sp++] >> (main.option_save_quetzal == true ? 12 : 8); main.frame_count--; main.fp = 1 + main.stack[main.sp++]; // fp = stack + 1 + *sp++; pc = main.stack[main.sp++]; pc = (main.stack[main.sp++] << 9) | (int)pc; // TODO Really don't trust casting PC to int FastMem.SET_PC(pc); /* Handle resulting value */ if (ct == 0) { store(value); } if (ct == 2) { main.stack[--main.sp] = value; } /* Stop main loop for direct calls */ if (ct == 2) { finished++; } }/* ret */
}/* interpret */ /* * call * * Call a subroutine. Save PC and FP then load new PC and initialise * new stack frame. Note that the caller may legally provide less or * more arguments than the function actually has. The call type "ct" * can be 0 (z_call_s), 1 (z_call_n) or 2 (direct call). * */ internal static void call(zword routine, int argc, int args_offset, int ct) { long pc; zword value; zbyte count; int i; if (main.sp < 4)//if (sp - stack < 4) { Err.runtime_error(ErrorCodes.ERR_STK_OVF); } FastMem.GET_PC(out pc); main.stack[--main.sp] = (zword)(pc >> 9); main.stack[--main.sp] = (zword)(pc & 0x1ff); main.stack[--main.sp] = (zword)(main.fp - 1); // *--sp = (zword) (fp - stack - 1); main.stack[--main.sp] = (zword)(argc | (ct << (main.option_save_quetzal == true ? 12 : 8))); main.fp = main.sp; main.frame_count++; DebugState.Output("Added Frame: {0} -> {1}:{2}:{3}:{4}", main.frame_count, main.stack[main.sp + 0], main.stack[main.sp + 1], main.stack[main.sp + 2], main.stack[main.sp + 3]); /* Calculate byte address of routine */ if (main.h_version <= ZMachine.V3) { pc = (long)routine << 1; } else if (main.h_version <= ZMachine.V5) { pc = (long)routine << 2; } else if (main.h_version <= ZMachine.V7) { pc = ((long)routine << 2) + ((long)main.h_functions_offset << 3); } else /* (h_version <= V8) */ { pc = (long)routine << 3; } if (pc >= main.story_size) { Err.runtime_error(ErrorCodes.ERR_ILL_CALL_ADDR); } FastMem.SET_PC(pc); /* Initialise local variables */ FastMem.CODE_BYTE(out count); if (count > 15) { Err.runtime_error(ErrorCodes.ERR_CALL_NON_RTN); } if (main.sp < count) { Err.runtime_error(ErrorCodes.ERR_STK_OVF); } if (main.option_save_quetzal == true) { main.stack[main.fp] |= (zword)(count << 8); /* Save local var count for Quetzal. */ } value = 0; for (i = 0; i < count; i++) { if (main.h_version <= ZMachine.V4) /* V1 to V4 games provide default */ { FastMem.CODE_WORD(out value); /* values for all local variables */ } main.stack[--main.sp] = (zword)((argc-- > 0) ? zargs[args_offset + i] : value); //*--sp = (zword) ((argc-- > 0) ? args[i] : value); } /* Start main loop for direct calls */ if (ct == 2) { interpret(); } }/* call */
/* * 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 restore_quetzal(FileStream svf, System.IO.Stream 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, out tmpl) || !read_long(svf, out ifzslen) || !read_long(svf, out currlen)) { return(0); } if (tmpl != ID_FORM || currlen != ID_IFZS) { Text.print_string("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 (!read_long(svf, out tmpl) || !read_long(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 + (zlong)skip; switch (tmpl) { /* `IFhd' header chunk; must be first in file. */ case 1229351012: // IFhd if ((progress & GOT_HEADER) > 0) { Text.print_string("Save file has two IFZS chunks!\n"); return(fatal); } progress |= GOT_HEADER; if (currlen < 13 || !read_word(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 (!read_word(svf, out tmpw)) { return(fatal); } if (tmpw != main.h_checksum) { progress = GOT_ERROR; } if ((progress & GOT_ERROR) > 0) { Text.print_string("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.SET_PC(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.print_string("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 (!read_word(svf, out tmpw)) { return(fatal); } if (tmpw > General.STACK_SIZE) { Text.print_string("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 (!read_word(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.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, 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.print_string("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.print_string("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 (!read_word(svf, out tmpw)) { return(fatal); } tmpw += (zword)y; /* Amount of stack + number of locals. */ // if (sp - stack <= tmpw) { if (main.sp <= tmpw) { Text.print_string("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 (!read_word(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.print_string("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.print_string("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) { byte[] buffer = new byte[currlen]; 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.print_string("`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.print_string("error: no valid header (`IFhd') chunk in file.\n"); } if ((progress & GOT_STACK) == 0) { Text.print_string("error: no valid stack (`Stks') chunk in file.\n"); } if ((progress & GOT_MEMORY) == 0) { Text.print_string("error: no valid memory (`CMem' or `UMem') chunk in file.\n"); } return((ushort)(progress == GOT_ALL ? 2 : fatal)); }