}/* reset_memory */ /* * storeb * * Write a byte value to the dynamic Z-machine memory. * */ internal static void storeb(zword addr, zbyte value) { if (addr >= main.h_dynamic_size) { Err.runtime_error(ErrorCodes.ERR_STORE_RANGE); } if (addr == ZMachine.H_FLAGS + 1) { /* flags register is modified */ main.h_flags &= (zword)(~(ZMachine.SCRIPTING_FLAG | ZMachine.FIXED_FONT_FLAG)); main.h_flags |= (zword)(value & (ZMachine.SCRIPTING_FLAG | ZMachine.FIXED_FONT_FLAG)); if ((value & ZMachine.SCRIPTING_FLAG) > 0) { if (!main.ostream_script) { Files.script_open(); } } else { if (main.ostream_script) { Files.script_close(); } } Screen.refresh_text_style(); } SET_BYTE(addr, value); DebugState.Output("storeb: {0} -> {1}", addr, value); }/* storeb */
}/* 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 */
internal static void SET_WORD(long addr, zword v) { ZMData[addr] = HI(v); ZMData[addr + 1] = LO(v); DebugState.Output("ZMP: {0} -> {1}", addr, v); }
private static void private_invoke(zinstruction instruction, string array, int index, int opcode) { DebugState.last_call_made = instruction.Method.Name + ":" + opcode; DebugState.Output(false, "Invoking: {0:X} -> {1} -> {2}", opcode, instruction.Method.Name, invokeCount); instruction.DynamicInvoke(); invokeCount++; }
}/* 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 */
/* * 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 */
}/* restart_header */ /* * init_memory * * Allocate memory and load the story file. * */ internal static void init_memory() { long size; zword addr; zword n; int i, j; // TODO Abstract this part /* Open story file */ // story_fp = new System.IO.FileStream(main.story_name, System.IO.FileMode.Open, System.IO.FileAccess.Read); story_fp = os_.path_open(main.story_data); if (story_fp == null) { os_.fatal("Cannot open story file"); } init_fp_pos = story_fp.Position; storyData = new byte[story_fp.Length]; story_fp.Read(storyData, 0, storyData.Length); story_fp.Position = 0; DebugState.Output("Starting story: {0}", main.story_name); /* Allocate memory for story header */ ZMData = new byte[64]; Frotz.Other.ZMath.clearArray(ZMData); /* Load header into memory */ if (story_fp.Read(ZMData, 0, 64) != 64) { os_.fatal("Story file read error"); } /* Copy header fields to global variables */ LOW_BYTE(ZMachine.H_VERSION, out main.h_version); if (main.h_version < ZMachine.V1 || main.h_version > ZMachine.V8) { os_.fatal("Unknown Z-code version"); } LOW_BYTE(ZMachine.H_CONFIG, out main.h_config); if (main.h_version == ZMachine.V3 && ((main.h_config & ZMachine.CONFIG_BYTE_SWAPPED) != 0)) { os_.fatal("Byte swapped story file"); } LOW_WORD(ZMachine.H_RELEASE, out main.h_release); LOW_WORD(ZMachine.H_RESIDENT_SIZE, out main.h_resident_size); LOW_WORD(ZMachine.H_START_PC, out main.h_start_pc); LOW_WORD(ZMachine.H_DICTIONARY, out main.h_dictionary); LOW_WORD(ZMachine.H_OBJECTS, out main.h_objects); LOW_WORD(ZMachine.H_GLOBALS, out main.h_globals); LOW_WORD(ZMachine.H_DYNAMIC_SIZE, out main.h_dynamic_size); LOW_WORD(ZMachine.H_FLAGS, out main.h_flags); for (i = 0, addr = ZMachine.H_SERIAL; i < 6; i++, addr++) { LOW_BYTE(addr, out main.h_serial[i]); } // TODO serial might need to be a char /* Auto-detect buggy story files that need special fixes */ main.story_id = Story.UNKNOWN; for (i = 0; records[i].story_id != Story.UNKNOWN; i++) { if (main.h_release == records[i].release) { for (j = 0; j < 6; j++) { if (main.h_serial[j] != records[i].serial[j]) { goto no_match; } } main.story_id = records[i].story_id; } no_match :; /* null statement */ } LOW_WORD(ZMachine.H_ABBREVIATIONS, out main.h_abbreviations); LOW_WORD(ZMachine.H_FILE_SIZE, out main.h_file_size); /* Calculate story file size in bytes */ if (main.h_file_size != 0) { main.story_size = 2 * main.h_file_size; if (main.h_version >= ZMachine.V4) { main.story_size *= 2; } if (main.h_version >= ZMachine.V6) { main.story_size *= 2; } if (main.story_id == Story.AMFV && main.h_release == 47) { main.story_size = 2 * main.h_file_size; } else if (main.story_size > 0) {/* os_path_open() set the size */ } else {/* some old games lack the file size entry */ main.story_size = story_fp.Length - init_fp_pos; story_fp.Position = init_fp_pos + 64; } LOW_WORD(ZMachine.H_CHECKSUM, out main.h_checksum); LOW_WORD(ZMachine.H_ALPHABET, out main.h_alphabet); LOW_WORD(ZMachine.H_FUNCTIONS_OFFSET, out main.h_functions_offset); LOW_WORD(ZMachine.H_STRINGS_OFFSET, out main.h_strings_offset); LOW_WORD(ZMachine.H_TERMINATING_KEYS, out main.h_terminating_keys); LOW_WORD(ZMachine.H_EXTENSION_TABLE, out main.h_extension_table); /* Zork Zero beta and Macintosh versions don't have the graphics flag set */ if (main.story_id == Story.ZORK_ZERO) { if (main.h_release == 96 || main.h_release == 153 || main.h_release == 242 || main.h_release == 296) { main.h_flags |= ZMachine.GRAPHICS_FLAG; } } /* Adjust opcode tables */ if (main.h_version <= ZMachine.V4) { Process.op0_opcodes[0x09] = new Process.zinstruction(Variable.z_pop); Process.op0_opcodes[0x0f] = new Process.zinstruction(Math.z_not); } else { Process.op0_opcodes[0x09] = new Process.zinstruction(Process.z_catch); Process.op0_opcodes[0x0f] = new Process.zinstruction(Process.z_call_n); } /* Allocate memory for story data */ byte[] temp = new byte[ZMData.Length]; Frotz.Other.ZMath.clearArray(temp); System.Array.Copy(ZMData, temp, ZMData.Length); ZMData = new byte[main.story_size]; Frotz.Other.ZMath.clearArray(ZMData); System.Array.Copy(temp, ZMData, temp.Length); /* Load story file in chunks of 32KB */ n = 0x8000; for (size = 64; size < main.story_size; size += n) { if (main.story_size - size < 0x8000) { n = (ushort)(main.story_size - size); } SET_PC(size); int read = story_fp.Read(ZMData, (int)pcp, n); if (read != n) { os_.fatal("Story file read error"); } } // Take a moment to calculate the checksum of the story file in case verify is called ZMData_checksum = 0; for (int k = 64; k < ZMData.Length; k++) { ZMData_checksum += ZMData[k]; } } DebugState.Output("Story Size: {0}", main.story_size); first_restart = true; /* Read header extension table */ main.hx_table_size = get_header_extension(ZMachine.HX_TABLE_SIZE); main.hx_unicode_table = get_header_extension(ZMachine.HX_UNICODE_TABLE); main.hx_flags = get_header_extension(ZMachine.HX_FLAGS); }/* init_memory */
internal static void SET_BYTE(long addr, byte v) { ZMData[addr] = v; DebugState.Output("ZMP: {0} -> {1}", addr, v); }