}/* 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 */
/* * runtime_error * * An error has occurred. Ignore it, pass it to os_fatal or report * it according to err_report_mode. * * errnum : Numeric code for error (1 to ERR_NUM_ERRORS) * */ internal static void runtime_error(int errnum) { bool wasfirst; if (errnum <= 0 || errnum > ErrorCodes.ERR_NUM_ERRORS) { return; } if (err_report_mode == ErrorCodes.ERR_REPORT_FATAL || (main.option_ignore_errors == false && errnum <= ErrorCodes.ERR_MAX_FATAL)) { Buffer.flush_buffer(); os_.fatal(err_messages[errnum - 1]); return; } wasfirst = (error_count[errnum - 1] == 0); error_count[errnum - 1]++; if ((err_report_mode == ErrorCodes.ERR_REPORT_ALWAYS) || (err_report_mode == ErrorCodes.ERR_REPORT_ONCE && wasfirst)) { long pc; FastMem.GET_PC(out pc); Text.print_string("Warning: "); Text.print_string(err_messages[errnum - 1]); Text.print_string(" (PC = "); print_long(pc, 16); Buffer.print_char(')'); if (err_report_mode == ErrorCodes.ERR_REPORT_ONCE) { Text.print_string(" (will ignore further occurrences)"); } else { Text.print_string(" (occurence "); print_long(error_count[errnum - 1], 10); Buffer.print_char(')'); } Buffer.new_line(); } } /* report_error */
}/* 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 */
/* * Save a game using Quetzal format. Return 1 if OK, 0 if failed. */ internal static zword save_quetzal(FileStream svf, MemoryStream stf) { zlong ifzslen = 0, cmemlen = 0, stkslen = 0; long pc; zword i, j, n; int nvars, nargs, nstk, p; zbyte var; long cmempos, stkspos; int c; /* Write `IFZS' header. */ if (!write_chnk(svf, ID_FORM, 0)) { return(0); } if (!write_long(svf, ID_IFZS)) { return(0); } /* Write `IFhd' chunk. */ FastMem.GET_PC(out pc); if (!write_chnk(svf, ID_IFhd, 13)) { return(0); } if (!write_word(svf, main.h_release)) { return(0); } for (i = ZMachine.H_SERIAL; i < ZMachine.H_SERIAL + 6; ++i) { if (!write_byte(svf, FastMem.ZMData[FastMem.zmp + i])) { return(0); } } if (!write_word(svf, main.h_checksum)) { return(0); } if (!write_long(svf, pc << 8)) /* Includes pad. */ return { (0); } /* Write `CMem' chunk. */ if ((cmempos = svf.Position) < 0) { return(0); } if (!write_chnk(svf, ID_CMem, 0)) { return(0); } // (void) fseek (stf, 0, SEEK_SET); stf.Position = 0; /* j holds current run length. */ for (i = 0, j = 0, cmemlen = 0; i < main.h_dynamic_size; ++i) { if ((c = stf.ReadByte()) == -1) { return(0); } c ^= (int)FastMem.ZMData[i]; if (c == 0) { ++j; /* It's a run of equal bytes. */ } else { /* Write out any run there may be. */ if (j > 0) { for (; j > 0x100; j -= 0x100) { if (!write_run(svf, 0xFF)) { return(0); } cmemlen += 2; } if (!write_run(svf, (byte)(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) > 0) /* Chunk length must be even. */ { if (!write_byte(svf, 0)) { return(0); } } /* Write `Stks' chunk. You are not expected to understand this. ;) */ if ((stkspos = svf.Position) < 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] = (zword)main.sp; /* The frame we'd get by doing a call now. */ for (i = (zword)(main.fp + 4), n = 0; i < General.STACK_SIZE + 4; i = (zword)(main.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 (main.h_version != ZMachine.V6) { for (i = 0; i < 6; ++i) { if (!write_byte(svf, 0)) { return(0); } } nstk = General.STACK_SIZE - frames[n]; if (!write_word(svf, nstk)) { return(0); } for (j = (zword)(General.STACK_SIZE - 1); j >= frames[n]; --j) { if (!write_word(svf, main.stack[j])) { return(0); } } stkslen = (zword)(8 + 2 * nstk); } /* Write out the rest of the stack frames. */ for (i = n; i > 0; --i) { p = frames[i] - 4; // p = stack + frames[i] - 4; /* Points to call frame. */ nvars = (main.stack[p] & 0x0F00) >> 8; nargs = main.stack[p] & 0x00FF; nstk = (zword)(frames[i] - frames[i - 1] - nvars - 4); pc = ((zlong)main.stack[p + 3] << 9) | main.stack[p + 2]; switch (main.stack[p] & 0xF000) /* Check type of call. */ { case 0x0000: /* Function. */ var = FastMem.ZMData[FastMem.zmp + pc]; pc = ((pc + 1) << 8) | (zlong)nvars; break; case 0x1000: /* Procedure. */ var = 0; pc = (pc << 8) | 0x10 | (zlong)nvars; /* Set procedure flag. */ break; /* case 0x2000: */ default: Err.runtime_error(ErrorCodes.ERR_SAVE_IN_INTER); return(0); } if (nargs != 0) { nargs = (zword)((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 < nvars + nstk; ++j, --p) { if (!write_word(svf, main.stack[p])) { return(0); } } /* Calculate length written thus far. */ stkslen += (zword)(8 + 2 * (nvars + nstk)); } /* Fill in variable chunk lengths. */ ifzslen = 3 * 8 + 4 + 14 + cmemlen + stkslen; if ((cmemlen & 1) > 0) { ++ifzslen; } svf.Position = 4; if (!write_long(svf, ifzslen)) { return(0); } svf.Position = cmempos + 4; if (!write_long(svf, cmemlen)) { return(0); } svf.Position = stkspos + 4; if (!write_long(svf, stkslen)) { return(0); } /* After all that, still nothing went wrong! */ return(1); }