/* process.c - Interpreter loop and program control * Copyright (c) 1995-1997 Stefan Jokisch * * This file is part of Frotz. * * Frotz is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Frotz is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "frotz.h" #ifdef DJGPP #include "djfrotz.h" #endif zword zargs[8]; int zargc; static int finished = 0; static void __extended__ (void); static void __illegal__ (void); void (*op0_opcodes[0x10]) (void) = { z_rtrue, z_rfalse, z_print, z_print_ret, z_nop, z_save, z_restore, z_restart, z_ret_popped, z_catch, z_quit, z_new_line, z_show_status, z_verify, __extended__, z_piracy }; void (*op1_opcodes[0x10]) (void) = { z_jz, z_get_sibling, z_get_child, z_get_parent, z_get_prop_len, z_inc, z_dec, z_print_addr, z_call_s, z_remove_obj, z_print_obj, z_ret, z_jump, z_print_paddr, z_load, z_call_n }; void (*var_opcodes[0x40]) (void) = { __illegal__, z_je, z_jl, z_jg, z_dec_chk, z_inc_chk, z_jin, z_test, z_or, z_and, z_test_attr, z_set_attr, z_clear_attr, z_store, z_insert_obj, z_loadw, z_loadb, z_get_prop, z_get_prop_addr, z_get_next_prop, z_add, z_sub, z_mul, z_div, z_mod, z_call_s, z_call_n, z_set_colour, z_throw, __illegal__, __illegal__, __illegal__, z_call_s, z_storew, z_storeb, z_put_prop, z_read, z_print_char, z_print_num, z_random, z_push, z_pull, z_split_window, z_set_window, z_call_s, z_erase_window, z_erase_line, z_set_cursor, z_get_cursor, z_set_text_style, z_buffer_mode, z_output_stream, z_input_stream, z_sound_effect, z_read_char, z_scan_table, z_not, z_call_n, z_call_n, z_tokenise, z_encode_text, z_copy_table, z_print_table, z_check_arg_count }; void (*ext_opcodes[0x1d]) (void) = { z_save, z_restore, z_log_shift, z_art_shift, z_set_font, z_draw_picture, z_picture_data, z_erase_picture, z_set_margins, z_save_undo, z_restore_undo, z_print_unicode, z_check_unicode, __illegal__, __illegal__, __illegal__, z_move_window, z_window_size, z_window_style, z_get_wind_prop, z_scroll_window, z_pop_stack, z_read_mouse, z_mouse_window, z_push_stack, z_put_wind_prop, z_print_form, z_make_menu, z_picture_table }; /* * init_process * * Initialize process variables. * */ void init_process (void) { finished = 0; } /* init_process */ /* * load_operand * * Load an operand, either a variable or a constant. * */ static void load_operand (zbyte type) { zword value; if (type & 2) { /* variable */ zbyte variable; CODE_BYTE (variable) if (variable == 0) value = *sp++; else if (variable < 16) value = *(fp - variable); else { zword addr = h_globals + 2 * (variable - 16); LOW_WORD (addr, value) } } else if (type & 1) { /* small constant */ zbyte bvalue; CODE_BYTE (bvalue) value = bvalue; } else CODE_WORD (value) /* large constant */ zargs[zargc++] = value; }/* load_operand */ /* * load_all_operands * * Given the operand specifier byte, load all (up to four) operands * for a VAR or EXT opcode. * */ static void load_all_operands (zbyte specifier) { int i; for (i = 6; i >= 0; i -= 2) { zbyte type = (specifier >> i) & 0x03; if (type == 3) break; load_operand (type); } }/* load_all_operands */ /* * interpret * * Z-code interpreter main loop * */ void interpret (void) { do { zbyte opcode; CODE_BYTE (opcode) zargc = 0; if (opcode < 0x80) { /* 2OP opcodes */ load_operand ((zbyte) (opcode & 0x40) ? 2 : 1); load_operand ((zbyte) (opcode & 0x20) ? 2 : 1); var_opcodes[opcode & 0x1f] (); } else if (opcode < 0xb0) { /* 1OP opcodes */ load_operand ((zbyte) (opcode >> 4)); op1_opcodes[opcode & 0x0f] (); } else if (opcode < 0xc0) { /* 0OP opcodes */ op0_opcodes[opcode - 0xb0] (); } else { /* VAR opcodes */ zbyte specifier1; zbyte specifier2; if (opcode == 0xec || opcode == 0xfa) { /* opcodes 0xec */ CODE_BYTE (specifier1) /* and 0xfa are */ CODE_BYTE (specifier2) /* call opcodes */ load_all_operands (specifier1); /* with up to 8 */ load_all_operands (specifier2); /* arguments */ } else { CODE_BYTE (specifier1) load_all_operands (specifier1); } var_opcodes[opcode - 0xc0] (); } #if defined(DJGPP) && defined(SOUND_SUPPORT) if (end_of_sound_flag) end_of_sound (); #endif } while (finished == 0); finished--; }/* 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). * */ void call (zword routine, int argc, zword *args, int ct) { long pc; zword value; zbyte count; int i; if (sp - stack < 4) runtime_error (ERR_STK_OVF); GET_PC (pc) *--sp = (zword) (pc >> 9); *--sp = (zword) (pc & 0x1ff); *--sp = (zword) (fp - stack - 1); *--sp = (zword) (argc | (ct << (f_setup.save_quetzal ? 12 : 8))); fp = sp; frame_count++; /* Calculate byte address of routine */ if (h_version <= V3) pc = (long) routine << 1; else if (h_version <= V5) pc = (long) routine << 2; else if (h_version <= V7) pc = ((long) routine << 2) + ((long) h_functions_offset << 3); else /* h_version == V8 */ pc = (long) routine << 3; if (pc >= story_size) runtime_error (ERR_ILL_CALL_ADDR); SET_PC (pc) /* Initialise local variables */ CODE_BYTE (count) if (count > 15) runtime_error (ERR_CALL_NON_RTN); if (sp - stack < count) runtime_error (ERR_STK_OVF); if (f_setup.save_quetzal) fp[0] |= (zword) count << 8; /* Save local var count for Quetzal. */ value = 0; for (i = 0; i < count; i++) { if (h_version <= V4) /* V1 to V4 games provide default */ CODE_WORD (value) /* values for all local variables */ *--sp = (zword) ((argc-- > 0) ? args[i] : value); } /* Start main loop for direct calls */ if (ct == 2) interpret (); }/* 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. * */ void ret (zword value) { long pc; int ct; if (sp > fp) runtime_error (ERR_STK_UNDF); sp = fp; ct = *sp++ >> (f_setup.save_quetzal ? 12 : 8); frame_count--; fp = stack + 1 + *sp++; pc = *sp++; pc = ((long) *sp++ << 9) | pc; SET_PC (pc) /* Handle resulting value */ if (ct == 0) store (value); if (ct == 2) *--sp = value; /* Stop main loop for direct calls */ if (ct == 2) finished++; }/* 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. * */ void branch (bool flag) { long pc; zword offset; zbyte specifier; zbyte off1; zbyte off2; CODE_BYTE (specifier) off1 = specifier & 0x3f; if (!flag) specifier ^= 0x80; if (!(specifier & 0x40)) { /* it's a long branch */ if (off1 & 0x20) /* propagate sign bit */ off1 |= 0xc0; CODE_BYTE (off2) offset = (off1 << 8) | off2; } else offset = off1; /* it's a short branch */ if (specifier & 0x80) { if (offset > 1) { /* normal branch */ GET_PC (pc) pc += (short) offset - 2; SET_PC (pc) } else ret (offset); /* special case, return 0 or 1 */ } }/* branch */ /* * store * * Store an operand, either as a variable or pushed on the stack. * */ void store (zword value) { zbyte variable; CODE_BYTE (variable) if (variable == 0) *--sp = value; else if (variable < 16) *(fp - variable) = value; else { zword addr = h_globals + 2 * (variable - 16); SET_WORD (addr, value) } }/* store */ /* * direct_call * * Call the interpreter loop directly. This is necessary when * * - a sound effect has been finished * - a read instruction has timed out * - a newline countdown has hit zero * * The interpreter returns the result value on the stack. * */ int direct_call (zword addr) { zword saved_zargs[8]; int saved_zargc; int i; /* Calls to address 0 return false */ if (addr == 0) return 0; /* Save operands and operand count */ for (i = 0; i < 8; i++) saved_zargs[i] = zargs[i]; saved_zargc = zargc; /* Call routine directly */ call (addr, 0, 0, 2); /* Restore operands and operand count */ for (i = 0; i < 8; i++) zargs[i] = saved_zargs[i]; zargc = saved_zargc; /* Resulting value lies on top of the stack */ return (short) *sp++; }/* direct_call */ /* * __extended__ * * Load and execute an extended opcode. * */ static void __extended__ (void) { zbyte opcode; zbyte specifier; CODE_BYTE (opcode) CODE_BYTE (specifier) load_all_operands (specifier); if (opcode < 0x1d) /* extended opcodes from 0x1d on */ ext_opcodes[opcode] (); /* are reserved for future spec' */ }/* __extended__ */ /* * __illegal__ * * Exit game because an unknown opcode has been hit. * */ static void __illegal__ (void) { runtime_error (ERR_ILL_OPCODE); }/* __illegal__ */ /* * z_catch, store the current stack frame for later use with z_throw. * * no zargs used * */ void z_catch (void) { store (f_setup.save_quetzal ? frame_count : (zword) (fp - stack)); }/* z_catch */ /* * z_throw, go back to the given stack frame and return the given value. * * zargs[0] = value to return * zargs[1] = stack frame * */ void z_throw (void) { if (f_setup.save_quetzal) { if (zargs[1] > frame_count) runtime_error (ERR_BAD_FRAME); /* Unwind the stack a frame at a time. */ for (; frame_count > zargs[1]; --frame_count) fp = stack + 1 + fp[1]; } else { if (zargs[1] > STACK_SIZE) runtime_error (ERR_BAD_FRAME); fp = stack + zargs[1]; } ret (zargs[0]); }/* z_throw */ /* * z_call_n, call a subroutine and discard its result. * * zargs[0] = packed address of subroutine * zargs[1] = first argument (optional) * ... * zargs[7] = seventh argument (optional) * */ void z_call_n (void) { if (zargs[0] != 0) call (zargs[0], zargc - 1, zargs + 1, 1); }/* z_call_n */ /* * z_call_s, call a subroutine and store its result. * * zargs[0] = packed address of subroutine * zargs[1] = first argument (optional) * ... * zargs[7] = seventh argument (optional) * */ void z_call_s (void) { if (zargs[0] != 0) call (zargs[0], zargc - 1, zargs + 1, 0); else store (0); }/* z_call_s */ /* * z_check_arg_count, branch if subroutine was called with >= n arg's. * * zargs[0] = number of arguments * */ void z_check_arg_count (void) { if (fp == stack + STACK_SIZE) branch (zargs[0] == 0); else branch (zargs[0] <= (*fp & 0xff)); }/* z_check_arg_count */ /* * z_jump, jump unconditionally to the given address. * * zargs[0] = PC relative address * */ void z_jump (void) { long pc; GET_PC (pc) pc += (short) zargs[0] - 2; if (pc >= story_size) runtime_error (ERR_ILL_JUMP_ADDR); SET_PC (pc) }/* z_jump */ /* * z_nop, no operation. * * no zargs used * */ void z_nop (void) { /* Do nothing */ }/* z_nop */ /* * z_quit, stop game and exit interpreter. * * no zargs used * */ void z_quit (void) { finished = 9999; }/* z_quit */ /* * z_ret, return from a subroutine with the given value. * * zargs[0] = value to return * */ void z_ret (void) { ret (zargs[0]); }/* z_ret */ /* * z_ret_popped, return from a subroutine with a value popped off the stack. * * no zargs used * */ void z_ret_popped (void) { ret (*sp++); }/* z_ret_popped */ /* * z_rfalse, return from a subroutine with false (0). * * no zargs used * */ void z_rfalse (void) { ret (0); }/* z_rfalse */ /* * z_rtrue, return from a subroutine with true (1). * * no zargs used * */ void z_rtrue (void) { ret (1); }/* z_rtrue */