}/* load_all_operands */ /* * interpret * * Z-code interpreter main loop * */ internal static void interpret() { do { zbyte opcode; FastMem.CODE_BYTE(out opcode); DebugState.Output("CODE: {0} -> {1:X}", FastMem.pcp - 1, opcode); if (main.abort_game_loop == true) { main.abort_game_loop = false; return; } zargc = 0; if (opcode < 0x80) { /* 2OP opcodes */ load_operand((zbyte)((opcode & 0x40) > 0 ? 2 : 1)); load_operand((zbyte)((opcode & 0x20) > 0 ? 2 : 1)); private_invoke(var_opcodes[opcode & 0x1f], "2OP", (opcode & 0x1f), opcode); } else if (opcode < 0xb0) { /* 1OP opcodes */ load_operand((zbyte)(opcode >> 4)); private_invoke(op1_opcodes[opcode & 0x0f], "1OP", (opcode & 0x0f), opcode); } else if (opcode < 0xc0) { /* 0OP opcodes */ private_invoke(op0_opcodes[opcode - 0xb0], "0OP", (opcode - 0xb0), opcode); } else { /* VAR opcodes */ zbyte specifier1; zbyte specifier2; if (opcode == 0xec || opcode == 0xfa) { /* opcodes 0xec */ FastMem.CODE_BYTE(out specifier1); /* and 0xfa are */ FastMem.CODE_BYTE(out specifier2); /* call opcodes */ load_all_operands(specifier1); /* with up to 8 */ load_all_operands(specifier2); /* arguments */ } else { FastMem.CODE_BYTE(out specifier1); load_all_operands(specifier1); } private_invoke(var_opcodes[opcode - 0xc0], "VAR", (opcode - 0xc0), opcode); } os_.tick(); } while (finished == 0); finished--; }/* interpret */
}/* direct_call */ /* * __extended__ * * Load and execute an extended opcode. * */ static void __extended__() { zbyte opcode; zbyte specifier; FastMem.CODE_BYTE(out opcode); FastMem.CODE_BYTE(out specifier); load_all_operands(specifier); if (opcode < 0x1e) /* extended opcodes from 0x1e on */ // ext_opcodes[opcode] (); /* are reserved for future spec' */ { private_invoke(ext_opcodes[opcode], "Extended", opcode, opcode); } }/* __extended__ */
}/* 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 */
/* * load_operand * * Load an operand, either a variable or a constant. * */ static void load_operand(zbyte type) { zword value; if ((type & 2) > 0) { /* variable */ zbyte variable; FastMem.CODE_BYTE(out variable); if (variable == 0) { value = main.stack[main.sp++]; } else if (variable < 16) { value = main.stack[main.fp - variable]; } else { zword addr = (zword)(main.h_globals + 2 * (variable - 16)); // TODO Make sure this logic FastMem.LOW_WORD(addr, out value); } } else if ((type & 1) > 0) { /* small constant */ zbyte bvalue; FastMem.CODE_BYTE(out bvalue); value = bvalue; } else { FastMem.CODE_WORD(out value); /* large constant */ } zargs[zargc++] = value; DebugState.Output(" Storing operand: {0} -> {1}", zargc - 1, value); }/* load_operand */
}/* branch */ /* * store * * Store an operand, either as a variable or pushed on the stack. * */ internal static void store(zword value) { zbyte variable; FastMem.CODE_BYTE(out variable); if (variable == 0) { main.stack[--main.sp] = value; // *--sp = value; DebugState.Output(" Storing {0} on stack at {1}", value, main.sp); } else if (variable < 16) { main.stack[main.fp - variable] = value; // *(fp - variable) = value; DebugState.Output(" Storing {0} on stack as Variable {1} at {2}", value, variable, main.sp); } else { zword addr = (zword)(main.h_globals + 2 * (variable - 16)); FastMem.SET_WORD(addr, value); DebugState.Output(" Storing {0} at {1}", value, addr); } }/* store */
}/* 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 */