diff options
author | Paul Gilbert | 2018-11-11 21:18:50 -0800 |
---|---|---|
committer | Paul Gilbert | 2018-12-08 19:05:59 -0800 |
commit | a083eb3d5ca682a0b465d383413f62ff38b8e814 (patch) | |
tree | b31a455c695ddfe2f68eb8d237e3cbd09281e10e /engines/gargoyle | |
parent | 38a458139aef488acf48abfcf61233c529984cb1 (diff) | |
download | scummvm-rg350-a083eb3d5ca682a0b465d383413f62ff38b8e814.tar.gz scummvm-rg350-a083eb3d5ca682a0b465d383413f62ff38b8e814.tar.bz2 scummvm-rg350-a083eb3d5ca682a0b465d383413f62ff38b8e814.zip |
GLK: FROTZ: Added bulk of game processor
Diffstat (limited to 'engines/gargoyle')
22 files changed, 6070 insertions, 126 deletions
diff --git a/engines/gargoyle/frotz/buffer.cpp b/engines/gargoyle/frotz/buffer.cpp index 15ec771bfc..7d9e64465b 100644 --- a/engines/gargoyle/frotz/buffer.cpp +++ b/engines/gargoyle/frotz/buffer.cpp @@ -61,7 +61,7 @@ void Buffer::flush() { void Buffer::printChar(zchar c) { static bool flag = false; - if (g_vm->_message || g_vm->_ostream_memory || g_vm->_enableBuffering) { + if (g_vm->message || g_vm->ostream_memory || g_vm->enable_buffering) { if (!flag) { // Characters 0 and ZC_RETURN are special cases if (c == ZC_RETURN) { diff --git a/engines/gargoyle/frotz/err.cpp b/engines/gargoyle/frotz/err.cpp index f9a3ce9580..f75b764ad0 100644 --- a/engines/gargoyle/frotz/err.cpp +++ b/engines/gargoyle/frotz/err.cpp @@ -67,14 +67,15 @@ Errors::Errors() { Common::fill(&_count[0], &_count[ERR_NUM_ERRORS], 0); } -void Errors::runtimeError(int errNum) { - int wasfirst; +void Errors::runtimeError(ErrorCode errNum) { +#ifdef TODO + int wasfirst; if (errNum <= 0 || errNum > ERR_NUM_ERRORS) return; - if (g_vm->_options._err_report_mode == ERR_REPORT_FATAL - || (!g_vm->_options._ignore_errors && errNum <= ERR_MAX_FATAL)) { + if (g_vm->_err_report_mode == ERR_REPORT_FATAL + || (!g_vm->_ignore_errors && errNum <= ERR_MAX_FATAL)) { g_vm->_buffer.flush(); error(ERR_MESSAGES[errNum - 1]); return; @@ -83,9 +84,9 @@ void Errors::runtimeError(int errNum) { wasfirst = (_count[errNum - 1] == 0); _count[errNum - 1]++; - if ((g_vm->_options._err_report_mode == ERR_REPORT_ALWAYS) - || (g_vm->_options._err_report_mode == ERR_REPORT_ONCE && wasfirst)) { - long pc; + if ((g_vm->_err_report_mode == ERR_REPORT_ALWAYS) + || (_err_report_mode == ERR_REPORT_ONCE && wasfirst)) { + long pc = g_vm->_processor GET_PC(pc); printString("Warning: "); printString(ERR_MESSAGES[errNum - 1]); @@ -93,7 +94,7 @@ void Errors::runtimeError(int errNum) { printLong(pc, 16); printChar(')'); - if (g_vm->_options._err_report_mode == ERR_REPORT_ONCE) { + if (_err_report_mode == ERR_REPORT_ONCE) { printString(" (will ignore further occurrences)"); } else { printString(" (occurence "); @@ -103,6 +104,7 @@ void Errors::runtimeError(int errNum) { newLine(); } +#endif } void Errors::printLong(uint value, int base) { diff --git a/engines/gargoyle/frotz/err.h b/engines/gargoyle/frotz/err.h index f61d7269af..e71c9881e8 100644 --- a/engines/gargoyle/frotz/err.h +++ b/engines/gargoyle/frotz/err.h @@ -28,26 +28,51 @@ namespace Gargoyle { namespace Frotz { -#define ERR_NUM_ERRORS 33 -#define ERR_MAX_FATAL 19 +enum ErrorCode { + ERR_TEXT_BUF_OVF = 1, ///< Text buffer overflow + ERR_STORE_RANGE = 2, ///< Store out of dynamic memory + ERR_DIV_ZERO = 3, ///< Division by zero + ERR_ILL_OBJ = 4, ///< Illegal object + ERR_ILL_ATTR = 5, ///< Illegal attribute + ERR_NO_PROP = 6, ///< No such property + ERR_STK_OVF = 7, ///< Stack overflow + ERR_ILL_CALL_ADDR = 8, ///< Call to illegal address + ERR_CALL_NON_RTN = 9, ///< Call to non-routine + ERR_STK_UNDF = 10, ///< Stack underflow + ERR_ILL_OPCODE = 11, ///< Illegal opcode + ERR_BAD_FRAME = 12, ///< Bad stack frame + ERR_ILL_JUMP_ADDR = 13, ///< Jump to illegal address + ERR_SAVE_IN_INTER = 14, ///< Can't save while in interrupt + ERR_STR3_NESTING = 15, ///< Nesting stream #3 too deep + ERR_ILL_WIN = 16, ///< Illegal window + ERR_ILL_WIN_PROP = 17, ///< Illegal window property + ERR_ILL_PRINT_ADDR = 18, ///< Print at illegal address + ERR_DICT_LEN = 19, ///< Illegal dictionary word length + ERR_MAX_FATAL = 19, + + // Less serious errors + ERR_JIN_0 = 20, ///< @jin called with object 0 + ERR_GET_CHILD_0 = 21, ///< @get_child called with object 0 + ERR_GET_PARENT_0 = 22, ///< @get_parent called with object 0 + ERR_GET_SIBLING_0 = 23, ///< @get_sibling called with object 0 + ERR_GET_PROP_ADDR_0 = 24, ///< @get_prop_addr called with object 0 + ERR_GET_PROP_0 = 25, ///< @get_prop called with object 0 + ERR_PUT_PROP_0 = 26, ///< @put_prop called with object 0 + ERR_CLEAR_ATTR_0 = 27, ///< @clear_attr called with object 0 + ERR_SET_ATTR_0 = 28, ///< @set_attr called with object 0 + ERR_TEST_ATTR_0 = 29, ///< @test_attr called with object 0 + ERR_MOVE_OBJECT_0 = 30, ///< @move_object called moving object 0 + ERR_MOVE_OBJECT_TO_0 = 31, ///< @move_object called moving into object 0 + ERR_REMOVE_OBJECT_0 = 32, ///< @remove_object called with object 0 + ERR_GET_NEXT_PROP_0 = 33, ///< @get_next_prop called with object 0 + ERR_NUM_ERRORS = 33 +}; class Errors { private: static const char *const ERR_MESSAGES[ERR_NUM_ERRORS]; int _count[ERR_NUM_ERRORS]; -public: - /** - * Constructor - */ - Errors(); - - /** - * An error has occurred. Ignore it, pass it to os_fatal or report - * it according to err_report_mode. - * @param errNum Numeric code for error (1 to ERR_NUM_ERRORS) - */ - void runtimeError(int errNum); - +private: /** * Print an unsigned 32bit number in decimal or hex. */ @@ -67,6 +92,23 @@ public: * Add a newline */ void newLine(); +protected: + /** + * Get the PC. Is implemented by the Processor class, which derives from Errors + */ + virtual zword getPC() const = 0; +public: + /** + * Constructor + */ + Errors(); + + /** + * An error has occurred. Ignore it, pass it to os_fatal or report + * it according to err_report_mode. + * @param errNum Numeric code for error (1 to ERR_NUM_ERRORS) + */ + void runtimeError(ErrorCode errNum); }; } // End of namespace Frotz diff --git a/engines/gargoyle/frotz/frotz.cpp b/engines/gargoyle/frotz/frotz.cpp index 64b9f54449..c0a9442ad4 100644 --- a/engines/gargoyle/frotz/frotz.cpp +++ b/engines/gargoyle/frotz/frotz.cpp @@ -28,27 +28,18 @@ namespace Frotz { Frotz *g_vm; -Frotz::Frotz(OSystem *syst, const GargoyleGameDescription *gameDesc) : Glk(syst, gameDesc), - _storyId(UNKNOWN), _storySize(0), _sp(nullptr), _fp(nullptr), _frameCount(0), - _ostream_screen(true), _ostream_script(false), _ostream_memory(false), - _ostream_record(false), _istream_replay(false), _message(false), - _cwin(0), _mwin(0), _mouse_x(0), _mouse_y(0), _menu_selected(0), - _enableWrapping(false), _enableScripting(true), _enableScrolling(false), - _enableBuffering(false), _reserveMem(0) { +Frotz::Frotz(OSystem *syst, const GargoyleGameDescription *gameDesc) : + Processor(syst, gameDesc) { g_vm = this; - Common::fill(&_stack[0], &_stack[STACK_SIZE], 0); } void Frotz::runGame(Common::SeekableReadStream *gameFile) { + story_fp = gameFile; initialize(); // TODO } -void Frotz::initialize() { - _mem.initialize(); -} - Common::Error Frotz::loadGameState(int slot) { return Common::kNoError; } diff --git a/engines/gargoyle/frotz/frotz.h b/engines/gargoyle/frotz/frotz.h index 4dd583449b..c032265453 100644 --- a/engines/gargoyle/frotz/frotz.h +++ b/engines/gargoyle/frotz/frotz.h @@ -23,11 +23,7 @@ #ifndef GARGOYLE_FROTZ_FROTZ #define GARGOYLE_FROTZ_FROTZ -#include "gargoyle/glk.h" -#include "gargoyle/frotz/frotz_types.h" -#include "gargoyle/frotz/buffer.h" -#include "gargoyle/frotz/err.h" -#include "gargoyle/frotz/mem.h" +#include "gargoyle/frotz/processor.h" namespace Gargoyle { namespace Frotz { @@ -35,52 +31,7 @@ namespace Frotz { /** * Frotz interpreter for Z-code games */ -class Frotz : public Glk { -private: - /** - * Perform any initialization - */ - void initialize(); -public: - UserOptions _options; - Header _header; - Buffer _buffer; - Mem _mem; - - // Story file name, id number and size - Common::SeekableReadStream *_gameFile; - Story _storyId; - size_t _storySize; - - // Stack data - zword _stack[STACK_SIZE]; - zword *_sp; - zword *_fp; - zword _frameCount; - - // IO streams - bool _ostream_screen; - bool _ostream_script; - bool _ostream_memory; - bool _ostream_record; - bool _istream_replay; - bool _message; - - // Current window and mouse data - int _cwin; - int _mwin; - int _mouse_y; - int _mouse_x; - int _menu_selected; - - // Window attributes - bool _enableWrapping; - bool _enableScripting; - bool _enableScrolling; - bool _enableBuffering; - - // Size of memory to reserve (in bytes) - size_t _reserveMem; +class Frotz : public Processor { public: /** * Constructor diff --git a/engines/gargoyle/frotz/frotz_types.h b/engines/gargoyle/frotz/frotz_types.h index 3c7312c1bc..c4f4298a4e 100644 --- a/engines/gargoyle/frotz/frotz_types.h +++ b/engines/gargoyle/frotz/frotz_types.h @@ -35,15 +35,6 @@ namespace Frotz { #define lo(v) (v & 0xff) #define hi(v) (v >> 8) -#define SET_WORD(addr,v) WRITE_BE_UINT16(addr,v) -#define LOW_WORD(addr,v) READ_BE_UINT16(addr,v) -#define HIGH_WORD(addr,v) READ_BE_UINT16(addr,v) -#define HIGH_LONG(addr,v) READ_BE_UINT32(addr,v) -#define CODE_WORD(v) v = g_vm->_mem.readWord() -#define CODE_IDX_WORD(v,i) v = g_vm->_mem.readWord(i) -#define GET_PC(v) v = g_vm->_mem.getPC() -#define SET_PC(v) g_vm->_mem.setPC(v); - /* There are four error reporting modes: never report errors; * report only the first time a given error type occurs; * report every time an error occurs; @@ -113,6 +104,113 @@ enum Story { UNKNOWN }; +enum Version { + V1 = 1, + V2 = 2, + V3 = 3, + V4 = 4, + V5 = 5, + V6 = 6, + V7 = 7, + V8 = 8, + V9 = 9 +}; + +enum ConfigFlag { + CONFIG_BYTE_SWAPPED = 0x01, ///< Story file is byte swapped - V3 + CONFIG_TIME = 0x02, ///< Status line displays time - V3 + CONFIG_TWODISKS = 0x04, ///< Story file occupied two disks - V3 + CONFIG_TANDY = 0x08, ///< Tandy licensed game - V3 + CONFIG_NOSTATUSLINE = 0x10, ///< Interpr can't support status lines - V3 + CONFIG_SPLITSCREEN = 0x20, ///< Interpr supports split screen mode - V3 + CONFIG_PROPORTIONAL = 0x40, ///< Interpr uses proportional font - V3 + + CONFIG_COLOUR = 0x01, ///< Interpr supports colour - V5+ + CONFIG_PICTURES = 0x02, ///< Interpr supports pictures - V6 + CONFIG_BOLDFACE = 0x04, ///< Interpr supports boldface style - V4+ + CONFIG_EMPHASIS = 0x08, ///< Interpr supports emphasis style - V4+ + CONFIG_FIXED = 0x10, ///< Interpr supports fixed width style - V4+ + CONFIG_SOUND = 0x20, ///< Interpr supports sound - V6 + CONFIG_TIMEDINPUT = 0x80, ///< Interpr supports timed input - V4+ + + SCRIPTING_FLAG = 0x0001, ///< Outputting to transscription file - V1+ + FIXED_FONT_FLAG = 0x0002, ///< Use fixed width font - V3+ + REFRESH_FLAG = 0x0004, ///< Refresh the screen - V6 + GRAPHICS_FLAG = 0x0008, ///< Game wants to use graphics - V5+ + OLD_SOUND_FLAG = 0x0010, ///< Game wants to use sound effects - V3 + UNDO_FLAG = 0x0010, ///< Game wants to use UNDO feature - V5+ + MOUSE_FLAG = 0x0020, ///< Game wants to use a mouse - V5+ + COLOUR_FLAG = 0x0040, ///< Game wants to use colours - V5+ + SOUND_FLAG = 0x0080, ///< Game wants to use sound effects - V5+ + MENU_FLAG = 0x0100 ///< Game wants to use menus - V6 +}; + +enum { + TRANSPARENT_FLAG = 0x0001 ///< Game wants to use transparency - V6 +}; + +enum FrotzInterp { +#define INTERP_DEFAULT 0 +#define INTERP_DEC_20 1 +#define INTERP_APPLE_IIE 2 +#define INTERP_MACINTOSH 3 +#define INTERP_AMIGA 4 +#define INTERP_ATARI_ST 5 +#define INTERP_MSDOS 6 +#define INTERP_CBM_128 7 +#define INTERP_CBM_64 8 +#define INTERP_APPLE_IIC 9 +#define INTERP_APPLE_IIGS 10 +#define INTERP_TANDY 11 +}; + +enum Colour { + BLACK_COLOUR = 2, + RED_COLOUR = 3, + GREEN_COLOUR = 4, + YELLOW_COLOUR = 5, + BLUE_COLOUR = 6, + MAGENTA_COLOUR = 7, + CYAN_COLOUR = 8, + WHITE_COLOUR = 9, + GREY_COLOUR = 10, ///< INTERP_MSDOS only + LIGHTGREY_COLOUR = 10, ///< INTERP_AMIGA only + MEDIUMGREY_COLOUR = 11, ///< INTERP_AMIGA only + DARKGREY_COLOUR = 12, ///< INTERP_AMIGA only + TRANSPARENT_COLOUR = 15 ///< ZSpec 1.1 +}; + +enum Style { + REVERSE_STYLE = 1, + BOLDFACE_STYLE = 2, + EMPHASIS_STYLE = 4, + FIXED_WIDTH_STYLE = 8 +}; + +enum FontStyle { + TEXT_FONT = 1, + PICTURE_FONT = 2, + GRAPHICS_FONT = 3, + FIXED_WIDTH_FONT = 4 +}; + +/*** Constants for os_beep */ + +#define BEEP_HIGH 1 +#define BEEP_LOW 2 + +/*** Constants for os_restart_game */ + +#define RESTART_BEGIN 0 +#define RESTART_WPROP_SET 1 +#define RESTART_END 2 + +/*** Constants for os_menu */ + +#define MENU_NEW 0 +#define MENU_ADD 1 +#define MENU_REMOVE 2 + typedef byte zbyte; typedef uint zchar; typedef uint16 zword; diff --git a/engines/gargoyle/frotz/glk_interface.cpp b/engines/gargoyle/frotz/glk_interface.cpp new file mode 100644 index 0000000000..e1311fbc62 --- /dev/null +++ b/engines/gargoyle/frotz/glk_interface.cpp @@ -0,0 +1,272 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/glk_interface.h" + +namespace Gargoyle { +namespace Frotz { + +GlkInterface::GlkInterface(OSystem *syst, const GargoyleGameDescription *gameDesc) : + Glk(syst, gameDesc), + oldstyle(0), curstyle(0), cury(1), curx(1), fixforced(0), + curr_fg(-2), curr_bg(-2), curr_font(1), prev_font(1), temp_font(0), + curr_status_ht(0), mach_status_ht(0), gos_status(nullptr), gos_upper(nullptr), + gos_lower(nullptr), gos_curwin(nullptr), gos_linepending(0), gos_linebuf(nullptr), + gos_linewin(nullptr), gos_channel(nullptr), cwin(0), mwin(0), mouse_x(0), mouse_y(0), + menu_selected(0), ostream_screen(false), ostream_script(false), ostream_memory(false), + ostream_record(false), istream_replay(false), message(false), + enable_wrapping(false), enable_scripting(false), enable_scrolling(false), + enable_buffering(false), next_sample(0), next_volume(0), + _soundLocked(false), _soundPlaying(false) { + Common::fill(&statusline[0], &statusline[256], '\0'); +} + +int GlkInterface::os_char_width(zchar z) { + return 1; +} + +int GlkInterface::os_string_width(const zchar *s) { + int width = 0; + zchar c; + while ((c = *s++) != 0) + if (c == ZC_NEW_STYLE || c == ZC_NEW_FONT) + s++; + else + width += os_char_width(c); + return width; +} + +int GlkInterface::os_string_length(zchar *s) { + int length = 0; + while (*s++) length++; + return length; +} + +void GlkInterface::os_prepare_sample(int a) { + glk_sound_load_hint(a, 1); +} + +void GlkInterface::os_finish_with_sample(int a) { + glk_sound_load_hint(a, 0); +} + +void GlkInterface::os_start_sample(int number, int volume, int repeats, zword eos) { + int vol; + + if (!gos_channel) { + gos_channel = glk_schannel_create(0); + if (!gos_channel) + return; + } + + switch (volume) { + case 1: vol = 0x02000; break; + case 2: vol = 0x04000; break; + case 3: vol = 0x06000; break; + case 4: vol = 0x08000; break; + case 5: vol = 0x0a000; break; + case 6: vol = 0x0c000; break; + case 7: vol = 0x0e000; break; + case 8: vol = 0x10000; break; + default: vol = 0x20000; break; + } + + // we dont do repeating or eos-callback for now... + glk_schannel_play_ext(gos_channel, number, 1, 0); + glk_schannel_set_volume(gos_channel, vol); +} + +void GlkInterface::os_stop_sample(int a) { + if (!gos_channel) + return; + glk_schannel_stop(gos_channel); +} + +void GlkInterface::os_beep(int volume) { +} + +void GlkInterface::start_sample(int number, int volume, int repeats, zword eos) { + // TODO +} + +void GlkInterface::start_next_sample() { + // TODO +} + +void GlkInterface::gos_update_width() { + glui32 width; + if (gos_upper) { + glk_window_get_size(gos_upper, &width, nullptr); + h_screen_cols = width; + SET_BYTE(H_SCREEN_COLS, width); + if (curx > width) { + glk_window_move_cursor(gos_upper, 0, cury - 1); + curx = 1; + } + } +} + +void GlkInterface::gos_update_height() { + glui32 height_upper; + glui32 height_lower; + if (gos_curwin) { + glk_window_get_size(gos_upper, nullptr, &height_upper); + glk_window_get_size(gos_lower, nullptr, &height_lower); + h_screen_rows = height_upper + height_lower + 1; + SET_BYTE(H_SCREEN_ROWS, h_screen_rows); + } +} + +void GlkInterface::reset_status_ht() { + glui32 height; + if (gos_upper) { + glk_window_get_size(gos_upper, nullptr, &height); + if (mach_status_ht != height) { + glk_window_set_arrangement( + glk_window_get_parent(gos_upper), + winmethod_Above | winmethod_Fixed, + mach_status_ht, nullptr); + } + } +} + +void GlkInterface::erase_window(zword w) { + if (w == 0) + glk_window_clear(gos_lower); + else if (gos_upper) { +#ifdef GARGLK + garglk_set_reversevideo_stream( + glk_window_get_stream(gos_upper), + true); +#endif /* GARGLK */ + + memset(statusline, ' ', sizeof statusline); + glk_window_clear(gos_upper); + reset_status_ht(); + curr_status_ht = 0; + } +} + +void GlkInterface::split_window(zword lines) { + if (!gos_upper) + return; + + // The top line is always set for V1 to V3 games + if (h_version < V4) + lines++; + + if (!lines || lines > curr_status_ht) { + glui32 height; + + glk_window_get_size(gos_upper, nullptr, &height); + if (lines != height) + glk_window_set_arrangement( + glk_window_get_parent(gos_upper), + winmethod_Above | winmethod_Fixed, + lines, nullptr); + curr_status_ht = lines; + } + mach_status_ht = lines; + if (cury > lines) + { + glk_window_move_cursor(gos_upper, 0, 0); + curx = cury = 1; + } + gos_update_width(); + + if (h_version == V3) + glk_window_clear(gos_upper); +} + +void GlkInterface::restart_screen() { + erase_window(0); + erase_window(1); + split_window(0); +} + +void GlkInterface::packspaces(zchar *src, zchar *dst) { + int killing = 0; + while (*src) { + if (*src == 0x20202020) + *src = ' '; + if (*src == ' ') + killing++; + else + killing = 0; + if (killing > 2) + src++; + else + *dst++ = *src++; + } + + *dst = 0; +} + +void GlkInterface::smartstatusline() { + zchar packed[256]; + zchar buf[256]; + zchar *a, *b, *c, *d; + int roomlen, scorelen, scoreofs; + int len, tmp; + + packspaces(statusline, packed); + len = os_string_length(packed); + + a = packed; + while (a[0] == ' ') + a++; + + b = a; + while (b[0] != 0 && !(b[0] == ' ' && b[1] == ' ')) + b++; + + c = b; + while (c[0] == ' ') + c++; + + d = packed + len - 1; + while (d[0] == ' ' && d > c) + d--; + if (d[0] != ' ' && d[0] != 0) + d++; + if (d < c) + d = c; + + roomlen = b - a; + scorelen = d - c; + scoreofs = h_screen_cols - scorelen - 2; + if (scoreofs <= roomlen) + scoreofs = roomlen + 2; + + for (tmp = 0; tmp < h_screen_cols; tmp++) + buf[tmp] = ' '; + + memcpy(buf + 1 + scoreofs, c, scorelen * sizeof(zchar)); + memcpy(buf + 1, a, roomlen * sizeof(zchar)); + + glk_window_move_cursor(gos_upper, 0, 0); + glk_put_buffer_uni(buf, h_screen_cols); + glk_window_move_cursor(gos_upper, cury - 1, curx - 1); +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/glk_interface.h b/engines/gargoyle/frotz/glk_interface.h new file mode 100644 index 0000000000..ea3f60cfe8 --- /dev/null +++ b/engines/gargoyle/frotz/glk_interface.h @@ -0,0 +1,147 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GARGOYLE_FROTZ_GLK_INTERFACE +#define GARGOYLE_FROTZ_GLK_INTERFACE + +#include "gargoyle/glk.h" +#include "gargoyle/frotz/mem.h" + +namespace Gargoyle { +namespace Frotz { + +enum SoundEffect { + EFFECT_PREPARE = 1, + EFFECT_PLAY = 2, + EFFECT_STOP = 3, + EFFECT_FINISH_WITH = 4 +}; + +/** + * Implements an intermediate interface on top of the GLK layer, providing screen + * and sound effect handling + */ +class GlkInterface : public Glk, public virtual Mem { +public: + zchar statusline[256]; + int oldstyle; + int curstyle; + int cury; + int curx; + int fixforced; + + int curr_fg; + int curr_bg; + int curr_font; + int prev_font; + int temp_font; + + int curr_status_ht; + int mach_status_ht; + + winid_t gos_status; + winid_t gos_upper; + winid_t gos_lower; + winid_t gos_curwin; + int gos_linepending; + zchar *gos_linebuf; + winid_t gos_linewin; + schanid_t gos_channel; + + // Current window and mouse data + int cwin; + int mwin; + int mouse_y; + int mouse_x; + int menu_selected; + + // IO streams + bool ostream_screen; + bool ostream_script; + bool ostream_memory; + bool ostream_record; + bool istream_replay; + bool message; + + // Window attributes + bool enable_wrapping; + bool enable_scripting; + bool enable_scrolling; + bool enable_buffering; + + // Sound fields + int next_sample; + int next_volume; + + bool _soundLocked; + bool _soundPlaying; +protected: + int os_char_width(zchar z); + int os_string_width(const zchar *s); + int os_string_length(zchar *s); + void os_prepare_sample(int a); + void os_finish_with_sample(int a); + + /** + * Play the given sample at the given volume (ranging from 1 to 8 and + * 255 meaning a default volume). The sound is played once or several + * times in the background (255 meaning forever). In Z-code 3 the + * repeats value is always 0 and the number of repeats is taken from + * the sound file itself. The end_of_sound function is called as soon + * as the sound finishes. + */ + void os_start_sample(int number, int volume, int repeats, zword eos); + + void os_stop_sample(int a); + void os_beep(int volume); + + /** + * Call the IO interface to play a sample. + */ + void start_sample(int number, int volume, int repeats, zword eos); + + void start_next_sample(); + void gos_update_width(); + void gos_update_height(); + void reset_status_ht(); + void erase_window(zword w); + void split_window(zword lines); + void restart_screen(); + + /** + * statusline overflowed the window size ... bad game! + * so ... split status text into regions, reformat and print anew. + */ + void packspaces(zchar *src, zchar *dst); + + void smartstatusline(); +public: + /** + * Constructor + */ + GlkInterface(OSystem *syst, const GargoyleGameDescription *gameDesc); +}; + +} // End of namespace Frotz +} // End of namespace Gargoyle + +#endif diff --git a/engines/gargoyle/frotz/mem.cpp b/engines/gargoyle/frotz/mem.cpp index 1b43e93d20..cb5a73b8d1 100644 --- a/engines/gargoyle/frotz/mem.cpp +++ b/engines/gargoyle/frotz/mem.cpp @@ -22,12 +22,13 @@ #include "gargoyle/frotz/mem.h" #include "gargoyle/frotz/frotz.h" +#include "common/memstream.h" #include "common/textconsole.h" namespace Gargoyle { namespace Frotz { -const Mem::StoryEntry Mem::RECORDS[25] = { +const Header::StoryEntry Header::RECORDS[25] = { { SHERLOCK, 21, "871214" }, { SHERLOCK, 26, "880127" }, { BEYOND_ZORK, 47, "870915" }, @@ -55,28 +56,99 @@ const Mem::StoryEntry Mem::RECORDS[25] = { { UNKNOWN, 0, "------" } }; -Mem::Mem() : story_fp(nullptr), blorb_ofs(0), blorb_len(0) { +void Header::loadHeader(Common::SeekableReadStream &f) { + h_version = f.readByte(); + h_config = f.readByte(); + + if (h_version < V1 || h_version > V8) + error("Unknown Z-code version"); + + if (h_version == V6) + error("Cannot play Z-code version 6"); + + if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED)) + error("Byte swapped story file"); + + h_release = f.readUint16BE(); + h_resident_size = f.readUint16BE(); + h_start_pc = f.readUint16BE(); + h_dictionary = f.readUint16BE(); + h_objects = f.readUint16BE(); + h_globals = f.readUint16BE(); + h_dynamic_size = f.readUint16BE(); + h_flags = f.readUint16BE(); + f.read(h_serial, 6); + + /* Auto-detect buggy story files that need special fixes */ + _storyId = UNKNOWN; + + for (int i = 0; RECORDS[i]._storyId != UNKNOWN; ++i) { + if (h_release == RECORDS[i]._release) { + if (!strncmp((const char *)h_serial, RECORDS[i]._serial, 6)) { + _storyId = RECORDS[i]._storyId; + break; + } + } + } + + h_abbreviations = f.readUint16BE(); + h_file_size = f.readUint16BE(); + h_checksum = f.readUint16BE(); + + f.seek(H_FUNCTIONS_OFFSET); + h_functions_offset = f.readUint16BE(); + h_strings_offset = f.readUint16BE(); + f.seek(H_TERMINATING_KEYS); + h_terminating_keys = f.readUint16BE(); + f.seek(H_ALPHABET); + h_alphabet = f.readUint16BE(); + h_extension_table = f.readUint16BE(); + + + // Zork Zero Macintosh doesn't have the graphics flag set + if (_storyId == ZORK_ZERO && h_release == 296) + h_flags |= GRAPHICS_FLAG; +} + +/*--------------------------------------------------------------------------*/ + +Mem::Mem() : story_fp(nullptr), blorb_ofs(0), blorb_len(0), story_size(0) { } void Mem::initialize() { -/* - long size; - zword addr; - unsigned n; - int i, j; - */ initializeStoryFile(); + loadGameHeader(); + + // Allocate memory for story data + if ((zmp = (zbyte *)realloc(zmp, story_size)) == nullptr) + error("Out of memory"); + + // Load story file in chunks of 32KB + uint n = 0x8000; + + for (uint size = 64; size < story_size; size += n) { + if (story_size - size < 0x8000) + n = story_size - size; + + setPC(size); - // TODO: More stuff + if (story_fp->read(pcp, n) != n) + error("Story file read error"); + + } + + // Read header extension table + hx_table_size = get_header_extension(HX_TABLE_SIZE); + hx_unicode_table = get_header_extension(HX_UNICODE_TABLE); + hx_flags = get_header_extension(HX_FLAGS); } void Mem::initializeStoryFile() { - Common::SeekableReadStream *f = g_vm->_gameFile; + Common::SeekableReadStream *f = story_fp; giblorb_map_t *map; giblorb_result_t res; uint32 magic; - story_fp = f; magic = f->readUint32BE(); if (magic == MKTAG('F', 'O', 'R', 'M')) { @@ -101,5 +173,41 @@ void Mem::initializeStoryFile() { error("This file is too small to be a Z-code file."); } +void Mem::loadGameHeader() { + // Load header + zmp = new byte[64]; + story_fp->seek(blorb_ofs); + story_fp->read(zmp, 64); + + Common::MemoryReadStream h(zmp, 64); + loadHeader(h); + + // Calculate story file size in bytes + if (h_file_size != 0) { + story_size = (long)2 * h_file_size; + + if (h_version >= V4) + story_size *= 2; + if (h_version >= V6) + story_size *= 2; + } else { + // Some old games lack the file size entry + story_size = blorb_len; + } +} + +zword Mem::get_header_extension(int entry) { + zword addr; + zword val; + + if (h_extension_table == 0 || entry > hx_table_size) + return 0; + + addr = h_extension_table + 2 * entry; + LOW_WORD(addr, val); + + return val; +} + } // End of namespace Scott } // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/mem.h b/engines/gargoyle/frotz/mem.h index 68c7b85ece..d37cdd3d99 100644 --- a/engines/gargoyle/frotz/mem.h +++ b/engines/gargoyle/frotz/mem.h @@ -28,11 +28,71 @@ namespace Gargoyle { namespace Frotz { +#define SET_WORD(addr,v) zmp[addr] = hi(v); zmp[addr+1] = lo(v) +#define LOW_WORD(addr,v) v = READ_BE_UINT16(&zmp[addr]) +#define HIGH_WORD(addr,v) v = READ_BE_UINT16(&zmp[addr]) +#define HIGH_LONG(addr,v) v = READ_BE_UINT32(&zmp[addr]) +#define SET_BYTE(addr,v) zmp[addr] = v +#define LOW_BYTE(addr,v) v = zmp[addr] + +enum HeaderByte { + H_VERSION = 0, + H_CONFIG = 1, + H_RELEASE = 2, + H_RESIDENT_SIZE = 4, + H_START_PC = 6, + H_DICTIONARY = 8, + H_OBJECTS = 10, + H_GLOBALS = 12, + H_DYNAMIC_SIZE = 14, + H_FLAGS = 16, + H_SERIAL = 18, + H_ABBREVIATIONS = 24, + H_FILE_SIZE = 26, + H_CHECKSUM = 28, + H_INTERPRETER_NUMBER = 30, + H_INTERPRETER_VERSION = 31, + H_SCREEN_ROWS = 32, + H_SCREEN_COLS = 33, + H_SCREEN_WIDTH = 34, + H_SCREEN_HEIGHT = 36, + H_FONT_HEIGHT = 38, ///< this is the font width in V5 + H_FONT_WIDTH = 39, ///< this is the font height in V5 + H_FUNCTIONS_OFFSET = 40, + H_STRINGS_OFFSET = 42, + H_DEFAULT_BACKGROUND = 44, + H_DEFAULT_FOREGROUND = 45, + H_TERMINATING_KEYS = 46, + H_LINE_WIDTH = 48, + H_STANDARD_HIGH = 50, + H_STANDARD_LOW = 51, + H_ALPHABET = 52, + H_EXTENSION_TABLE = 54, + H_USER_NAME = 56 +}; + +enum { + HX_TABLE_SIZE = 0, + HX_MOUSE_X = 1, + HX_MOUSE_Y = 2, + HX_UNICODE_TABLE = 3, + HX_FLAGS = 4, + HX_FORE_COLOUR = 5, + HX_BACK_COLOUR = 6 +}; /** * Story file header data */ struct Header { +private: + struct StoryEntry { + Story _storyId; + zword _release; + char _serial[7]; + }; + static const StoryEntry RECORDS[25]; +public: zbyte h_version; zbyte h_config; zword h_release; @@ -75,6 +135,11 @@ struct Header { zword hx_fore_colour; zword hx_back_colour; + Story _storyId; + + /** + * Constructor + */ Header() : h_version(0), h_config(0), h_release(0), h_resident_size(0), h_start_pc(0), h_dictionary(0), h_objects(0), h_globals(0), h_dynamic_size(0), h_flags(0), h_abbreviations(0), h_file_size(0), h_checksum(0), h_interpreter_number(0), @@ -84,22 +149,22 @@ struct Header { h_terminating_keys(0), h_line_width(0), h_standard_high(1), h_standard_low(1), h_alphabet(0), h_extension_table(0), hx_table_size(0), hx_mouse_x(0), hx_mouse_y(0), hx_unicode_table(0), - hx_flags(0), hx_fore_colour(0), hx_back_colour(0) { + hx_flags(0), hx_fore_colour(0), hx_back_colour(0), _storyId(UNKNOWN) { Common::fill(&h_serial[0], &h_serial[6], '\0'); Common::fill(&h_user_name[0], &h_user_name[8], '\0'); } + + /** + * Load the header + */ + void loadHeader(Common::SeekableReadStream &f); }; class Mem : public Header { - struct StoryEntry { - Story _storyId; - zword _release; - char _serial[7]; - }; - static const StoryEntry RECORDS[25]; -private: +protected: Common::SeekableReadStream *story_fp; uint blorb_ofs, blorb_len; + uint story_size; byte *pcp; byte *zmp; private: @@ -107,6 +172,11 @@ private: * Handles setting the story file, parsing it if it's a Blorb file */ void initializeStoryFile(); + + /** + * Handles loading the game header + */ + void loadGameHeader(); public: /** * Constructor @@ -116,7 +186,7 @@ public: /** * Initialize */ - void initialize(); + virtual void initialize(); /** * Read a word @@ -142,6 +212,11 @@ public: * Set the PC */ void setPC(uint ofs) { pcp = zmp + ofs; } + + /** + * Read a value from the header extension (former mouse table). + */ + zword get_header_extension(int entry); }; } // End of namespace Frotz diff --git a/engines/gargoyle/frotz/processor.cpp b/engines/gargoyle/frotz/processor.cpp new file mode 100644 index 0000000000..1a31898a2a --- /dev/null +++ b/engines/gargoyle/frotz/processor.cpp @@ -0,0 +1,659 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/processor.h" +#include "gargoyle/frotz/frotz.h" + +namespace Gargoyle { +namespace Frotz { + +// TODO: Stubs to replace with actual code +zword save_undo() { return 0; } +zword restore_undo() { return 0; } + + +Opcode Processor::var_opcodes[64] = { + &Processor::__illegal__, + &Processor::z_je, + &Processor::z_jl, + &Processor::z_jg, + &Processor::z_dec_chk, + &Processor::z_inc_chk, + &Processor::z_jin, + &Processor::z_test, + &Processor::z_or, + &Processor::z_and, + &Processor::z_test_attr, + &Processor::z_set_attr, + &Processor::z_clear_attr, + &Processor::z_store, + &Processor::z_insert_obj, + &Processor::z_loadw, + &Processor::z_loadb, + &Processor::z_get_prop, + &Processor::z_get_prop_addr, + &Processor::z_get_next_prop, + &Processor::z_add, + &Processor::z_sub, + &Processor::z_mul, + &Processor::z_div, + &Processor::z_mod, + &Processor::z_call_s, + &Processor::z_call_n, + &Processor::z_set_colour, + &Processor::z_throw, + &Processor::__illegal__, + &Processor::__illegal__, + &Processor::__illegal__, + &Processor::z_call_s, + &Processor::z_storew, + &Processor::z_storeb, + &Processor::z_put_prop, + &Processor::z_read, + &Processor::z_print_char, + &Processor::z_print_num, + &Processor::z_random, + &Processor::z_push, + &Processor::z_pull, + &Processor::z_split_window, + &Processor::z_set_window, + &Processor::z_call_s, + &Processor::z_erase_window, + &Processor::z_erase_line, + &Processor::z_set_cursor, + &Processor::z_get_cursor, + &Processor::z_set_text_style, + &Processor::z_buffer_mode, + &Processor::z_output_stream, + &Processor::z_input_stream, + &Processor::z_sound_effect, + &Processor::z_read_char, + &Processor::z_scan_table, + &Processor::z_not, + &Processor::z_call_n, + &Processor::z_call_n, + &Processor::z_tokenise, + &Processor::z_encode_text, + &Processor::z_copy_table, + &Processor::z_print_table, + &Processor::z_check_arg_count +}; + +Opcode Processor::ext_opcodes[64] = { + &Processor::z_save, + &Processor::z_restore, + &Processor::z_log_shift, + &Processor::z_art_shift, + &Processor::z_set_font, + &Processor::__illegal__, // glkify - Processor::z_draw_picture, + &Processor::__illegal__, // glkify - Processor::z_picture_data, + &Processor::__illegal__, // glkify - Processor::z_erase_picture, + &Processor::__illegal__, // glkify - Processor::z_set_margins, + &Processor::z_save_undo, + &Processor::z_restore_undo, + &Processor::z_print_unicode, + &Processor::z_check_unicode, + &Processor::z_set_true_colour, // spec 1.1 + &Processor::__illegal__, + &Processor::__illegal__, + &Processor::__illegal__, // glkify - Processor::z_move_window, + &Processor::__illegal__, // glkify - Processor::z_window_size, + &Processor::__illegal__, // glkify - Processor::z_window_style, + &Processor::__illegal__, // glkify - Processor::z_get_wind_prop, + &Processor::__illegal__, // glkify - Processor::z_scroll_window, + &Processor::z_pop_stack, + &Processor::__illegal__, // glkify - Processor::z_read_mouse, + &Processor::__illegal__, // glkify - Processor::z_mouse_window, + &Processor::z_push_stack, + &Processor::__illegal__, // glkify - Processor::z_put_wind_prop, + &Processor::z_print_form, + &Processor::z_make_menu, + &Processor::__illegal__, // glkify - Processor::z_picture_table + &Processor::z_buffer_screen, // spec 1.1 +}; + +Processor::Processor(OSystem *syst, const GargoyleGameDescription *gameDesc) : + GlkInterface(syst, gameDesc), Mem(), Errors(), UserOptions(), + _finished(0), _sp(nullptr), _fp(nullptr), _frameCount(0), + zargc(0), _decoded(nullptr), _encoded(nullptr), _resolution(0), + _randomInterval(0), _randomCtr(0), first_restart(true) { + static const Opcode OP0_OPCODES[16] = { + &Processor::z_rtrue, + &Processor::z_rfalse, + &Processor::z_print, + &Processor::z_print_ret, + &Processor::z_nop, + &Processor::z_save, + &Processor::z_restore, + &Processor::z_restart, + &Processor::z_ret_popped, + &Processor::z_catch, + &Processor::z_quit, + &Processor::z_new_line, + &Processor::z_show_status, + &Processor::z_verify, + &Processor::__extended__, + &Processor::z_piracy + }; + static const Opcode OP1_OPCODES[16] = { + &Processor::z_jz, + &Processor::z_get_sibling, + &Processor::z_get_child, + &Processor::z_get_parent, + &Processor::z_get_prop_len, + &Processor::z_inc, + &Processor::z_dec, + &Processor::z_print_addr, + &Processor::z_call_s, + &Processor::z_remove_obj, + &Processor::z_print_obj, + &Processor::z_ret, + &Processor::z_jump, + &Processor::z_print_paddr, + &Processor::z_load, + &Processor::z_call_n + }; + + Common::copy(&OP0_OPCODES[0], &OP0_OPCODES[16], op0_opcodes); + Common::copy(&OP1_OPCODES[0], &OP1_OPCODES[16], op1_opcodes); + Common::fill(&_stack[0], &_stack[STACK_SIZE], 0); + Common::fill(&zargs[0], &zargs[8], 0); +} + +void Processor::initialize() { + Mem::initialize(); + + if (h_version <= V4) { + op0_opcodes[9] = &Processor::z_pop; + op1_opcodes[15] = &Processor::z_not; + } else { + op0_opcodes[9] = &Processor::z_catch; + op1_opcodes[15] = &Processor::z_call_n; + } +} + +void Processor::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 { + // large constant + CODE_WORD(value); + } + + zargs[zargc++] = value; +} + +void Processor::load_all_operands(zbyte specifier) { + for (int i = 6; i >= 0; i -= 2) { + zbyte type = (specifier >> i) & 0x03; + + if (type == 3) + break; + + load_operand(type); + } +} + +void Processor::interpret() { + 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); + + (*this.*var_opcodes[opcode & 0x1f])(); + + } else if (opcode < 0xb0) { + // 1OP opcodes + load_operand((zbyte)(opcode >> 4)); + + (*this.*op1_opcodes[opcode & 0x0f])(); + + } else if (opcode < 0xc0) { + // 0OP opcodes + (*this.*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); + } + + (*this.*var_opcodes[opcode - 0xc0])(); + } + +#if defined(DJGPP) && defined(SOUND_SUPPORT) + if (end_of_sound_flag) + end_of_sound(); +#endif + } while (!_finished); + + _finished--; +} + +void Processor::call(zword routine, int argc, zword *args, int ct) { + long pc; + zword value; + zbyte count; + int i; + + if (_sp - _stack < 4) + runtimeError(ERR_STK_OVF); + + GET_PC(pc); + + *--_sp = (zword)(pc >> 9); + *--_sp = (zword)(pc & 0x1ff); + *--_sp = (zword)(_fp - _stack - 1); + *--_sp = (zword)(argc | (ct << (_save_quetzal ? 12 : 8))); + + _fp = _sp; + _frameCount++; + + // 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 if (h_version <= V8) + pc = (long)routine << 3; + else { + // h_version == V9 + long indirect = (long)routine << 2; + HIGH_LONG(indirect, pc); + } + + if ((uint)pc >= story_size) + runtimeError(ERR_ILL_CALL_ADDR); + + SET_PC(pc); + + // Initialise local variables + CODE_BYTE(count); + + if (count > 15) + runtimeError(ERR_CALL_NON_RTN); + if (_sp - _stack < count) + runtimeError(ERR_STK_OVF); + + if (_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(); +} + +void Processor::ret(zword value) { + long pc; + int ct; + + if (_sp > _fp) + runtimeError(ERR_STK_UNDF); + + _sp = _fp; + + ct = *_sp++ >> (_save_quetzal ? 12 : 8); + _frameCount--; + _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++; +} + +void Processor::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 { + // It's a short branch + offset = off1; + } + + if (specifier & 0x80) { + if (offset > 1) { + // normal branch + GET_PC(pc); + pc += (short)offset - 2; + SET_PC(pc); + } else { + // special case, return 0 or 1 + ret(offset); + } + } +} + +void Processor::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); + } +} + +int Processor::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++; +} + +void Processor::seed_random(int value) { + if (value == 0) { + // Now using random values + _randomInterval = 0; + } else if (value < 1000) { + // special seed value + _randomCtr = 0; + _randomInterval = value; + } else { + // standard seed value + _random.setSeed(value); + _randomInterval = 0; + } +} + +void Processor::__extended__() { + zbyte opcode; + zbyte specifier; + + CODE_BYTE(opcode); + CODE_BYTE(specifier); + + load_all_operands(specifier); + + if (opcode < 0x1e) // extended opcodes from 0x1e on + (*this.*ext_opcodes[opcode])(); // are reserved for future spec' +} + +void Processor::__illegal__() { + runtimeError(ERR_ILL_OPCODE); +} + +void Processor::z_catch() { + store(_save_quetzal ? _frameCount : (zword)(_fp - _stack)); +} + +void Processor::z_throw() { + if (_save_quetzal) { + if (zargs[1] > _frameCount) + runtimeError(ERR_BAD_FRAME); + + // Unwind the stack a frame at a time. + for (; _frameCount > zargs[1]; --_frameCount) + _fp = _stack + 1 + _fp[1]; + } else { + if (zargs[1] > STACK_SIZE) + runtimeError(ERR_BAD_FRAME); + + _fp = _stack + zargs[1]; + } + + ret(zargs[0]); +} + +void Processor::z_call_n() { + if (zargs[0] != 0) + call(zargs[0], zargc - 1, zargs + 1, 1); +} + +void Processor::z_call_s() { + if (zargs[0] != 0) + call(zargs[0], zargc - 1, zargs + 1, 0); + else + store(0); +} + +void Processor::z_check_arg_count() { + if (_fp == _stack + STACK_SIZE) + branch(zargs[0] == 0); + else + branch(zargs[0] <= (*_fp & 0xff)); +} + +void Processor::z_jump() { + long pc; + GET_PC(pc); + + pc += (short)zargs[0] - 2; + + if ((uint)pc >= story_size) + runtimeError(ERR_ILL_JUMP_ADDR); + + SET_PC(pc); +} + +void Processor::z_nop() { + // Do nothing +} + +void Processor::z_quit() { + _finished = 9999; +} + +void Processor::z_ret() { + ret(zargs[0]); +} + +void Processor::z_ret_popped() { + ret(*_sp++); +} + +void Processor::z_rfalse() { + ret(0); +} + +void Processor::z_rtrue() { + ret(1); +} + +void Processor::z_random() { + if ((short) zargs[0] <= 0) { + // set random seed + seed_random(- (short) zargs[0]); + store(0); + + } else { + // generate random number + zword result; + if (_randomInterval != 0) { + // ...in special mode + result = _randomCtr++; + if (_randomCtr == _randomInterval) + _randomCtr = 0; + } else { + // ...in standard mode + result = _random.getRandomNumber(0xffff); + } + + store((zword)(result % zargs[0] + 1)); + } +} + +void Processor::z_sound_effect() { + zword number = zargs[0]; + zword effect = zargs[1]; + zword volume = zargs[2]; + + if (zargc < 1) + number = 0; + if (zargc < 2) + effect = EFFECT_PLAY; + if (zargc < 3) + volume = 8; + + if (number >= 3 || number == 0) { + _soundLocked = true; + + if (_storyId == LURKING_HORROR && (number == 9 || number == 16)) { + if (effect == EFFECT_PLAY) { + next_sample = number; + next_volume = volume; + + _soundLocked = false; + + if (!_soundPlaying) + start_next_sample(); + } else { + _soundLocked = false; + } + return; + } + + _soundPlaying = false; + + switch (effect) { + + case EFFECT_PREPARE: + os_prepare_sample (number); + break; + case EFFECT_PLAY: + start_sample(number, lo (volume), hi (volume), (zargc == 4) ? zargs[3] : 0); + break; + case EFFECT_STOP: + os_stop_sample (number); + break; + case EFFECT_FINISH_WITH: + os_finish_with_sample (number); + break; + } + + _soundLocked = false; + } else { + os_beep(number); + } +} + +void Processor::z_piracy() { + branch(!_piracy); +} + +void Processor::z_save_undo(void) { + store((zword)save_undo()); +} + +void Processor::z_restore_undo(void) { + store((zword)restore_undo()); +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/processor.h b/engines/gargoyle/frotz/processor.h new file mode 100644 index 0000000000..074100abab --- /dev/null +++ b/engines/gargoyle/frotz/processor.h @@ -0,0 +1,1322 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GARGOYLE_FROTZ_PROCESSOR +#define GARGOYLE_FROTZ_PROCESSOR + +#include "gargoyle/frotz/buffer.h" +#include "gargoyle/frotz/err.h" +#include "gargoyle/frotz/mem.h" +#include "gargoyle/frotz/glk_interface.h" + +namespace Gargoyle { +namespace Frotz { + +#define CODE_BYTE(v) v = *pcp++ +#define CODE_WORD(v) v = READ_BE_UINT16(pcp += 2) +#define CODE_IDX_WORD(v,i) v = READ_BE_UINT16(pcp + i) +#define GET_PC(v) v = pcp - zmp +#define SET_PC(v) pcp = zmp + v + + +enum string_type { + LOW_STRING, ABBREVIATION, HIGH_STRING, EMBEDDED_STRING, VOCABULARY +}; + +class Processor; +typedef void (Processor::*Opcode)(); + +/** + * Zcode processor + */ +class Processor : public virtual Mem, public Errors, public GlkInterface, public UserOptions { +private: + Opcode op0_opcodes[16]; + Opcode op1_opcodes[16]; + static Opcode var_opcodes[64]; + static Opcode ext_opcodes[64]; + int _finished; + zword zargs[8]; + int zargc; + uint _randomInterval; + uint _randomCtr; + bool first_restart; + + // Stack data + zword _stack[STACK_SIZE]; + zword *_sp; + zword *_fp; + zword _frameCount; + + // Text related fields + static zchar ZSCII_TO_LATIN1[]; + zchar *_decoded, *_encoded; + int _resolution; +private: + /** + * \defgroup General support methods + * @{ + */ + + /** + * Load an operand, either a variable or a constant. + */ + void load_operand(zbyte type); + + /** + * Given the operand specifier byte, load all (up to four) operands + * for a VAR or EXT opcode. + */ + void load_all_operands(zbyte specifier); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * Store an operand, either as a variable or pushed on the stack. + */ + void store(zword value); + + /* + * 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); + + /** + * Set the seed value for the random number generator. + */ + void seed_random(int value); + + /**@}*/ + + /** + * \defgroup Input support methods + * @{ + */ + + /** + * Check if the given key is an input terminator. + */ + bool is_terminator(zchar key); + + /** + * Ask the user a question; return true if the answer is yes. + */ + bool read_yes_or_no(const char *s); + + /** + * Read a string from the current input stream. + */ + void read_string(int max, zchar *buffer); + + /** + * Ask the user to type in a number and return it. + */ + int read_number(); + + /**@}*/ + + /** + * \defgroup Object support methods + * @{ + */ + + /** + * Calculate the address of an object. + */ + zword object_address(zword obj); + + /** + * Return the address of the given object's name. + */ + zword object_name(zword object); + + /** + * Calculate the start address of the property list associated with an object. + */ + zword first_property(zword obj); + + /** + * Calculate the address of the next property in a property list. + */ + zword next_property(zword prop_addr); + + /** + * Unlink an object from its parent and siblings. + */ + void unlink_object(zword object); + + /**@}*/ + + /** + * \defgroup Screen support methods + * @{ + */ + + void screen_char(zchar c); + void screen_new_line(); + void screen_word(const zchar *s); + void screen_mssg_on(); + void screen_mssg_off(); + + /**@}*/ + + /** + * \defgroup Stream support methods + * @{ + */ + + /** + * Write a single character to the scrollback buffer. + * + */ + void scrollback_char(zchar c); + + /** + * Write a string to the scrollback buffer. + */ + void scrollback_word(const zchar *s); + + /** + * Send an input line to the scrollback buffer. + */ + void scrollback_write_input(const zchar *buf, zchar key); + + /** + * Remove an input line from the scrollback buffer. + */ + void scrollback_erase_input(const zchar *buf); + + /** + * Start printing a "debugging" message. + */ + void stream_mssg_on(); + + /** + * Stop printing a "debugging" message. + */ + void stream_mssg_off(); + + /** + * Send a single character to the output stream. + */ + void stream_char(zchar c); + + /** + * Send a string of characters to the output streams. + */ + void stream_word(const zchar *s); + + /** + * Send a newline to the output streams. + */ + void stream_new_line(); + + /** + * Read a single keystroke from the current input stream. + */ + zchar stream_read_key(zword timeout, zword routine, bool hot_keys); + + /** + * Read a line of input from the current input stream. + */ + zchar stream_read_input(int max, zchar *buf, zword timeout, zword routine, + bool hot_keys, bool no_scripting); + + /**@}*/ + + /** + * \defgroup Text support methods + * @{ + */ + + /** + * Map a ZSCII character into Unicode. + */ + zchar translate_from_zscii(zbyte c); + + /** + * Convert a Unicode character to ZSCII, returning 0 on failure. + */ + zbyte unicode_to_zscii(zchar c); + + /** + * Map a Unicode character onto the ZSCII alphabet. + * + */ + zbyte translate_to_zscii(zchar c); + + /** + * Return a character from one of the three character sets. + */ + zchar alphabet(int set, int index); + + /** + * Find the number of bytes used for dictionary resolution. + */ + void find_resolution(); + + /** + * Copy a ZSCII string from the memory to the global "decoded" string. + */ + void load_string(zword addr, zword length); + + /** + * Encode the Unicode text in the global "decoded" string then write + * the result to the global "encoded" array. (This is used to look up + * words in the dictionary.) Up to V3 the vocabulary resolution is + * two, from V4 it is three, and from V9 it is any number of words. + * Because each word contains three Z-characters, that makes six or + * nine Z-characters respectively. Longer words are chopped to the + * proper size, shorter words are are padded out with 5's. For word + * completion we pad with 0s and 31s, the minimum and maximum + * Z-characters. + */ + void encode_text(int padding); + + /** + * Convert _encoded text to Unicode. The _encoded text consists of 16bit + * words. Every word holds 3 Z-characters (5 bits each) plus a spare + * bit to mark the last word. The Z-characters translate to ZSCII by + * looking at the current current character set. Some select another + * character set, others refer to abbreviations. + * + * There are several different string types: + * + * LOW_STRING - from the lower 64KB (byte address) + * ABBREVIATION - from the abbreviations table (word address) + * HIGH_STRING - from the end of the memory map (packed address) + * EMBEDDED_STRING - from the instruction stream (at PC) + * VOCABULARY - from the dictionary (byte address) + * + * The last type is only used for word completion. + */ + void decode_text(string_type st, zword addr); + + /** + * Print a signed 16bit number. + */ + void print_num(zword value); + + /** + * print_object + * + * Print an object description. + * + */ + void print_object(zword object); + + /** + * Scan a dictionary searching for the given word. The first argument + * can be + * + * 0x00 - find the first word which is >= the given one + * 0x05 - find the word which exactly matches the given one + * 0x1f - find the last word which is <= the given one + * + * The return value is 0 if the search fails. + */ + zword lookup_text(int padding, zword dct); + + /** + * tokenise_text + * + * Translate a single word to a token and append it to the token + * buffer. Every token consists of the address of the dictionary + * entry, the length of the word and the offset of the word from + * the start of the text buffer. Unknown words cause empty slots + * if the flag is set (such that the text can be scanned several + * times with different dictionaries); otherwise they are zero. + * + */ + void tokenise_text(zword text, zword length, zword from, zword parse, zword dct, bool flag); + + /** + * Split an input line into words and translate the words to tokens. + */ + void tokenise_line(zword text, zword token, zword dct, bool flag); + + /** + * Scan the vocabulary to complete the last word on the input line + * (similar to "tcsh" under Unix). The return value is + * + * 2 ==> completion is impossible + * 1 ==> completion is ambiguous + * 0 ==> completion is successful + * + * The function also returns a string in its second argument. In case + * of 2, the string is empty; in case of 1, the string is the longest + * extension of the last word on the input line that is common to all + * possible completions (for instance, if the last word on the input + * is "fo" and its only possible completions are "follow" and "folly" + * then the string is "ll"); in case of 0, the string is an extension + * to the last word that results in the only possible completion. + */ + int completion(const zchar *buffer, zchar *result); + + /** + * Convert a Unicode character to lowercase. + * Taken from Zip2000 by Kevin Bracey. + */ + zchar unicode_tolower(zchar c); + + /**@}*/ +private: + /** + * \defgroup General Opcode methods + * @{ + */ + + /* + * Load and execute an extended opcode. + */ + void __extended__(); + + /* + * Exit game because an unknown opcode has been hit. + */ + void __illegal__(); + + /* + * Store the current _stack frame for later use with z_throw. + * + * no zargs used + */ + void z_catch(); + + /** + * Go back to the given _stack frame and return the given value. + * + * zargs[0] = value to return + * zargs[1] = _stack frame + */ + void z_throw(); + + /* + * 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(); + + /** + * 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(); + + /** + * Branch if subroutine was called with >= n arg's. + * + * zargs[0] = number of arguments + */ + void z_check_arg_count(); + + /** + * Jump unconditionally to the given address. + * + * zargs[0] = PC relative address + */ + void z_jump(); + + /* + * No operation. + * + * no zargs used + */ + void z_nop(); + + /* + * Stop game and exit interpreter. + * + * no zargs used + */ + void z_quit(); + + /* + * Return from a subroutine with the given value. + * + * zargs[0] = value to return + */ + void z_ret(); + + /* + * Return from a subroutine with a value popped off the stack. + * + * no zargs used + */ + void z_ret_popped(); + + /* + * Return from a subroutine with false (0). + * + * no zargs used + */ + void z_rfalse(); + + /* + * Return from a subroutine with true (1). + * + * no zargs used + */ + void z_rtrue(); + + /** + * Store a random number or set the random number seed. + * + * zargs[0] = range (positive) or seed value (negative) + */ + void z_random(); + + /** + * Load / play / stop / discard a sound effect. + * + * zargs[0] = number of bleep (1 or 2) or sample + * zargs[1] = operation to perform (samples only) + * zargs[2] = repeats and volume (play sample only) + * zargs[3] = end-of-sound routine (play sample only, optional) + * + * Note: Volumes range from 1 to 8, volume 255 is the default volume. + * Repeats are stored in the high byte, 255 is infinite loop. + * + */ + void z_sound_effect(); + + /** + * Branch if the story file is a legal copy + */ + void z_piracy(); + + /** + * Save the current Z-machine state for a future undo. + * + * no zargs used + */ + void z_save_undo(); + + /** + * Restore a Z-machine state from memory. + * + * no zargs used + */ + void z_restore_undo(); + + /**@}*/ + + /** + * \defgroup Input Opcode methods + * @{ + */ + + /** + * Add or remove a menu and branch if successful. + * + * zargs[0] = number of menu + * zargs[1] = table of menu entries or 0 to remove menu + */ + void z_make_menu(); + + /** + * Read a line of input and (in V5+) store the terminating key. + * + * zargs[0] = address of text buffer + * zargs[1] = address of token buffer + * zargs[2] = timeout in tenths of a second (optional) + * zargs[3] = packed address of routine to be called on timeout + */ + void z_read(); + + /** + * Read and store a key. + * + * zargs[0] = input device (must be 1) + * zargs[1] = timeout in tenths of a second (optional) + * zargs[2] = packed address of routine to be called on timeout + */ + void z_read_char(); + + /** + * z_read_mouse, write the current mouse status into a table. + * + * zargs[0] = address of table + */ + void z_read_mouse(); + + /**@}*/ + + /** + * \defgroup Math Opcode methods + * @{ + */ + + /** + * 16 bit addition. + * + * zargs[0] = first value + * zargs[1] = second value + */ + void z_add(); + + /** + * Bitwise AND operation. + * + * zargs[0] = first value + * zargs[1] = second value + */ + void z_and(); + + /** + * Arithmetic SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right + */ + void z_art_shift(); + + /** + * Signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + */ + void z_div(); + + /** + * B ranch if the first value equals any of the following. + * + * zargs[0] = first value + * zargs[1] = second value (optional) + * ... + * zargs[3] = fourth value (optional) + */ + void z_je(); + + /** + * Branch if the first value is greater than the second. + * + * zargs[0] = first value + * zargs[1] = second value + */ + void z_jg(); + + /** + * Branch if the first value is less than the second. + * + * zargs[0] = first value + * zargs[1] = second value + */ + void z_jl(); + + /** + * Branch if value is zero. + * + * zargs[0] = value + */ + void z_jz(); + + /** + * Logical SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right (negative) + */ + void z_log_shift(); + + /* + * Remainder after signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + */ + void z_mod(); + + /** + * 16 bit multiplication. + * + * zargs[0] = first value + * zargs[1] = second value + */ + void z_mul(); + + /** + * Bitwise NOT operation. + * + * zargs[0] = value + */ + void z_not(); + + /** + * Bitwise OR operation. + * + * zargs[0] = first value + * zargs[1] = second value + */ + void z_or(); + + /** + * 16 bit substraction. + * + * zargs[0] = first value + * zargs[1] = second value + */ + void z_sub(); + + /** + * Branch if all the flags of a bit mask are set in a value. + * + * zargs[0] = value to be examined + * zargs[1] = bit mask + */ + void z_test(); + + /**@}*/ + + /** + * \defgroup Object Opcode methods + * @{ + */ + + /** + * Branch if the first object is inside the second. + * + * zargs[0] = first object + * zargs[1] = second object + */ + void z_jin(); + + /** + * Store the child of an object. + * + * zargs[0] = object + */ + void z_get_child(); + + /** + * Store the number of the first or next property. + * + * zargs[0] = object + * zargs[1] = address of current property (0 gets the first property) + */ + void z_get_next_prop(); + + /** + * Store the parent of an object. + * + * zargs[0] = object + */ + void z_get_parent(); + + /** + * Store the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + */ + void z_get_prop(); + + /** + * Store the address of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + */ + void z_get_prop_addr(); + + /** + * Store the length of an object property. + * + * zargs[0] = address of property to be examined + */ + void z_get_prop_len(); + + /** + * Store the sibling of an object. + * + * zargs[0] = object + */ + void z_get_sibling(); + + /** + * Make an object the first child of another object. + * + * zargs[0] = object to be moved + * zargs[1] = destination object + */ + void z_insert_obj(); + + /** + * Set the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to set + * zargs[2] = value to set property to + */ + void z_put_prop(); + + /** + * Unlink an object from its parent and siblings. + * + * zargs[0] = object + */ + void z_remove_obj(); + + /** + * Set an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to set + */ + void z_set_attr(); + + /** + * Branch if an object attribute is set. + * + * zargs[0] = object + * zargs[1] = number of attribute to test + */ + void z_test_attr(); + + /** + * Clear an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to be cleared + */ + void z_clear_attr(); + + /**@}*/ + + /** + * \defgroup Screen Opcode methods + * @{ + */ + + /** + * Turn text buffering on/off. + * + * zargs[0] = new text buffering flag (0 or 1) + */ + void z_buffer_mode(); + + /** + * Set the screen buffering mode. + * + * zargs[0] = mode + */ + void z_buffer_screen(); + + /** + * Erase the line starting at the cursor position. + * + * zargs[0] = 1 + #units to erase (1 clears to the end of the line) + */ + void z_erase_line(); + + /** + * Erase a window or the screen to background colour. + * + * zargs[0] = window (-3 current, -2 screen, -1 screen & unsplit) + */ + void z_erase_window(); + + /** + * Write the cursor coordinates into a table. + * + * zargs[0] = address to write information to + */ + void z_get_cursor(); + + /** + * Print ASCII text in a rectangular area. + * + * zargs[0] = address of text to be printed + * zargs[1] = width of rectangular area + * zargs[2] = height of rectangular area (optional) + * zargs[3] = number of char's to skip between lines (optional) + */ + void z_print_table(); + + /** + * Set the foreground and background colours + * to specific RGB colour values. + * + * zargs[0] = foreground colour + * zargs[1] = background colour + * zargs[2] = window (-3 is the current one, optional) + */ + void z_set_true_colour(); + + /** + * Set the foreground and background colours. + * + * zargs[0] = foreground colour + * zargs[1] = background colour + * zargs[2] = window (-3 is the current one, optional) + */ + void z_set_colour(); + + /** + * Set the font for text output and store the previous font. + * + * zargs[0] = number of font or 0 to keep current font + */ + void z_set_font(); + + /** + * Set the cursor position or turn the cursor on/off. + * + * zargs[0] = y-coordinate or -2/-1 for cursor on/off + * zargs[1] = x-coordinate + * zargs[2] = window (-3 is the current one, optional) + */ + void z_set_cursor(); + + /** + * z_set_text_style, set the style for text output. + * + * zargs[0] = style flags to set or 0 to reset text style + */ + void z_set_text_style(); + + /** + * Select the current window. + * + * zargs[0] = window to be selected (-3 is the current one) + */ + void z_set_window(); + + /** + * Display the status line for V1 to V3 games. + * + * no zargs used + */ + void pad_status_line(int column); + + /** + * Display the status line for V1 to V3 games. + * + * no zargs used + */ + void z_show_status(); + + /** + * Split the screen into an upper (1) and lower (0) window. + * + * zargs[0] = height of upper window in screen units (V6) or #lines + */ + void z_split_window(); + + /**@}*/ + + /** + * \defgroup Stream Opcode methods + * @{ + */ + + /** + * Select an input stream. + * + * zargs[0] = input stream to be selected + */ + void z_input_stream(); + + /** + * Open or close an output stream. + * + * zargs[0] = stream to open (positive) or close (negative) + * zargs[1] = address to redirect output to (stream 3 only) + * zargs[2] = width of redirected output (stream 3 only, optional) + */ + void z_output_stream(); + + /** + * Re-load dynamic area, clear the stack and set the PC. + * + * no zargs used + */ + void z_restart(); + + /** + * Save [a part of] the Z-machine state to disk. + * + * zargs[0] = address of memory area to save (optional) + * zargs[1] = number of bytes to save + * zargs[2] = address of suggested file name + */ + void z_save(); + + /** + * Restore [a part of] a Z-machine state from disk + * + * zargs[0] = address of area to restore (optional) + * zargs[1] = number of bytes to restore + * zargs[2] = address of suggested file name + */ + void z_restore(); + + /** + * Check the story file integrity. + * + * no zargs used + */ + void z_verify(); + + /**@}*/ + + /** + * \defgroup Table Opcode methods + * @{ + */ + + /** + * Copy a table or fill it with zeroes. + * + * zargs[0] = address of table + * zargs[1] = destination address or 0 for fill + * zargs[2] = size of table + * + * Note: Copying is safe even when source and destination overlap; but + * if zargs[1] is negative the table _must_ be copied forwards. + */ + void z_copy_table(); + + /** + * Store a value from a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + */ + void z_loadb(); + + /** + * Store a value from a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + */ + void z_loadw(); + + /** + * Find and store the address of a target within a table. + * + * zargs[0] = target value to be searched for + * zargs[1] = address of table + * zargs[2] = number of table entries to check value against + * zargs[3] = type of table (optional, defaults to 0x82) + * + * Note: The table is a word array if bit 7 of zargs[3] is set; otherwise + * it's a byte array. The lower bits hold the address step. + */ + void z_scan_table(); + + /** + * Write a byte into a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + */ + void z_storeb(); + + /** + * Write a word into a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + */ + void z_storew(); + + /**@}*/ + + /** + * \defgroup Text Opcode methods + * @{ + */ + + /** + * Test if a unicode character can be printed (bit 0) and read (bit 1). + * + * zargs[0] = Unicode + */ + void z_check_unicode(); + + /** + * Encode a ZSCII string for use in a dictionary. + * + * zargs[0] = address of text buffer + * zargs[1] = length of ASCII string + * zargs[2] = offset of ASCII string within the text buffer + * zargs[3] = address to store encoded text in + * + * This is a V5+ opcode and therefore the dictionary resolution must be + * three 16bit words. + */ + void z_encode_text(); + + /** + * Print a new line. + * + * no zargs used + * + */ + void z_new_line(); + + /** + * Print a string embedded in the instruction stream. + * + * no zargs used + */ + void z_print(); + + /** + * Print a string from the lower 64KB. + * + * zargs[0] = address of string to print + */ + void z_print_addr(); + + /** + * Print a single ZSCII character. + * + * zargs[0] = ZSCII character to be printed + */ + void z_print_char(); + + /** + * Print a formatted table. + * + * zargs[0] = address of formatted table to be printed + */ + void z_print_form(); + + /** + * Print a signed number. + * + * zargs[0] = number to print + */ + void z_print_num(); + + /** + * Print an object description. + * + * zargs[0] = number of object to be printed + */ + void z_print_obj(); + + /** + * Print the string at the given packed address. + * + * zargs[0] = packed address of string to be printed + */ + void z_print_paddr(); + + /* + * Print the string at PC, print newline then return true. + * + * no zargs used + */ + void z_print_ret(); + + /** + * Print a string of ASCII characters. + */ + void print_string(const char *s); + + /** + * Print unicode character + * + * zargs[0] = Unicode + */ + void z_print_unicode(); + + /** + * Make a lexical analysis of a ZSCII string. + * + * zargs[0] = address of string to analyze + * zargs[1] = address of token buffer + * zargs[2] = address of dictionary (optional) + * zargs[3] = set when unknown words cause empty slots (optional) + */ + void z_tokenise(); + + /**@}*/ + + /** + * \defgroup Variable Opcode methods + * @{ + */ + + /** + * Decrement a variable. + * + * zargs[0] = variable to decrement + */ + void z_dec(); + + /** + * Decrement a variable and branch if now less than value. + * + * zargs[0] = variable to decrement + * zargs[1] = value to check variable against + */ + void z_dec_chk(); + + /** + * Increment a variable. + * + * zargs[0] = variable to increment + */ + void z_inc(); + + /** + * Increment a variable and branch if now greater than value. + * + * zargs[0] = variable to increment + * zargs[1] = value to check variable against + */ + void z_inc_chk(); + + /** + * Store the value of a variable. + * + * zargs[0] = variable to store + */ + void z_load(); + + /** + * Pop a value off the game stack and discard it. + * + * no zargs used + */ + void z_pop(); + + /** + * Pop n values off the game or user stack and discard them. + * + * zargs[0] = number of values to discard + * zargs[1] = address of user stack (optional) + */ + void z_pop_stack(); + + /** + * Pop a value off... + * + * a) ...the game or a user stack and store it (V6) + * + * zargs[0] = address of user stack (optional) + * + * b) ...the game stack and write it to a variable (other than V6) + * + * zargs[0] = variable to write value to + */ + void z_pull(); + + /** + * Push a value onto the game stack. + * + * zargs[0] = value to push onto the stack + */ + void z_push(); + + /** + * Push a value onto a user stack then branch if successful. + * + * zargs[0] = value to push onto the stack + * zargs[1] = address of user stack + */ + void z_push_stack(); + + /** + * Write a value to a variable. + * + * zargs[0] = variable to be written to + * zargs[1] = value to write + */ + void z_store(); + + /**@}*/ +protected: + /** + * Get the PC. Is implemented by the Processor class, which derives from Errors + */ + virtual zword getPC() const { return pcp - zmp; } +public: + /** + * Constructor + */ + Processor(OSystem *syst, const GargoyleGameDescription *gameDesc); + + /** + * Initialization + */ + virtual void initialize() override; + + /** + * Z-code interpreter main loop + */ + void interpret(); +}; + +} // End of namespace Frotz +} // End of namespace Gargoyle + +#endif diff --git a/engines/gargoyle/frotz/processor_input.cpp b/engines/gargoyle/frotz/processor_input.cpp new file mode 100644 index 0000000000..e7f92ab607 --- /dev/null +++ b/engines/gargoyle/frotz/processor_input.cpp @@ -0,0 +1,207 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/processor.h" + +namespace Gargoyle { +namespace Frotz { + +// TODO: Implement method stubs +static zchar stream_read_key(zword, zword, bool) { return 0; } +static zchar stream_read_input(int, zchar *, zword, zword, bool, bool) { return 0;} +static void storeb(zword, zchar) {} +static void storew(zword, zword) {} +static void save_undo() {} +static zword os_read_mouse() { return 0; } + + +#define INPUT_BUFFER_SIZE 200 + +void Processor::z_make_menu() { + // This opcode was only used for the Macintosh version of Journey. + // It controls menus with numbers greater than 2 (menus 0, 1 and 2 + // are system menus). + branch (false); +} + +bool Processor::read_yes_or_no(const char *s) { + zchar key; + + print_string(s); + print_string("? (y/n) >"); + + key = stream_read_key(0, 0, false); + + if (key == 'y' || key == 'Y') { + print_string("y\n"); + return true; + } else { + print_string("n\n"); + return false; + } +} + +void Processor::read_string(int max, zchar *buffer) { + zchar key; + + buffer[0] = 0; + + do { + key = stream_read_input(max, buffer, 0, 0, false, false); + } while (key != ZC_RETURN); +} + +int Processor::read_number() { + zchar buffer[6]; + int value = 0; + int i; + + read_string(5, buffer); + + for (i = 0; buffer[i] != 0; i++) + if (buffer[i] >= '0' && buffer[i] <= '9') + value = 10 * value + buffer[i] - '0'; + + return value; + +} + +void Processor::z_read() { + zchar buffer[INPUT_BUFFER_SIZE]; + zword addr; + zchar key; + zbyte max, size; + zbyte c; + int i; + + // Supply default arguments + if (zargc < 3) + zargs[2] = 0; + + // Get maximum input size + addr = zargs[0]; + + LOW_BYTE(addr, max); + + if (h_version <= V4) + max--; + + if (max >= INPUT_BUFFER_SIZE) + max = INPUT_BUFFER_SIZE - 1; + + // Get initial input size + if (h_version >= V5) { + addr++; + LOW_BYTE(addr, size); + } else { + size = 0; + } + + // Copy initial input to local buffer + for (i = 0; i < size; i++) { + addr++; + LOW_BYTE(addr, c); + buffer[i] = translate_from_zscii(c); + } + buffer[i] = 0; + + // Draw status line for V1 to V3 games + if (h_version <= V3) + z_show_status(); + + // Read input from current input stream + key = stream_read_input( + max, buffer, // buffer and size + zargs[2], // timeout value + zargs[3], // timeout routine + false, // enable hot keys + h_version == V6 // no script in V6 + ); + + if (key == ZC_BAD) + return; + + // Perform save_undo for V1 to V4 games + if (h_version <= V4) + save_undo(); + + // Copy local buffer back to dynamic memory + for (i = 0; buffer[i] != 0; i++) { + if (key == ZC_RETURN) { + buffer[i] = unicode_tolower (buffer[i]); + } + + storeb((zword) (zargs[0] + ((h_version <= V4) ? 1 : 2) + i), translate_to_zscii (buffer[i])); + } + + // Add null character (V1-V4) or write input length into 2nd byte + if (h_version <= V4) + storeb((zword) (zargs[0] + 1 + i), 0); + else + storeb((zword) (zargs[0] + 1), i); + + // Tokenise line if a token buffer is present + if (key == ZC_RETURN && zargs[1] != 0) + tokenise_line (zargs[0], zargs[1], 0, false); + + // Store key + if (h_version >= V5) + store(translate_to_zscii(key)); +} + +void Processor::z_read_char() { + zchar key; + + // Supply default arguments + if (zargc < 2) + zargs[1] = 0; + + // Read input from the current input stream + key = stream_read_key( + zargs[1], // timeout value + zargs[2], // timeout routine + false // enable hot keys + ); + + if (key == ZC_BAD) + return; + + // Store key + store (translate_to_zscii (key)); +} + +void Processor::z_read_mouse(){ + zword btn; + + // Read the mouse position, the last menu click and which buttons are down + btn = os_read_mouse(); + hx_mouse_y = mouse_y; + hx_mouse_x = mouse_x; + + storew((zword) (zargs[0] + 0), hx_mouse_y); + storew((zword) (zargs[0] + 2), hx_mouse_x); + storew((zword) (zargs[0] + 4), btn); // mouse button bits + storew((zword) (zargs[0] + 6), menu_selected); // menu selection +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/processor_maths.cpp b/engines/gargoyle/frotz/processor_maths.cpp new file mode 100644 index 0000000000..e64dd55a1a --- /dev/null +++ b/engines/gargoyle/frotz/processor_maths.cpp @@ -0,0 +1,105 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/processor.h" + +namespace Gargoyle { +namespace Frotz { + +void Processor::z_add() { + store((zword)((short)zargs[0] + (short)zargs[1])); +} + +void Processor::z_and() { + store((zword)(zargs[0] & zargs[1])); +} + +void Processor::z_art_shift() { + if ((short)zargs[1] > 0) + store((zword)((short)zargs[0] << (short)zargs[1])); + else + store((zword)((short)zargs[0] >> - (short)zargs[1])); +} + +void Processor::z_div() { + if (zargs[1] == 0) + runtimeError(ERR_DIV_ZERO); + + store((zword)((short)zargs[0] / (short)zargs[1])); +} + +void Processor::z_je() { + branch( + zargc > 1 && (zargs[0] == zargs[1] || ( + zargc > 2 && (zargs[0] == zargs[2] || ( + zargc > 3 && (zargs[0] == zargs[3]))))) + ); +} + +void Processor::z_jg() { + branch((short)zargs[0] > (short)zargs[1]); +} + +void Processor::z_jl() { + branch((short)zargs[0] < (short)zargs[1]); +} + +void Processor::z_jz() { + branch((short)zargs[0] == 0); +} + +void Processor::z_log_shift() { + if ((short)zargs[1] > 0) + store((zword)(zargs[0] << (short)zargs[1])); + else + store((zword)(zargs[0] >> - (short)zargs[1])); +} + +void Processor::z_mod() { + if (zargs[1] == 0) + runtimeError(ERR_DIV_ZERO); + + store((zword)((short)zargs[0] % (short)zargs[1])); +} + +void Processor::z_mul() { + store((zword)((short)zargs[0] * (short)zargs[1])); +} + +void Processor::z_not() { + store((zword)~zargs[0]); +} + +void Processor::z_or() { + store((zword)(zargs[0] | zargs[1])); +} + +void Processor::z_sub() { + store((zword)((short)zargs[0] - (short)zargs[1])); +} + +void Processor::z_test() { + branch((zargs[0] & zargs[1]) == zargs[1]); +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/processor_objects.cpp b/engines/gargoyle/frotz/processor_objects.cpp new file mode 100644 index 0000000000..3d2b17162d --- /dev/null +++ b/engines/gargoyle/frotz/processor_objects.cpp @@ -0,0 +1,735 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/processor.h" + +namespace Gargoyle { +namespace Frotz { + +// TODO: Method stubs to implement +static void new_line() {} + +#define MAX_OBJECT 2000 + +enum O1 { + O1_PARENT = 4, + O1_SIBLING = 5, + O1_CHILD = 6, + O1_PROPERTY_OFFSET = 7, + O1_SIZE = 9 +}; + +enum O4 { + O4_PARENT = 6, + O4_SIBLING = 8, + O4_CHILD = 10, + O4_PROPERTY_OFFSET = 12, + O4_SIZE = 14 +}; + +zword Processor::object_address(zword obj) { + // Check object number + if (obj > ((h_version <= V3) ? 255 : MAX_OBJECT)) { + print_string("@Attempt to address illegal object "); + print_num(obj); + print_string(". This is normally fatal."); + new_line(); + runtimeError(ERR_ILL_OBJ); + } + + // Return object address + if (h_version <= V3) + return h_objects + ((obj - 1) * O1_SIZE + 62); + else + return h_objects + ((obj - 1) * O4_SIZE + 126); +} + +zword Processor::object_name(zword object) { + zword obj_addr; + zword name_addr; + + obj_addr = object_address(object); + + // The object name address is found at the start of the properties + if (h_version <= V3) + obj_addr += O1_PROPERTY_OFFSET; + else + obj_addr += O4_PROPERTY_OFFSET; + + LOW_WORD(obj_addr, name_addr); + + return name_addr; +} + +zword Processor::first_property(zword obj) { + zword prop_addr; + zbyte size; + + // Fetch address of object name + prop_addr = object_name (obj); + + // Get length of object name + LOW_BYTE(prop_addr, size); + + // Add name length to pointer + return prop_addr + 1 + 2 * size; +} + +zword Processor::next_property(zword prop_addr) { + zbyte value; + + // Load the current property id + LOW_BYTE(prop_addr, value); + prop_addr++; + + // Calculate the length of this property + if (h_version <= V3) + value >>= 5; + else if (!(value & 0x80)) + value >>= 6; + else { + LOW_BYTE(prop_addr, value); + value &= 0x3f; + + if (value == 0) + // demanded by Spec 1.0 + value = 64; + } + + // Add property length to current property pointer + return prop_addr + value + 1; +} + +void Processor::unlink_object(zword object) { + zword obj_addr; + zword parent_addr; + zword sibling_addr; + + if (object == 0) { + runtimeError(ERR_REMOVE_OBJECT_0); + return; + } + + obj_addr = object_address(object); + + if (h_version <= V3) { + + zbyte parent; + zbyte younger_sibling; + zbyte older_sibling; + zbyte zero = 0; + + // Get parent of object, and return if no parent + obj_addr += O1_PARENT; + LOW_BYTE(obj_addr, parent); + if (!parent) + return; + + // Get (older) sibling of object and set both parent and sibling pointers to 0 + SET_BYTE(obj_addr, zero); + obj_addr += O1_SIBLING - O1_PARENT; + LOW_BYTE(obj_addr, older_sibling); + SET_BYTE(obj_addr, zero); + + // Get first child of parent (the youngest sibling of the object) + parent_addr = object_address(parent) + O1_CHILD; + LOW_BYTE(parent_addr, younger_sibling); + + // Remove object from the list of siblings + if (younger_sibling == object) + SET_BYTE(parent_addr, older_sibling); + else { + do { + sibling_addr = object_address(younger_sibling) + O1_SIBLING; + LOW_BYTE(sibling_addr, younger_sibling); + } while (younger_sibling != object); + SET_BYTE(sibling_addr, older_sibling); + } + } else { + zword parent; + zword younger_sibling; + zword older_sibling; + zword zero = 0; + + // Get parent of object, and return if no parent + obj_addr += O4_PARENT; + LOW_WORD(obj_addr, parent); + if (!parent) + return; + + // Get (older) sibling of object and set both parent and sibling pointers to 0 + SET_WORD(obj_addr, zero); + obj_addr += O4_SIBLING - O4_PARENT; + LOW_WORD(obj_addr, older_sibling); + SET_WORD(obj_addr, zero); + + // Get first child of parent (the youngest sibling of the object) + parent_addr = object_address(parent) + O4_CHILD; + LOW_WORD(parent_addr, younger_sibling); + + // Remove object from the list of siblings + if (younger_sibling == object) { + SET_WORD(parent_addr, older_sibling); + } else { + do { + sibling_addr = object_address(younger_sibling) + O4_SIBLING; + LOW_WORD(sibling_addr, younger_sibling); + } while (younger_sibling != object); + SET_WORD(sibling_addr, older_sibling); + } + } +} + +void Processor::z_clear_attr() { + zword obj_addr; + zbyte value; + + if (_storyId == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtimeError(ERR_ILL_ATTR); + + // If we are monitoring attribute assignment display a short note + if (_attribute_assignment) { + stream_mssg_on(); + print_string("@clear_attr "); + print_object(zargs[0]); + print_string(" "); + print_num(zargs[1]); + stream_mssg_off(); + } + + if (zargs[0] == 0) { + runtimeError(ERR_CLEAR_ATTR_0); + return; + } + + // Get attribute address + obj_addr = object_address(zargs[0]) + zargs[1] / 8; + + // Clear attribute bit + LOW_BYTE(obj_addr, value); + value &= ~(0x80 >> (zargs[1] & 7)); + SET_BYTE(obj_addr, value); +} + +void Processor::z_jin() { + zword obj_addr; + + // If we are monitoring object locating display a short note + if (_object_locating) { + stream_mssg_on(); + print_string("@jin "); + print_object(zargs[0]); + print_string(" "); + print_object(zargs[1]); + stream_mssg_off(); + } + + if (zargs[0] == 0) { + runtimeError(ERR_JIN_0); + branch(0 == zargs[1]); + return; + } + + obj_addr = object_address(zargs[0]); + + if (h_version <= V3) { + zbyte parent; + + // Get parent id from object + obj_addr += O1_PARENT; + LOW_BYTE(obj_addr, parent); + + // Branch if the parent is obj2 + branch(parent == zargs[1]); + + } else { + zword parent; + + // Get parent id from object + obj_addr += O4_PARENT; + LOW_WORD(obj_addr, parent); + + // Branch if the parent is obj2 + branch(parent == zargs[1]); + } +} + +void Processor::z_get_child() { + zword obj_addr; + + // If we are monitoring object locating display a short note + if (_object_locating) { + stream_mssg_on(); + print_string("@get_child "); + print_object(zargs[0]); + stream_mssg_off(); + } + + if (zargs[0] == 0) { + runtimeError(ERR_GET_CHILD_0); + store(0); + branch(false); + return; + } + + obj_addr = object_address(zargs[0]); + + if (h_version <= V3) { + zbyte child; + + // Get child id from object + obj_addr += O1_CHILD; + LOW_BYTE(obj_addr, child); + + // Store child id and branch + store(child); + branch(child); + } else { + zword child; + + // Get child id from object + obj_addr += O4_CHILD; + LOW_WORD(obj_addr, child); + + // Store child id and branch + store(child); + branch(child); + } +} + +void Processor::z_get_next_prop() { + zword prop_addr; + zbyte value; + zbyte mask; + + if (zargs[0] == 0) { + runtimeError(ERR_GET_NEXT_PROP_0); + store(0); + return; + } + + // Property id is in bottom five (six) bits + mask = (h_version <= V3) ? 0x1f : 0x3f; + + // Load address of first property + prop_addr = first_property(zargs[0]); + + if (zargs[1] != 0) { + // Scan down the property list + do { + LOW_BYTE(prop_addr, value); + prop_addr = next_property(prop_addr); + } while ((value & mask) > zargs[1]); + + // Exit if the property does not exist + if ((value & mask) != zargs[1]) + runtimeError(ERR_NO_PROP); + } + + // Return the property id + LOW_BYTE(prop_addr, value); + store((zword) (value & mask)); +} + +void Processor::z_get_parent() { + zword obj_addr; + + // If we are monitoring object locating display a short note + if (_object_locating) { + stream_mssg_on(); + print_string("@get_parent "); + print_object(zargs[0]); + stream_mssg_off(); + } + + if (zargs[0] == 0) { + runtimeError(ERR_GET_PARENT_0); + store(0); + return; + } + + obj_addr = object_address(zargs[0]); + + if (h_version <= V3) { + zbyte parent; + + // Get parent id from object + obj_addr += O1_PARENT; + LOW_BYTE(obj_addr, parent); + + // Store parent + store(parent); + + } else { + zword parent; + + // Get parent id from object + obj_addr += O4_PARENT; + LOW_WORD(obj_addr, parent); + + // Store parent + store(parent); + } +} + +void Processor::z_get_prop() { + zword prop_addr; + zword wprop_val; + zbyte bprop_val; + zbyte value; + zbyte mask; + + if (zargs[0] == 0) { + runtimeError(ERR_GET_PROP_0); + store(0); + return; + } + + // Property id is in bottom five (six) bits + mask = (h_version <= V3) ? 0x1f : 0x3f; + + // Load address of first property + prop_addr = first_property(zargs[0]); + + // Scan down the property list + for (;;) { + LOW_BYTE(prop_addr, value); + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property(prop_addr); + } + + if ((value & mask) == zargs[1]) { + // property found + + // Load property(byte or word sized) + prop_addr++; + + if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) { + LOW_BYTE(prop_addr, bprop_val); + wprop_val = bprop_val; + } else { + LOW_WORD(prop_addr, wprop_val); + } + } else { + // property not found + + // Load default value + prop_addr = h_objects + 2 * (zargs[1] - 1); + LOW_WORD(prop_addr, wprop_val); + } + + // Store the property value + store(wprop_val); +} + +void Processor::z_get_prop_addr() { + zword prop_addr; + zbyte value; + zbyte mask; + + if (zargs[0] == 0) { + runtimeError(ERR_GET_PROP_ADDR_0); + store(0); + return; + } + + if (_storyId == BEYOND_ZORK) + if (zargs[0] > MAX_OBJECT) + { store(0); return; } + + // Property id is in bottom five (six) bits + mask = (h_version <= V3) ? 0x1f : 0x3f; + + // Load address of first property + prop_addr = first_property(zargs[0]); + + // Scan down the property list + for (;;) { + LOW_BYTE(prop_addr, value); + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property(prop_addr); + } + + // Calculate the property address or return zero + if ((value & mask) == zargs[1]) { + + if (h_version >= V4 && (value & 0x80)) + prop_addr++; + store((zword) (prop_addr + 1)); + + } else { + store(0); + } +} + +void Processor::z_get_prop_len() { + zword addr; + zbyte value; + + // Back up the property pointer to the property id + addr = zargs[0] - 1; + LOW_BYTE(addr, value); + + // Calculate length of property + if (h_version <= V3) + value = (value >> 5) + 1; + else if (!(value & 0x80)) + value = (value >> 6) + 1; + else { + value &= 0x3f; + + if (value == 0) + value = 64; // demanded by Spec 1.0 + } + + // Store length of property + store(value); +} + +void Processor::z_get_sibling() { + zword obj_addr; + + if (zargs[0] == 0) { + runtimeError(ERR_GET_SIBLING_0); + store(0); + branch(false); + return; + } + + obj_addr = object_address(zargs[0]); + + if (h_version <= V3) { + zbyte sibling; + + // Get sibling id from object + obj_addr += O1_SIBLING; + LOW_BYTE(obj_addr, sibling); + + // Store sibling and branch + store(sibling); + branch(sibling); + + } else { + zword sibling; + + // Get sibling id from object + obj_addr += O4_SIBLING; + LOW_WORD(obj_addr, sibling); + + // Store sibling and branch + store(sibling); + branch(sibling); + } +} + +void Processor::z_insert_obj() { + zword obj1 = zargs[0]; + zword obj2 = zargs[1]; + zword obj1_addr; + zword obj2_addr; + + // If we are monitoring object movements display a short note + if (_object_movement) { + stream_mssg_on(); + print_string("@move_obj "); + print_object(obj1); + print_string(" "); + print_object(obj2); + stream_mssg_off(); + } + + if (obj1 == 0) { + runtimeError(ERR_MOVE_OBJECT_0); + return; + } + + if (obj2 == 0) { + runtimeError(ERR_MOVE_OBJECT_TO_0); + return; + } + + // Get addresses of both objects + obj1_addr = object_address(obj1); + obj2_addr = object_address(obj2); + + // Remove object 1 from current parent + unlink_object(obj1); + + // Make object 1 first child of object 2 + if (h_version <= V3) { + zbyte child; + + obj1_addr += O1_PARENT; + SET_BYTE(obj1_addr, obj2); + obj2_addr += O1_CHILD; + LOW_BYTE(obj2_addr, child); + SET_BYTE(obj2_addr, obj1); + obj1_addr += O1_SIBLING - O1_PARENT; + SET_BYTE(obj1_addr, child); + + } else { + zword child; + + obj1_addr += O4_PARENT; + SET_WORD(obj1_addr, obj2); + obj2_addr += O4_CHILD; + LOW_WORD(obj2_addr, child); + SET_WORD(obj2_addr, obj1); + obj1_addr += O4_SIBLING - O4_PARENT; + SET_WORD(obj1_addr, child); + } +} + +void Processor::z_put_prop() { + zword prop_addr; + zword value; + zbyte mask; + + if (zargs[0] == 0) { + runtimeError(ERR_PUT_PROP_0); + return; + } + + // Property id is in bottom five or six bits + mask = (h_version <= V3) ? 0x1f : 0x3f; + + // Load address of first property + prop_addr = first_property(zargs[0]); + + // Scan down the property list + for (;;) { + LOW_BYTE(prop_addr, value); + if ((value & mask) <= zargs[1]) + break; + + prop_addr = next_property(prop_addr); + } + + // Exit if the property does not exist + if ((value & mask) != zargs[1]) + runtimeError(ERR_NO_PROP); + + // Store the new property value (byte or word sized) + prop_addr++; + + if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) { + zbyte v = zargs[2]; + SET_BYTE(prop_addr, v); + } else { + zword v = zargs[2]; + SET_WORD(prop_addr, v); + } +} + +void Processor::z_remove_obj() { + // If we are monitoring object movements display a short note + if (_object_movement) { + stream_mssg_on(); + print_string("@remove_obj "); + print_object(zargs[0]); + stream_mssg_off(); + } + + // Call unlink_object to do the job + unlink_object(zargs[0]); +} + +void Processor::z_set_attr() { + zword obj_addr; + zbyte value; + + if (_storyId == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtimeError(ERR_ILL_ATTR); + + // If we are monitoring attribute assignment display a short note + if (_attribute_assignment) { + stream_mssg_on(); + print_string("@set_attr "); + print_object(zargs[0]); + print_string(" "); + print_num(zargs[1]); + stream_mssg_off(); + } + + if (zargs[0] == 0) { + runtimeError(ERR_SET_ATTR_0); + return; + } + + // Get attribute address + obj_addr = object_address(zargs[0]) + zargs[1] / 8; + + // Load attribute byte + LOW_BYTE(obj_addr, value); + + // Set attribute bit + value |= 0x80 >> (zargs[1] & 7); + + // Store attribute byte + SET_BYTE(obj_addr, value); +} + +void Processor::z_test_attr() { + zword obj_addr; + zbyte value; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtimeError(ERR_ILL_ATTR); + + // If we are monitoring attribute testing display a short note + if (_attribute_testing) { + stream_mssg_on(); + print_string("@test_attr "); + print_object(zargs[0]); + print_string(" "); + print_num(zargs[1]); + stream_mssg_off(); + } + + if (zargs[0] == 0) { + runtimeError(ERR_TEST_ATTR_0); + branch(false); + return; + } + + // Get attribute address + obj_addr = object_address(zargs[0]) + zargs[1] / 8; + + // Load attribute byte + LOW_BYTE(obj_addr, value); + + // Test attribute + branch(value & (0x80 >> (zargs[1] & 7))); +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/processor_screen.cpp b/engines/gargoyle/frotz/processor_screen.cpp new file mode 100644 index 0000000000..5b3b8cb379 --- /dev/null +++ b/engines/gargoyle/frotz/processor_screen.cpp @@ -0,0 +1,455 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/processor.h" + +namespace Gargoyle { +namespace Frotz { + +// TODO: Method stubs to implement +static void storew(zword, zword) {} +static void print_char(zchar) {} + + +void Processor::screen_char(zchar c) { + // TODO +} + +void Processor::screen_new_line() { + // TODO +} + +void Processor::screen_word(const zchar *s) { + // TODO +} + +void Processor::screen_mssg_on() { + // TODO +} + +void Processor::screen_mssg_off() { + // TODO +} + + +void Processor::z_buffer_mode() { +} + +void Processor::z_buffer_screen() { + store(0); +} + +void Processor::z_erase_line() { + int i; + + if (gos_upper && gos_curwin == gos_upper) { + for (i = 0; i < h_screen_cols + 1 - curx; i++) + glk_put_char(' '); + glk_window_move_cursor(gos_curwin, curx - 1, cury - 1); + } +} + +void Processor::z_erase_window() { + short w = zargs[0]; + if (w == -2) + { + if (gos_upper) { + glk_set_window(gos_upper); +#ifdef GARGLK + garglk_set_zcolors(curr_fg, curr_bg); +#endif /* GARGLK */ + glk_window_clear(gos_upper); + glk_set_window(gos_curwin); + } + glk_window_clear(gos_lower); + } + if (w == -1) + { + if (gos_upper) { + glk_set_window(gos_upper); +#ifdef GARGLK + garglk_set_zcolors(curr_fg, curr_bg); +#endif /* GARGLK */ + glk_window_clear(gos_upper); + } + glk_window_clear(gos_lower); + split_window(0); + glk_set_window(gos_lower); + gos_curwin = gos_lower; + } + if (w == 0) + glk_window_clear(gos_lower); + if (w == 1 && gos_upper) + glk_window_clear(gos_upper); +} + +void Processor::z_get_cursor() { + storew((zword) (zargs[0] + 0), cury); + storew((zword) (zargs[0] + 2), curx); +} + +void Processor::z_print_table() { + zword addr = zargs[0]; + zword x; + int i, j; + + // Supply default arguments + if (zargc < 3) + zargs[2] = 1; + if (zargc < 4) + zargs[3] = 0; + + // Write text in width x height rectangle + x = curx; + + for (i = 0; i < zargs[2]; i++) { + if (i != 0) { + cury += 1; + curx = x; + } + + for (j = 0; j < zargs[1]; j++) { + + zbyte c; + + LOW_BYTE(addr, c); + addr++; + + print_char(c); + } + + addr += zargs[3]; + } +} + +#define zB(i) ((((i >> 10) & 0x1F) << 3) | (((i >> 10) & 0x1F) >> 2)) +#define zG(i) ((((i >> 5) & 0x1F) << 3) | (((i >> 5) & 0x1F) >> 2)) +#define zR(i) ((((i ) & 0x1F) << 3) | (((i ) & 0x1F) >> 2)) + +#define zRGB(i) (zR(i) << 16 | zG(i) << 8 | zB(i)) + +void Processor::z_set_true_colour() { + int zfore = zargs[0]; + int zback = zargs[1]; + + if (!(zfore < 0)) + zfore = zRGB(zargs[0]); + + if (!(zback < 0)) + zback = zRGB(zargs[1]); + +#ifdef GARGLK + garglk_set_zcolors(zfore, zback); +#endif /* GARGLK */ + + curr_fg = zfore; + curr_bg = zback; +} + +static const int zcolor_map[] = { + -2, ///< 0 = current + -1, ///< 1 = default + 0x0000, ///< 2 = black + 0x001D, ///< 3 = red + 0x0340, ///< 4 = green + 0x03BD, ///< 5 = yellow + 0x59A0, ///< 6 = blue + 0x7C1F, ///< 7 = magenta + 0x77A0, ///< 8 = cyan + 0x7FFF, ///< 9 = white + 0x5AD6, ///< 10 = light grey + 0x4631, ///< 11 = medium grey + 0x2D6B, ///< 12 = dark grey +}; + +#define zcolor_NUMCOLORS (13) + +void Processor::z_set_colour() { + int zfore = zargs[0]; + int zback = zargs[1]; + + switch (zfore) { + case -1: + zfore = -3; + break; + + case 0: + case 1: + zfore = zcolor_map[zfore]; + break; + + default: + if (zfore < zcolor_NUMCOLORS) + zfore = zRGB(zcolor_map[zfore]); + break; + } + + switch (zback) { + case -1: + zback = -3; + + case 0: + case 1: + zback = zcolor_map[zback]; + break; + + default: + if (zback < zcolor_NUMCOLORS) + zback = zRGB(zcolor_map[zback]); + break; + } + +#ifdef GARGLK + garglk_set_zcolors(zfore, zback); +#endif /* GARGLK */ + + curr_fg = zfore; + curr_bg = zback; +} + +void Processor::z_set_font() { + zword font = zargs[0]; + + switch (font) { + case 0: + // previous font + temp_font = curr_font; + curr_font = prev_font; + prev_font = temp_font; + zargs[0] = 0xf000; // tickle tickle! + z_set_text_style(); + store (curr_font); + break; + + case 1: /* normal font */ + prev_font = curr_font; + curr_font = 1; + zargs[0] = 0xf000; // tickle tickle! + z_set_text_style(); + store (prev_font); + break; + + case 4: /* fixed-pitch font*/ + prev_font = curr_font; + curr_font = 4; + zargs[0] = 0xf000; // tickle tickle! + z_set_text_style(); + store (prev_font); + break; + + case 2: // picture font, undefined per 1.1 + case 3: // character graphics font + default: // unavailable + store (0); + break; + } +} + +void Processor::z_set_cursor() { + cury = zargs[0]; + curx = zargs[1]; + + if (gos_upper) { + if (cury > mach_status_ht) { + mach_status_ht = cury; + reset_status_ht(); + } + + glk_window_move_cursor(gos_upper, curx - 1, cury - 1); + } +} + +void Processor::z_set_text_style() { + int style; + + if (zargs[0] == 0) + curstyle = 0; + else if (zargs[0] != 0xf000) /* not tickle time */ + curstyle |= zargs[0]; + + if (h_flags & FIXED_FONT_FLAG || curr_font == 4) + style = curstyle | FIXED_WIDTH_STYLE; + else + style = curstyle; + + if (gos_linepending && gos_curwin == gos_linewin) + return; + + if (style & REVERSE_STYLE) { +#ifdef GARGLK + garglk_set_reversevideo(true); +#endif /* GARGLK */ + } + + if (style & FIXED_WIDTH_STYLE) { + if (style & BOLDFACE_STYLE && style & EMPHASIS_STYLE) + glk_set_style(style_BlockQuote); // monoz + else if (style & EMPHASIS_STYLE) + glk_set_style(style_Alert); // monoi + else if (style & BOLDFACE_STYLE) + glk_set_style(style_Subheader); // monob + else + glk_set_style(style_Preformatted); // monor + } else { + if (style & BOLDFACE_STYLE && style & EMPHASIS_STYLE) + glk_set_style(style_Note); // propz + else if (style & EMPHASIS_STYLE) + glk_set_style(style_Emphasized); // propi + else if (style & BOLDFACE_STYLE) + glk_set_style(style_Header); // propb + else + glk_set_style(style_Normal); // propr + } + + if (curstyle == 0) { +#ifdef GARGLK + garglk_set_reversevideo(false); +#endif /* GARGLK */ + } +} + +void Processor::z_set_window() { + int win = zargs[0]; + + if (win == 0) { + glk_set_window(gos_lower); + gos_curwin = gos_lower; + } else { + if (gos_upper) + glk_set_window(gos_upper); + gos_curwin = gos_upper; + } + + if (win == 0) + enable_scripting = true; + else + enable_scripting = false; + + zargs[0] = 0xf000; // tickle tickle! + z_set_text_style(); +} + +void Processor::pad_status_line(int column) { + int spaces; + spaces = (h_screen_cols + 1 - curx) - column; + while (spaces-- > 0) + print_char(' '); +} + +void Processor::z_show_status() { + zword global0; + zword global1; + zword global2; + zword addr; + + bool brief = false; + + if (!gos_upper) + return; + + // One V5 game (Wishbringer Solid Gold) contains this opcode by accident, + // so just return if the version number does not fit + if (h_version >= V4) + return; + + // Read all relevant global variables from the memory of the Z-machine + // into local variables + + addr = h_globals; + LOW_WORD(addr, global0); + addr += 2; + LOW_WORD(addr, global1); + addr += 2; + LOW_WORD(addr, global2); + + // Move to top of the status window, and print in reverse style. + glk_set_window(gos_upper); + gos_curwin = gos_upper; + +#ifdef GARGLK + garglk_set_reversevideo(true); +#endif /* GARGLK */ + + curx = cury = 1; + glk_window_move_cursor(gos_upper, 0, 0); + + // If the screen width is below 55 characters then we have to use + // the brief status line format + if (h_screen_cols < 55) + brief = true; + + // Print the object description for the global variable 0 + print_char (' '); + print_object (global0); + + // A header flag tells us whether we have to display the current + // time or the score/moves information + if (h_config & CONFIG_TIME) { + // print hours and minutes + zword hours = (global1 + 11) % 12 + 1; + + pad_status_line (brief ? 15 : 20); + + print_string ("Time: "); + + if (hours < 10) + print_char (' '); + print_num (hours); + + print_char (':'); + + if (global2 < 10) + print_char ('0'); + print_num (global2); + + print_char (' '); + + print_char ((global1 >= 12) ? 'p' : 'a'); + print_char ('m'); + + } else { + // print score and moves + pad_status_line (brief ? 15 : 30); + + print_string (brief ? "S: " : "Score: "); + print_num (global1); + + pad_status_line (brief ? 8 : 14); + + print_string (brief ? "M: " : "Moves: "); + print_num (global2); + } + + // Pad the end of the status line with spaces + pad_status_line (0); + + // Return to the lower window + glk_set_window(gos_lower); + gos_curwin = gos_lower; +} + +void Processor::z_split_window() { + split_window(zargs[0]); +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/processor_streams.cpp b/engines/gargoyle/frotz/processor_streams.cpp new file mode 100644 index 0000000000..5fe526fbe4 --- /dev/null +++ b/engines/gargoyle/frotz/processor_streams.cpp @@ -0,0 +1,533 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/processor.h" + +namespace Gargoyle { +namespace Frotz { + +// TODO: Implement method stubs +static void os_scrollback_char(zchar) {} +static void os_scrollback_erase(zword) {} +static void flush_buffer() {} +static void script_open() {} +static void script_close() {} +static void script_mssg_on() {} +static void script_mssg_off() {} +static void script_char(zchar) {} +static void script_word(const zchar *) {} +static void script_new_line() {} +static void script_erase_input(const zchar *) {} +static void script_write_input(zchar *, char) {} +static void memory_open(zword, zword, bool) {} +static void memory_close() {} +static void memory_word(const zchar *) {} +static void memory_new_line() {} +static void replay_open() {} +static void replay_close() {} +static zchar replay_read_key() { return 0; } +static zchar replay_read_input(zchar *) { return 0; } +static zchar console_read_key(zword) { return 0; } +static zchar console_read_input(uint, zchar *, uint, bool) { return 0; } +static void record_open() {} +static void record_close() {} +static void record_write_key(zchar) {} +static void record_write_input(zchar *, zchar) {} +static void os_restart_game(zword) {} +static void restart_header() {} + + +void Processor::scrollback_char (zchar c) { + if (c == ZC_INDENT) + { scrollback_char (' '); scrollback_char (' '); scrollback_char (' '); return; } + if (c == ZC_GAP) + { scrollback_char (' '); scrollback_char (' '); return; } + + os_scrollback_char(c); +} + +void Processor::scrollback_word(const zchar *s) { + int i; + + for (i = 0; s[i] != 0; i++) { + if (s[i] == ZC_NEW_FONT || s[i] == ZC_NEW_STYLE) + i++; + else + scrollback_char(s[i]); + } +} + +void Processor::scrollback_write_input(const zchar *buf, zchar key) { + int i; + + for (i = 0; buf[i] != 0; i++) + scrollback_char (buf[i]); + + if (key == ZC_RETURN) + scrollback_char ('\n'); +} + +void Processor::scrollback_erase_input(const zchar *buf) { + int width; + int i; + + for (i = 0, width = 0; buf[i] != 0; i++) + width++; + + os_scrollback_erase(width); + +} + +void Processor::stream_mssg_on() { + flush_buffer(); + + if (ostream_screen) + screen_mssg_on(); + if (ostream_script && enable_scripting) + script_mssg_on(); + + message = true; +} + +void Processor::stream_mssg_off() { + flush_buffer(); + + if (ostream_screen) + screen_mssg_off(); + if (ostream_script && enable_scripting) + script_mssg_off(); + + message = false; +} + +void Processor::stream_char(zchar c) { + if (ostream_screen) + screen_char(c); + if (ostream_script && enable_scripting) + script_char(c); + if (enable_scripting) + scrollback_char(c); +} + +void Processor::stream_word(const zchar *s) { + if (ostream_memory && !message) + memory_word(s); + else { + if (ostream_screen) + screen_word(s); + if (ostream_script && enable_scripting) + script_word(s); + if (enable_scripting) + scrollback_word(s); + } +} + +void Processor::stream_new_line() { + if (ostream_memory && !message) + memory_new_line(); + else { + if (ostream_screen) + screen_new_line(); + if (ostream_script && enable_scripting) + script_new_line(); + if (enable_scripting) + os_scrollback_char ('\n'); + } +} + +zchar Processor::stream_read_key(zword timeout, zword routine, bool hot_keys) { + zchar key = ZC_BAD; + + flush_buffer(); + + // Read key from current input stream +continue_input: + + do { + if (istream_replay) + key = replay_read_key(); + else + key = console_read_key(timeout); + } while (key == ZC_BAD); + + // Copy key to the command file + if (ostream_record && !istream_replay) + record_write_key(key); + + // Handle timeouts + if (key == ZC_TIME_OUT) + if (direct_call (routine) == 0) + goto continue_input; + + // Return key + return key; +} + +zchar Processor::stream_read_input(int max, zchar *buf, zword timeout, zword routine, + bool hot_keys, bool no_scripting) { + zchar key = ZC_BAD; + bool no_scrollback = no_scripting; + + if (h_version == V6 && _storyId == UNKNOWN && !ostream_script) + no_scrollback = false; + + flush_buffer(); + + // Remove initial input from the transscript file or from the screen + if (ostream_script && enable_scripting && !no_scripting) + script_erase_input(buf); + + // Read input line from current input stream +continue_input: + + do { + if (istream_replay) + key = replay_read_input(buf); + else + key = console_read_input(max, buf, timeout, key != ZC_BAD); + } while (key == ZC_BAD); + + // Copy input line to the command file + if (ostream_record && !istream_replay) + record_write_input(buf, key); + + // Handle timeouts + if (key == ZC_TIME_OUT) + if (direct_call(routine) == 0) + goto continue_input; + + // Copy input line to transscript file or to the screen + if (ostream_script && enable_scripting && !no_scripting) + script_write_input(buf, key); + + // Return terminating key + return key; +} + +void Processor::z_input_stream() { + flush_buffer(); + + if (zargs[0] == 0 && istream_replay) + replay_close(); + if (zargs[0] == 1 && !istream_replay) + replay_open(); +} + +void Processor::z_output_stream() { + flush_buffer(); + + switch ((short) zargs[0]) { + case 1: ostream_screen = true; + break; + case -1: ostream_screen = false; + break; + case 2: if (!ostream_script) script_open(); + break; + case -2: if (ostream_script) script_close(); + break; + case 3: memory_open(zargs[1], zargs[2], zargc >= 3); + break; + case -3: memory_close(); + break; + case 4: if (!ostream_record) record_open(); + break; + case -4: if (ostream_record) record_close(); + break; + default: + break; + } +} + +void Processor::z_restart(void) { + flush_buffer(); + + os_restart_game(RESTART_BEGIN); + + seed_random(0); + + if (!first_restart) { + story_fp->seek(blorb_ofs); + + if (story_fp->read(zmp, h_dynamic_size) != h_dynamic_size) + error("Story file read error"); + + } else { + first_restart = false; + } + + restart_header(); + restart_screen(); + + _sp = _fp = _stack + STACK_SIZE; + _frameCount = 0; + + if (h_version != V6 && h_version != V9) { + long pc = (long)h_start_pc; + SET_PC(pc); + } else { + call(h_start_pc, 0, nullptr, 0); + } + + os_restart_game(RESTART_END); +} + + +void Processor::z_save(void) { +#ifdef TODO + bool success = false; + + if (zargc != 0) { + // Open auxilary file + frefid_t ref = glk_fileref_create_by_prompt(fileusage_Data | fileusage_BinaryMode, + filemode_Write, 0); + if (ref == nullptr) + goto finished; + + // Write data + strid_t f = glk_stream_open_file(ref, filemode_Write); + + glk_put_buffer_stream(f, (const char *)zmp + zargs[0], zargs[1]); + + stream_result_t result; + glk_stream_close(f, &result); + + } else { + long pc; + zword addr; + zword nsp, nfp; + int skip; + int i; + + /* Open game file */ + + if ((gfp = frotzopenprompt (FILE_SAVE)) == nullptr) + goto finished; + + if (option_save_quetzal) { + success = save_quetzal (gfp, story_fp, blorb_ofs); + } else { + /* Write game file */ + + fputc ((int) hi (h_release), gfp); + fputc ((int) lo (h_release), gfp); + fputc ((int) hi (h_checksum), gfp); + fputc ((int) lo (h_checksum), gfp); + + GET_PC (pc) + + fputc ((int) (pc >> 16) & 0xff, gfp); + fputc ((int) (pc >> 8) & 0xff, gfp); + fputc ((int) (pc) & 0xff, gfp); + + nsp = (int) (_sp - _stack); + nfp = (int) (_fp - _stack); + + fputc ((int) hi (nsp), gfp); + fputc ((int) lo (nsp), gfp); + fputc ((int) hi (nfp), gfp); + fputc ((int) lo (nfp), gfp); + + for (i = nsp; i < STACK_SIZE; i++) { + fputc ((int) hi (_stack[i]), gfp); + fputc ((int) lo (_stack[i]), gfp); + } + + fseek (story_fp, blorb_ofs, SEEK_SET); + + for (addr = 0, skip = 0; addr < h_dynamic_size; addr++) + if (zmp[addr] != fgetc (story_fp) || skip == 255 || addr + 1 == h_dynamic_size) { + fputc (skip, gfp); + fputc (zmp[addr], gfp); + skip = 0; + } else skip++; + } + + /* Close game file and check for errors */ + + if (fclose (gfp) == EOF || ferror (story_fp)) { + print_string ("Error writing save file\n"); + goto finished; + } + + /* Success */ + + success = 1; + + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); +#endif +} + +void Processor::z_restore() { +#ifdef TODO + FILE *gfp; + + zword success = 0; + + if (zargc != 0) { + + /* Get the file name */ + + /* Open auxilary file */ + + if ((gfp = frotzopenprompt(FILE_LOAD_AUX)) == nullptr) + goto finished; + + /* Load auxilary file */ + + success = fread (zmp + zargs[0], 1, zargs[1], gfp); + + /* Close auxilary file */ + + fclose (gfp); + + } else { + + long pc; + zword release; + zword addr; + int i; + + /* Open game file */ + + if ((gfp = frotzopenprompt(FILE_RESTORE)) == nullptr) + goto finished; + + if (option_save_quetzal) { + success = restore_quetzal (gfp, story_fp, blorb_ofs); + + } else { + /* Load game file */ + + release = (unsigned) fgetc (gfp) << 8; + release |= fgetc (gfp); + + (void) fgetc (gfp); + (void) fgetc (gfp); + + /* Check the release number */ + + if (release == h_release) { + + pc = (long) fgetc (gfp) << 16; + pc |= (unsigned) fgetc (gfp) << 8; + pc |= fgetc (gfp); + + SET_PC (pc); + + _sp = _stack + (fgetc (gfp) << 8); + _sp += fgetc (gfp); + _fp = _stack + (fgetc (gfp) << 8); + _fp += fgetc (gfp); + + for (i = (int) (_sp - _stack); i < STACK_SIZE; i++) { + _stack[i] = (unsigned) fgetc (gfp) << 8; + _stack[i] |= fgetc (gfp); + } + + fseek (story_fp, blorb_ofs, SEEK_SET); + + for (addr = 0; addr < h_dynamic_size; addr++) { + int skip = fgetc (gfp); + for (i = 0; i < skip; i++) + zmp[addr++] = fgetc (story_fp); + zmp[addr] = fgetc (gfp); + (void) fgetc (story_fp); + } + + /* Check for errors */ + + if (ferror (gfp) || ferror (story_fp) || addr != h_dynamic_size) + success = -1; + else + + /* Success */ + + success = 2; + + } else print_string ("Invalid save file\n"); + } + + if ((short) success >= 0) { + + /* Close game file */ + + fclose (gfp); + + if ((short) success > 0) { + zbyte old_screen_rows; + zbyte old_screen_cols; + + /* In V3, reset the upper window. */ + if (h_version == V3) + split_window (0); + + LOW_BYTE (H_SCREEN_ROWS, old_screen_rows); + LOW_BYTE (H_SCREEN_COLS, old_screen_cols); + + /* Reload cached header fields. */ + restart_header (); + + /* + * Since QUETZAL files may be saved on many different machines, + * the screen sizes may vary a lot. Erasing the status window + * seems to cover up most of the resulting badness. + */ + if (h_version > V3 && h_version != V6 + && (h_screen_rows != old_screen_rows + || h_screen_cols != old_screen_cols)) + erase_window (1); + } + } else + os_fatal ("Error reading save file"); + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); +#endif +} + +void Processor::z_verify(void) { + zword checksum = 0; + + // Sum all bytes in story file except header bytes + story_fp->seek(blorb_ofs + 64); + + for (uint i = 64; i < story_size; i++) + checksum += story_fp->readByte(); + + // Branch if the checksums are equal + branch(checksum == h_checksum); +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/processor_table.cpp b/engines/gargoyle/frotz/processor_table.cpp new file mode 100644 index 0000000000..4e660ca693 --- /dev/null +++ b/engines/gargoyle/frotz/processor_table.cpp @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/processor.h" + +namespace Gargoyle { +namespace Frotz { + +// TODO: Method stubs to implement +static void storeb(zword, zbyte) {} +static void storew(zword, zword) {} + + +void Processor::z_copy_table() { + zword addr; + zword size = zargs[2]; + zbyte value; + int i; + + if (zargs[1] == 0) /* zero table */ + + for (i = 0; i < size; i++) + storeb((zword) (zargs[0] + i), 0); + + else if ((short) size < 0 || zargs[0] > zargs[1]) /* copy forwards */ + + for (i = 0; i < (((short) size < 0) ? - (short) size : size); i++) { + addr = zargs[0] + i; + LOW_BYTE(addr, value); + storeb((zword) (zargs[1] + i), value); + } else { + // copy backwards + for (i = size - 1; i >= 0; i--) { + addr = zargs[0] + i; + LOW_BYTE(addr, value); + storeb((zword) (zargs[1] + i), value); + } + } +} + +void Processor::z_loadb() { + zword addr = zargs[0] + zargs[1]; + zbyte value; + + LOW_BYTE(addr, value); + + store(value); +} + +void Processor::z_loadw() { + zword addr = zargs[0] + 2 * zargs[1]; + zword value; + + LOW_WORD(addr, value); + + store(value); +} + +void Processor::z_scan_table() { + zword addr = zargs[1]; + int i; + + // Supply default arguments + if (zargc < 4) + zargs[3] = 0x82; + + // Scan byte or word array + for (i = 0; i < zargs[2]; i++) { + if (zargs[3] & 0x80) { + // scan word array + zword wvalue; + + LOW_WORD(addr, wvalue); + + if (wvalue == zargs[0]) + goto finished; + } else { + // scan byte array + zbyte bvalue; + + LOW_BYTE(addr, bvalue); + + if (bvalue == zargs[0]) + goto finished; + } + + addr += zargs[3] & 0x7f; + } + + addr = 0; + +finished: + store(addr); + branch(addr); +} + +void Processor::z_storeb() { + storeb((zword) (zargs[0] + zargs[1]), zargs[2]); +} + +void Processor::z_storew() { + storew((zword)(zargs[0] + 2 * zargs[1]), zargs[2]); +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/processor_text.cpp b/engines/gargoyle/frotz/processor_text.cpp new file mode 100644 index 0000000000..e660ac1fd9 --- /dev/null +++ b/engines/gargoyle/frotz/processor_text.cpp @@ -0,0 +1,904 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/processor.h" + +namespace Gargoyle { +namespace Frotz { + +// TODO: Replace method stubs +static void new_line() {} +static void print_char(zchar) {} +static void storeb(zword, zchar) {} +static void storew(zword, zword) {} + + +zchar Processor::ZSCII_TO_LATIN1[] = { + 0x0e4, 0x0f6, 0x0fc, 0x0c4, 0x0d6, 0x0dc, 0x0df, 0x0bb, + 0x0ab, 0x0eb, 0x0ef, 0x0ff, 0x0cb, 0x0cf, 0x0e1, 0x0e9, + 0x0ed, 0x0f3, 0x0fa, 0x0fd, 0x0c1, 0x0c9, 0x0cd, 0x0d3, + 0x0da, 0x0dd, 0x0e0, 0x0e8, 0x0ec, 0x0f2, 0x0f9, 0x0c0, + 0x0c8, 0x0cc, 0x0d2, 0x0d9, 0x0e2, 0x0ea, 0x0ee, 0x0f4, + 0x0fb, 0x0c2, 0x0ca, 0x0ce, 0x0d4, 0x0db, 0x0e5, 0x0c5, + 0x0f8, 0x0d8, 0x0e3, 0x0f1, 0x0f5, 0x0c3, 0x0d1, 0x0d5, + 0x0e6, 0x0c6, 0x0e7, 0x0c7, 0x0fe, 0x0f0, 0x0de, 0x0d0, + 0x0a3, 0x153, 0x152, 0x0a1, 0x0bf +}; + +zchar Processor::translate_from_zscii(zbyte c) { + if (c == 0xfc) + return ZC_MENU_CLICK; + if (c == 0xfd) + return ZC_DOUBLE_CLICK; + if (c == 0xfe) + return ZC_SINGLE_CLICK; + + if (c >= 0x9b && _storyId != BEYOND_ZORK) { + if (hx_unicode_table != 0) { + // game has its own Unicode table + zbyte N; + LOW_BYTE(hx_unicode_table, N); + + if (c - 0x9b < N) { + zword addr = hx_unicode_table + 1 + 2 * (c - 0x9b); + zword unicode; + + LOW_WORD(addr, unicode); + + if (unicode < 0x20) + return '?'; + + return unicode; + } else { + return '?'; + } + } else { + // game uses standard set + if (c <= 0xdf) { + return ZSCII_TO_LATIN1[c - 0x9b]; + } else { + return '?'; + } + } + } + + return (zchar)c; +} + +zbyte Processor::unicode_to_zscii(zchar c) { + int i; + + if (c >= ZC_LATIN1_MIN) { + if (hx_unicode_table != 0) { + // game has its own Unicode table + zbyte N; + LOW_BYTE(hx_unicode_table, N); + + for (i = 0x9b; i < 0x9b + N; i++) { + zword addr = hx_unicode_table + 1 + 2 * (i - 0x9b); + zword unicode; + + LOW_WORD(addr, unicode); + + if (c == unicode) + return (zbyte) i; + } + + return 0; + } else { + // game uses standard set + for (i = 0x9b; i <= 0xdf; i++) + if (c == ZSCII_TO_LATIN1[i - 0x9b]) + return (zbyte) i; + + return 0; + } + } + + return (zbyte)c; +} + +zbyte Processor::translate_to_zscii(zchar c) { + if (c == ZC_SINGLE_CLICK) + return 0xfe; + if (c == ZC_DOUBLE_CLICK) + return 0xfd; + if (c == ZC_MENU_CLICK) + return 0xfc; + if (c == 0) + return 0; + + c = unicode_to_zscii (c); + if (c == 0) + c = '?'; + + return (zbyte)c; +} + +zchar Processor::alphabet(int set, int index) { + if (h_version > V1 && set == 2 && index == 1) + // always newline + return '\r'; + + if (h_alphabet != 0) { + // game uses its own alphabet + zbyte c; + + zword addr = h_alphabet + 26 * set + index; + LOW_BYTE(addr, c); + + return translate_from_zscii(c); + } else { + // game uses default alphabet + if (set == 0) + return 'a' + index; + else if (set == 1) + return 'A' + index; + else if (h_version == V1) + return " 0123456789.,!?_#'\"/\\<-:()"[index]; + else + return " ^0123456789.,!?_#'\"/\\-:()"[index]; + } +} + +void Processor::find_resolution() { + zword dct = h_dictionary; + zword entry_count; + zbyte sep_count; + zbyte entry_len; + + LOW_BYTE(dct, sep_count); + dct += 1 + sep_count; // skip word separators + LOW_BYTE(dct, entry_len); + dct += 1; // skip entry length + LOW_WORD(dct, entry_count); + dct += 2; // get number of entries + + if (h_version < V9) { + _resolution = (h_version <= V3) ? 2 : 3; + } else { + zword addr = dct; + zword code; + + if (entry_count == 0) + runtimeError(ERR_DICT_LEN); + + // check the first word in the dictionary + do { + LOW_WORD(addr, code); + addr += 2; + } while (!(code & 0x8000) && (addr - dct < entry_len + 1)); + + _resolution = (addr - dct) / 2; + } + + if (2 * _resolution > entry_len) { + runtimeError(ERR_DICT_LEN); + } + + _decoded = (zchar *)malloc (sizeof (zchar) * (3 * _resolution) + 1); + _encoded = (zchar *)malloc (sizeof (zchar) * _resolution); +} + +void Processor::load_string (zword addr, zword length) { + int i = 0; + + if (_resolution == 0) + find_resolution(); + + while (i < 3 * _resolution) { + if (i < length) { + zbyte c; + LOW_BYTE(addr, c); + addr++; + + _decoded[i++] = translate_from_zscii(c); + } else { + _decoded[i++] = 0; + } + } +} + +void Processor::encode_text(int padding) { + static const zchar again[] = { 'a', 'g', 'a', 'i', 'n', 0, 0, 0, 0 }; + static const zchar examine[] = { 'e', 'x', 'a', 'm', 'i', 'n', 'e', 0, 0 }; + static const zchar wait[] = { 'w', 'a', 'i', 't', 0, 0, 0, 0, 0 }; + + zbyte *zchars; + const zchar *ptr; + zchar c; + int i = 0; + + if (_resolution == 0) find_resolution(); + + zchars = new byte[3 * (_resolution + 1)]; + ptr = _decoded; + + // Expand abbreviations that some old Infocom games lack + if (_expand_abbreviations && (h_version <= V8)) { + if (padding == 0x05 && _decoded[1] == 0) { + switch (_decoded[0]) { + case 'g': ptr = again; break; + case 'x': ptr = examine; break; + case 'z': ptr = wait; break; + default: break; + } + } + } + + // Translate string to a sequence of Z-characters + while (i < 3 * _resolution) { + if ((c = *ptr++) != 0) { + int index, set; + zbyte c2; + + if (c == ' ') { + zchars[i++] = 0; + continue; + } + + // Search character in the alphabet + for (set = 0; set < 3; set++) + for (index = 0; index < 26; index++) + if (c == alphabet (set, index)) + goto letter_found; + + // Character not found, store its ZSCII value + c2 = translate_to_zscii (c); + + zchars[i++] = 5; + zchars[i++] = 6; + zchars[i++] = c2 >> 5; + zchars[i++] = c2 & 0x1f; + continue; + + letter_found: + // Character found, store its index + if (set != 0) + zchars[i++] = ((h_version <= V2) ? 1 : 3) + set; + + zchars[i++] = index + 6; + } else { + zchars[i++] = padding; + } + } + + // Three Z-characters make a 16bit word + for (i = 0; i < _resolution; i++) + _encoded[i] = + (zchars[3 * i + 0] << 10) | + (zchars[3 * i + 1] << 5) | + (zchars[3 * i + 2]); + + _encoded[_resolution - 1] |= 0x8000; + delete[] zchars; +} + +#define outchar(c) if (st == VOCABULARY) *ptr++=c; else print_char(c) + +void Processor::decode_text(enum string_type st, zword addr) { + zchar *ptr = nullptr; + long byte_addr = 0; + zchar c2; + zword code; + zbyte c, prev_c = 0; + int shift_state = 0; + int shift_lock = 0; + int status = 0; + + if (_resolution == 0) + find_resolution(); + + // Calculate the byte address if necessary + if (st == ABBREVIATION) + byte_addr = (long)addr << 1; + + else if (st == HIGH_STRING) { + if (h_version <= V3) + byte_addr = (long)addr << 1; + else if (h_version <= V5) + byte_addr = (long)addr << 2; + else if (h_version <= V7) + byte_addr = ((long)addr << 2) + ((long)h_strings_offset << 3); + else if (h_version <= V8) + byte_addr = (long)addr << 3; + else { + // h_version == V9 + long indirect = (long)addr << 2; + HIGH_LONG(indirect, byte_addr); + } + + if ((uint)byte_addr >= story_size) + runtimeError(ERR_ILL_PRINT_ADDR); + } + + // Loop until a 16bit word has the highest bit set + if (st == VOCABULARY) + ptr = _decoded; + + do { + int i; + + // Fetch the next 16bit word + if (st == LOW_STRING || st == VOCABULARY) { + LOW_WORD(addr, code); + addr += 2; + } else if (st == HIGH_STRING || st == ABBREVIATION) { + HIGH_WORD(byte_addr, code); + byte_addr += 2; + } else { + CODE_WORD(code); + } + + // Read its three Z-characters + for (i = 10; i >= 0; i -= 5) { + zword abbr_addr; + zword ptr_addr; + zchar zc; + + c = (code >> i) & 0x1f; + + switch (status) { + case 0: + // normal operation + if (shift_state == 2 && c == 6) + status = 2; + + else if (h_version == V1 && c == 1) + new_line(); + + else if (h_version >= V2 && shift_state == 2 && c == 7) + new_line(); + + else if (c >= 6) + outchar(alphabet(shift_state, c - 6)); + + else if (c == 0) + outchar(' '); + + else if (h_version >= V2 && c == 1) + status = 1; + + else if (h_version >= V3 && c <= 3) + status = 1; + + else { + shift_state = (shift_lock + (c & 1) + 1) % 3; + + if (h_version <= V2 && c >= 4) + shift_lock = shift_state; + + break; + } + + shift_state = shift_lock; + break; + + case 1: + // abbreviation + ptr_addr = h_abbreviations + 64 * (prev_c - 1) + 2 * c; + + LOW_WORD(ptr_addr, abbr_addr); + decode_text(ABBREVIATION, abbr_addr); + + status = 0; + break; + + case 2: + // ZSCII character - first part + status = 3; + break; + + case 3: + // ZSCII character - second part + zc = (prev_c << 5) | c; + + if (zc > 767) { + // Unicode escape + while (zc-- > 767) { + if (st == LOW_STRING || st == VOCABULARY) { + LOW_WORD(addr, c2); + addr += 2; + } else if (st == HIGH_STRING || st == ABBREVIATION) { + HIGH_WORD(byte_addr, c2); + byte_addr += 2; + } else + CODE_WORD(c2); + + outchar(c2 ^ 0xFFFF); + } + } else { + c2 = translate_from_zscii(zc); + outchar(c2); + } + + status = 0; + break; + + default: + break; + } + + prev_c = c; + } + } while (!(code & 0x8000)); + + if (st == VOCABULARY) + *ptr = 0; +} + +#undef outchar + +void Processor::print_num(zword value) { + int i; + + /* Print sign */ + + if ((short)value < 0) { + print_char('-'); + value = -(short)value; + } + + /* Print absolute value */ + + for (i = 10000; i != 0; i /= 10) + if (value >= i || i == 1) + print_char('0' + (value / i) % 10); + +} + +void Processor::print_object(zword object) { + zword addr = object_name(object); + zword code = 0x94a5; + zbyte length; + + LOW_BYTE(addr, length); + addr++; + + if (length != 0) + LOW_WORD(addr, code); + + if (code == 0x94a5) { + // _encoded text 0x94a5 == empty string + print_string("object#"); // supply a generic name + print_num(object); // for anonymous objects + } else { + decode_text(LOW_STRING, addr); + } +} + +void Processor::print_string(const char *s) { + char c; + + while ((c = *s++) != 0) { + if (c == '\n') + new_line(); + else + print_char(c); + } +} + +zword Processor::lookup_text(int padding, zword dct) { + zword entry_addr; + zword entry_count; + zword entry; + zword addr; + zbyte entry_len; + zbyte sep_count; + int entry_number; + int lower, upper; + int i; + bool sorted; + + if (_resolution == 0) + find_resolution(); + + encode_text(padding); + + LOW_BYTE(dct, sep_count); // skip word separators + dct += 1 + sep_count; + LOW_BYTE(dct, entry_len); // get length of entries + dct += 1; + LOW_WORD(dct, entry_count); // get number of entries + dct += 2; + + if ((short)entry_count < 0) { + // bad luck, entries aren't sorted + entry_count = -(short)entry_count; + sorted = false; + + } else { + sorted = true; // entries are sorted + } + + lower = 0; + upper = entry_count - 1; + + while (lower <= upper) { + if (sorted) + // binary search + entry_number = (lower + upper) / 2; + else + // linear search + entry_number = lower; + + entry_addr = dct + entry_number * entry_len; + + // Compare word to dictionary entry + addr = entry_addr; + + for (i = 0; i < _resolution; i++) { + LOW_WORD(addr, entry); + if (_encoded[i] != entry) + goto continuing; + addr += 2; + } + + return entry_addr; // exact match found, return now + + continuing: + if (sorted) { + // binary search + if (_encoded[i] > entry) + lower = entry_number + 1; + else + upper = entry_number - 1; + } else { + // linear search + lower++; + } + } + + // No exact match has been found + if (padding == 0x05) + return 0; + + entry_number = (padding == 0x00) ? lower : upper; + + if (entry_number == -1 || entry_number == entry_count) + return 0; + + return dct + entry_number * entry_len; +} + +void Processor::tokenise_text(zword text, zword length, zword from, zword parse, zword dct, bool flag) { + zword addr; + zbyte token_max, token_count; + + LOW_BYTE(parse, token_max); + parse++; + LOW_BYTE(parse, token_count); + + if (token_count < token_max) { + // sufficient space left for token? + storeb(parse++, token_count + 1); + + load_string((zword)(text + from), length); + + addr = lookup_text(0x05, dct); + + if (addr != 0 || !flag) { + + parse += 4 * token_count; + + storew((zword)(parse + 0), addr); + storeb((zword)(parse + 2), length); + storeb((zword)(parse + 3), from); + } + } +} + +void Processor::tokenise_line(zword text, zword token, zword dct, bool flag) { + zword addr1; + zword addr2; + zbyte length = 0; + zbyte c; + + // Use standard dictionary if the given dictionary is zero + if (dct == 0) + dct = h_dictionary; + + // Remove all tokens before inserting new ones + storeb((zword)(token + 1), 0); + + // Move the first pointer across the text buffer searching for the beginning + // of a word. If this succeeds, store the position in a second pointer. + // Move the first pointer searching for the end of the word. When it is found, + // "tokenise" the word. Continue until the end of the buffer is reached. + addr1 = text; + addr2 = 0; + + if (h_version >= V5) { + addr1++; + LOW_BYTE(addr1, length); + } + + do { + zword sep_addr; + zbyte sep_count; + zbyte separator; + + // Fetch next ZSCII character + addr1++; + + if (h_version >= V5 && addr1 == text + 2 + length) + c = 0; + else + LOW_BYTE(addr1, c); + + // Check for separator + sep_addr = dct; + + LOW_BYTE(sep_addr, sep_count); + sep_addr++; + + do { + LOW_BYTE(sep_addr, separator); + sep_addr++; + } while (c != separator && --sep_count != 0); + + // This could be the start or the end of a word + if (sep_count == 0 && c != ' ' && c != 0) { + if (addr2 == 0) + addr2 = addr1; + } else if (addr2 != 0) { + tokenise_text(text, (zword)(addr1 - addr2), (zword)(addr2 - text), + token, dct, flag); + + addr2 = 0; + } + + // Translate separator (which is a word in its own right) + if (sep_count != 0) + tokenise_text(text, (zword)(1), (zword)(addr1 - text), token, dct, flag); + + } while (c != 0); +} + +int Processor::completion(const zchar *buffer, zchar *result) { + zword minaddr; + zword maxaddr; + zchar *ptr; + zchar c; + int len; + int i; + + *result = 0; + + if (_resolution == 0) + find_resolution(); + + // Copy last word to "_decoded" string + len = 0; + + while ((c = *buffer++) != 0) + if (c != ' ') { + if (len < 3 * _resolution) + _decoded[len++] = c; + } else { + len = 0; + } + + _decoded[len] = 0; + + // Search the dictionary for first and last possible extensions + minaddr = lookup_text(0x00, h_dictionary); + maxaddr = lookup_text(0x1f, h_dictionary); + + if (minaddr == 0 || maxaddr == 0 || minaddr > maxaddr) + return 2; + + // Copy first extension to "result" string + decode_text(VOCABULARY, minaddr); + + ptr = result; + + for (i = len; (c = _decoded[i]) != 0; i++) + *ptr++ = c; + *ptr = 0; + + // Merge second extension with "result" string + decode_text(VOCABULARY, maxaddr); + + for (i = len, ptr = result; (c = _decoded[i]) != 0; i++, ptr++) { + if (*ptr != c) + break; + } + *ptr = 0; + + // Search was ambiguous or successful + return (minaddr == maxaddr) ? 0 : 1; +} + +zchar Processor::unicode_tolower(zchar c) { + static const byte tolower_basic_latin[0x100] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, + 0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F, + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xD7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xDF, + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF + }; + static const byte tolower_latin_extended_a[0x80] = { + 0x01,0x01,0x03,0x03,0x05,0x05,0x07,0x07,0x09,0x09,0x0B,0x0B,0x0D,0x0D,0x0F,0x0F, + 0x11,0x11,0x13,0x13,0x15,0x15,0x17,0x17,0x19,0x19,0x1B,0x1B,0x1D,0x1D,0x1F,0x1F, + 0x21,0x21,0x23,0x23,0x25,0x25,0x27,0x27,0x29,0x29,0x2B,0x2B,0x2D,0x2D,0x2F,0x2F, + 0x00,0x31,0x33,0x33,0x35,0x35,0x37,0x37,0x38,0x3A,0x3A,0x3C,0x3C,0x3E,0x3E,0x40, + 0x40,0x42,0x42,0x44,0x44,0x46,0x46,0x48,0x48,0x49,0x4B,0x4B,0x4D,0x4D,0x4F,0x4F, + 0x51,0x51,0x53,0x53,0x55,0x55,0x57,0x57,0x59,0x59,0x5B,0x5B,0x5D,0x5D,0x5F,0x5F, + 0x61,0x61,0x63,0x63,0x65,0x65,0x67,0x67,0x69,0x69,0x6B,0x6B,0x6D,0x6D,0x6F,0x6F, + 0x71,0x71,0x73,0x73,0x75,0x75,0x77,0x77,0x00,0x7A,0x7A,0x7C,0x7C,0x7E,0x7E,0x7F + }; + static const byte tolower_greek[0x50] = { + 0x80,0x81,0x82,0x83,0x84,0x85,0xAC,0x87,0xAD,0xAE,0xAF,0x8B,0xCC,0x8D,0xCD,0xCE, + 0x90,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, + 0xC0,0xC1,0xA2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xAC,0xAD,0xAE,0xAF, + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF + }; + static const byte tolower_cyrillic[0x60] = { + 0x00,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F + }; + + if (c < 0x0100) + c = tolower_basic_latin[c]; + else if (c == 0x0130) + c = 0x0069; // Capital I with dot -> lower case i + else if (c == 0x0178) + c = 0x00FF; // Capital Y diaeresis -> lower case y diaeresis + else if (c < 0x0180) + c = tolower_latin_extended_a[c - 0x100] + 0x100; + else if (c >= 0x380 && c < 0x3D0) + c = tolower_greek[c - 0x380] + 0x300; + else if (c >= 0x400 && c < 0x460) + c = tolower_cyrillic[c - 0x400] + 0x400; + + return c; +} + + +void Processor::z_check_unicode() { + zword c = zargs[0]; + zword result = 0; + + if (c <= 0x1f) { + if ((c == 0x08) || (c == 0x0d) || (c == 0x1b)) + result = 2; + } else if (c <= 0x7e) { + result = 3; + } else { + // we support unicode + result = 1; + } + + store (result); +} + +void Processor::z_encode_text() { + int i; + + load_string((zword) (zargs[0] + zargs[2]), zargs[1]); + + encode_text(0x05); + + for (i = 0; i < _resolution; i++) + storew((zword) (zargs[3] + 2 * i), _encoded[i]); + +} + +void Processor::z_new_line() { + new_line (); +} + +void Processor::z_print () { + decode_text(EMBEDDED_STRING, 0); +} + +void Processor::z_print_addr() { + decode_text(LOW_STRING, zargs[0]); +} + +void Processor::z_print_char() { + print_char (translate_from_zscii(zargs[0])); +} + +void Processor::z_print_form() { + zword count; + zword addr = zargs[0]; + bool first = true; + + for (;;) { + LOW_WORD(addr, count); + addr += 2; + + if (count == 0) + break; + + if (!first) + new_line (); + + while (count--) { + zbyte c; + + LOW_BYTE(addr, c); + addr++; + + print_char(translate_from_zscii (c)); + } + + first = false; + } +} + +void Processor::z_print_num() { + print_num (zargs[0]); +} + +void Processor::z_print_obj() { + print_object(zargs[0]); +} + +void Processor::z_print_paddr() { + decode_text (HIGH_STRING, zargs[0]); +} + +void Processor::z_print_ret() { + decode_text(EMBEDDED_STRING, 0); + new_line(); + ret(1); +} + +void Processor::z_print_unicode() { + if (zargs[0] < 0x20) + print_char('?'); + else + print_char(zargs[0]); +} + +void Processor::z_tokenise() { + // Supply default arguments + if (zargc < 3) + zargs[2] = 0; + if (zargc < 4) + zargs[3] = 0; + + // Call tokenise_line to do the real work + tokenise_line(zargs[0], zargs[1], zargs[2], zargs[3] != 0); +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/frotz/processor_variables.cpp b/engines/gargoyle/frotz/processor_variables.cpp new file mode 100644 index 0000000000..24bbd28fe3 --- /dev/null +++ b/engines/gargoyle/frotz/processor_variables.cpp @@ -0,0 +1,203 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gargoyle/frotz/processor.h" + +namespace Gargoyle { +namespace Frotz { + +// TODO: Replace method stubs +static void storew(zword, zword) {} + + +void Processor::z_dec() { + zword value; + + if (zargs[0] == 0) + (*_sp)--; + else if (zargs[0] < 16) + (*(_fp - zargs[0]))--; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD(addr, value); + value--; + SET_WORD(addr, value); + } +} + +void Processor::z_dec_chk() { + zword value; + + if (zargs[0] == 0) + value = --(*_sp); + else if (zargs[0] < 16) + value = --(*(_fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD(addr, value); + value--; + SET_WORD(addr, value); + } + + branch((short)value < (short)zargs[1]); +} + +void Processor::z_inc() { + zword value; + + if (zargs[0] == 0) + (*_sp)++; + else if (zargs[0] < 16) + (*(_fp - zargs[0]))++; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD(addr, value); + value++; + SET_WORD(addr, value); + } +} + +void Processor::z_inc_chk() { + zword value; + + if (zargs[0] == 0) + value = ++(*_sp); + else if (zargs[0] < 16) + value = ++(*(_fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD(addr, value); + value++; + SET_WORD(addr, value); + } + + branch((short)value > (short)zargs[1]); +} + +void Processor::z_load() { + zword value; + + if (zargs[0] == 0) + value = *_sp; + else if (zargs[0] < 16) + value = *(_fp - zargs[0]); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD(addr, value); + } + + store(value); +} + +void Processor::z_pop() { + _sp++; +} + +void Processor::z_pop_stack() { + if (zargc == 2) { + // it's a user stack + zword size; + zword addr = zargs[1]; + + LOW_WORD(addr, size); + + size += zargs[0]; + storew(addr, size); + } else { + // it's the game stack + _sp += zargs[0]; + } +} + +void Processor::z_pull() { + zword value; + + if (h_version != V6) { + // not a V6 game, pop stack and write + value = *_sp++; + + if (zargs[0] == 0) + *_sp = value; + else if (zargs[0] < 16) + *(_fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD(addr, value); + } + } else { + // it's V6, but is there a user stack? + if (zargc == 1) { + // it's a user stack + zword size; + zword addr = zargs[0]; + + LOW_WORD(addr, size); + + size++; + storew(addr, size); + + addr += 2 * size; + LOW_WORD(addr, value); + } else { + // it's the game stack + value = *_sp++; + } + + store(value); + } +} + +void Processor::z_push() { + *--_sp = zargs[0]; +} + +void Processor::z_push_stack() { + zword size; + zword addr = zargs[1]; + + LOW_WORD(addr, size); + + if (size != 0) { + storew((zword)(addr + 2 * size), zargs[0]); + + size--; + storew(addr, size); + } + + branch(size); +} + +void Processor::z_store() { + zword value = zargs[1]; + + if (zargs[0] == 0) + *_sp = value; + else if (zargs[0] < 16) + *(_fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD(addr, value); + } +} + +} // End of namespace Scott +} // End of namespace Gargoyle diff --git a/engines/gargoyle/glk.h b/engines/gargoyle/glk.h index 9dc7f97b2c..a7a119866f 100644 --- a/engines/gargoyle/glk.h +++ b/engines/gargoyle/glk.h @@ -72,7 +72,7 @@ public: glui32 size, winid_t keyWin); void glk_window_get_arrangement(winid_t win, glui32 *method, glui32 *size, winid_t *keyWin); - winid_t glk_window_iterate(winid_t win, glui32 *rock); + winid_t glk_window_iterate(winid_t win, glui32 *rock = 0); glui32 glk_window_get_rock(winid_t win); glui32 glk_window_get_type(winid_t win); winid_t glk_window_get_parent(winid_t win); @@ -85,8 +85,8 @@ public: strid_t glk_window_get_echo_stream(winid_t win); void glk_set_window(winid_t win); - strid_t glk_stream_open_file(frefid_t fileref, FileMode fmode, glui32 rock); - strid_t glk_stream_open_memory(char *buf, glui32 buflen, FileMode fmode, glui32 rock); + strid_t glk_stream_open_file(frefid_t fileref, FileMode fmode, glui32 rock = 0); + strid_t glk_stream_open_memory(char *buf, glui32 buflen, FileMode fmode, glui32 rock = 0); void glk_stream_close(strid_t str, stream_result_t *result); strid_t glk_stream_iterate(strid_t str, glui32 *rockptr) const; glui32 glk_stream_get_rock(strid_t str) const; @@ -114,10 +114,10 @@ public: glui32 glk_style_distinguish(winid_t win, glui32 style1, glui32 style2); bool glk_style_measure(winid_t win, glui32 style, glui32 hint, glui32 *result); - frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock); - frefid_t glk_fileref_create_by_name(glui32 usage, const char *name, glui32 rock); - frefid_t glk_fileref_create_by_prompt(glui32 usage, FileMode fmode, glui32 rock); - frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock); + frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock = 0); + frefid_t glk_fileref_create_by_name(glui32 usage, const char *name, glui32 rock = 0); + frefid_t glk_fileref_create_by_prompt(glui32 usage, FileMode fmode, glui32 rock = 0); + frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock = 0); void glk_fileref_destroy(frefid_t fref); frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr); glui32 glk_fileref_get_rock(frefid_t fref); @@ -169,8 +169,8 @@ public: glui32 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); glui32 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len); - strid_t glk_stream_open_file_uni(frefid_t fileref, FileMode fmode, glui32 rock); - strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, FileMode fmode, glui32 rock); + strid_t glk_stream_open_file_uni(frefid_t fileref, FileMode fmode, glui32 rock = 0); + strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, FileMode fmode, glui32 rock = 0); void glk_request_char_event_uni(winid_t win); void glk_request_line_event_uni(winid_t win, glui32 *buf, @@ -206,7 +206,7 @@ public: #ifdef GLK_MODULE_SOUND - schanid_t glk_schannel_create(glui32 rock); + schanid_t glk_schannel_create(glui32 rock = 0); void glk_schannel_destroy(schanid_t chan); schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr); glui32 glk_schannel_get_rock(schanid_t chan); diff --git a/engines/gargoyle/module.mk b/engines/gargoyle/module.mk index d359429487..9a1b4c40c1 100644 --- a/engines/gargoyle/module.mk +++ b/engines/gargoyle/module.mk @@ -26,7 +26,17 @@ MODULE_OBJS := \ frotz/detection_tables.o \ frotz/err.o \ frotz/frotz.o \ + frotz/glk_interface.o \ frotz/mem.o \ + frotz/processor.o \ + frotz/processor_input.o \ + frotz/processor_maths.o \ + frotz/processor_objects.o \ + frotz/processor_screen.o \ + frotz/processor_streams.o \ + frotz/processor_table.o \ + frotz/processor_text.o \ + frotz/processor_variables.o \ scott/detection.o \ scott/scott.o |