diff options
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  | 
