From c4e3fb793915f93d384d4597bfb496f66119c7ff Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Sat, 8 Sep 2018 19:51:49 -0500 Subject: [PATCH] Initial commit. --- .gitattributes | 2 + .gitignore | 2 + 8x8thin.sta | Bin 0 -> 32036 bytes ansiterm.c | 535 +++++++++++++++++++ ansiterm.h | 16 + build-IIgs.sh | 26 + control.c | 369 +++++++++++++ extern.c | 147 ++++++ fileio.c | 1350 ++++++++++++++++++++++++++++++++++++++++++++++++ gamedata.z5 | Bin 0 -> 188416 bytes ifengine.pro | 75 +++ input.c | 606 ++++++++++++++++++++++ interpre.c | 483 +++++++++++++++++ joeyio.c | 223 ++++++++ jzip.h | 37 ++ main.c | 122 +++++ math.c | 305 +++++++++++ memory.c | 419 +++++++++++++++ object.c | 454 ++++++++++++++++ operand.c | 234 +++++++++ osdepend.c | 525 +++++++++++++++++++ property.c | 583 +++++++++++++++++++++ quetzal.c | 573 ++++++++++++++++++++ screen.c | 577 +++++++++++++++++++++ text.c | 1145 ++++++++++++++++++++++++++++++++++++++++ variable.c | 126 +++++ ztypes.h | 799 ++++++++++++++++++++++++++++ 27 files changed, 9733 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 8x8thin.sta create mode 100644 ansiterm.c create mode 100644 ansiterm.h create mode 100755 build-IIgs.sh create mode 100644 control.c create mode 100644 extern.c create mode 100644 fileio.c create mode 100644 gamedata.z5 create mode 100644 ifengine.pro create mode 100644 input.c create mode 100644 interpre.c create mode 100644 joeyio.c create mode 100644 jzip.h create mode 100644 main.c create mode 100644 math.c create mode 100644 memory.c create mode 100644 object.c create mode 100644 operand.c create mode 100644 osdepend.c create mode 100644 property.c create mode 100644 quetzal.c create mode 100644 screen.c create mode 100644 text.c create mode 100644 variable.c create mode 100644 ztypes.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..115ef26 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.sta filter=lfs diff=lfs merge=lfs -text +*.z5 filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..738de72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +*.user diff --git a/8x8thin.sta b/8x8thin.sta new file mode 100644 index 0000000000000000000000000000000000000000..331d8dafb7495db67f12b807325b7cf5aae88e67 GIT binary patch literal 32036 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 a&A`pT&A`pT&A`pT&A`pT&A|U61OEamllT?@ literal 0 HcmV?d00001 diff --git a/ansiterm.c b/ansiterm.c new file mode 100644 index 0000000..07e0b6f --- /dev/null +++ b/ansiterm.c @@ -0,0 +1,535 @@ +#include +#include + +#include "joey.h" +#ifdef JOEY_IIGS +segment "ansiterm"; +#endif + +#include "ansiterm.h" + + +typedef struct { + int x; + int y; +} Vector2T; + + +static int _columns = 40; +static int _rows = 25; +static int _xoff = 0; +static int _yoff = 0; +static bool _escFound = false; +static bool _inEscape = false; +static bool _bold = false; +static bool _blink = false; +static bool _reverse = false; +static int _foreground = 7; +static int _background = 0; +static int _fColor = 7; +static int _bColor = 0; +static int _number = 0; +static int _parameters[5]; +static int _parameterCount = 0; +static Vector2T _cursorSave; +static Vector2T _cursor; +static jlStaT *_ansiFont = NULL; +static int *_screenBuffer = NULL; + + +void termDebug(const char *what, ...); +bool termParseANSI(char c); +void termRenderCharacterAtCursor(char c); +void termRepaint(void); +void termResetSequence(void); +void termScrollUp(void); +void termUpdateColors(void); + + +void termClearScreen(void) { + memset(_screenBuffer, 0, (size_t)(2 * _columns * _rows)); +} + + +__attribute__((__format__ (__printf__, 1, 0))) +void termDebug(const char *what, ...) { + char msg[1024]; + va_list va; + + va_start(va, what); + vsprintf(msg, what, va); + va_end(va); + + printf("%s\n", msg); +} + + +void termGetCursor(int *x, int *y) { + *x = _cursor.x; + *y = _cursor.y; +} + + +void termMoveCursor(int x, int y) { + if (x < 1) { + _cursor.x = 1; + termDebug("Attempt to position cursor too far left: %d", x); + } else { + if (x > _columns) { + _cursor.x = _columns; + termDebug("Attempt to position cursor too far right: %d", x); + } else { + _cursor.x = x; + } + } + if (y < 1) { + _cursor.y = 1; + termDebug("Attempt to position cursor too far up: %d", y); + } else { + if (y > _rows) { + _cursor.y = _rows; + termDebug("Attempt to position cursor too far down: %d", y); + } else { + _cursor.y = y; + } + } +} + + +bool termParseANSI(char c) { + int x; + int y; + int oldX; + int cx; + int p; + bool updateDisplay = false; + Vector2T cursor = _cursor; + + // Find leading ESC character. + if ((c == (char)27) && !_escFound && !_inEscape) { + _escFound = true; + return updateDisplay; + } + + // Find [ character. + if ((c == '[') && _escFound && !_inEscape) { + _inEscape = true; + return updateDisplay; + } + + // We don't do non-'ESC[' sequences. + if (_escFound && !_inEscape) { + termDebug("Unexpected Character: %d", (int)c); + termResetSequence(); + } + + if (_inEscape) { + switch (c) { + // All we know is that a Xenomorph may be involved. + case '[': + case (char)27: + termDebug("BUG! Escape found inside sequence."); + break; + // End of a parameter. + case ';': + _parameters[_parameterCount++] = _number; + _number = 0; + break; + // Cursor up. + case 'A': + y = _number; + termDebug("Moving cursor up %d", y); + termMoveCursor(cursor.x, cursor.y - y); + termResetSequence(); + break; + // Cursor down. + case 'B': + y = _number; + termDebug("Moving cursor down %d", y); + termMoveCursor(cursor.x, cursor.y + y); + termResetSequence(); + break; + // Cursor forward. + case 'C': + x = _number; + termDebug("Moving cursor right %d", x); + termMoveCursor(cursor.x + x, cursor.y); + termResetSequence(); + break; + // Cursor backward. + case 'D': + x = _number; + termDebug("Moving cursor left %d", x); + termMoveCursor(cursor.x - x, cursor.y); + termResetSequence(); + break; + // Cursor line down. + case 'E': + y = _number; + termDebug("Moving cursor down line %d", y); + termMoveCursor(1, cursor.y + y); + //***TODO*** This should allow scrolling + termResetSequence(); + break; + // Cursor line up. + case 'F': + y = _number; + termDebug("Moving cursor up line %d", y); + termMoveCursor(1, cursor.y - y); + termResetSequence(); + break; + // Cursor horizontal absolute. + case 'G': + x = _number; + termDebug("Moving cursor horizontally to %d", x); + termMoveCursor(x, cursor.y); + termResetSequence(); + break; + // Move cursor. + case 'H': + case 'f': + switch (_parameterCount) { + case 0: + // Absolute position, Y. Kinda. Moves X to 1. + y = _number; + termDebug("Moving cursor to 1x%d", y); + termMoveCursor(1, y); + break; + case 1: + // Absolute position. + x = _number; + y = _parameters[0]; + termDebug("Moving cursor to %dx%d", x, y); + termMoveCursor(x, y); + break; + default: + termDebug("Unknown Cursor Move"); + break; + } + termResetSequence(); + break; + // Clear display. + case 'J': + if ((_parameterCount == 0) && (_number == 2)) { + termDebug("Clear display"); + termClearScreen(); + updateDisplay = true; + termMoveCursor(1, 1); + } else { + termDebug("Unimplemented Screen Clear"); + } + termResetSequence(); + break; + // Clear from cursor to end of line. + case 'K': + x = _number; + if (x == 0) { + termDebug("Clear to end of line"); + oldX = cursor.x; + for (cx=cursor.x; cx<=_columns; cx++) { + termMoveCursor(cx, cursor.y); + termRenderCharacterAtCursor(' '); + } + termMoveCursor(oldX, cursor.y); + } else { + termDebug("Unimplemented Line Clear"); + } + termResetSequence(); + break; + // Insert lines. + case 'L': + //***TODO*** + termDebug("Unimplemented Insert Line"); + termResetSequence(); + break; + // Delete lines. + case 'M': + //***TODO*** + termDebug("Unimplemented Delete Line"); + termResetSequence(); + break; + // Delete characters. + case 'P': + //***TODO*** + termDebug("Unimplemented Delete Character"); + termResetSequence(); + break; + // Scroll Up. + case 'S': + //***TODO*** + termDebug("Unimplemented Scroll Up"); + termResetSequence(); + break; + // Scroll Down. + case 'T': + //***TODO*** + termDebug("Unimplemented Scroll Down"); + termResetSequence(); + break; + // Clear Screen with Normal Attribute. + case 'U': + x = _bColor; + _bColor = 0; + termUpdateColors(); + termClearScreen(); + updateDisplay = true; + _bColor = x; + termUpdateColors(); + termMoveCursor(1, 1); + termResetSequence(); + break; + // Back-TAB. + case 'Z': + //***TODO*** + termDebug("Unimplemented Back TAB"); + termResetSequence(); + break; + // Set attributes. + case 'm': + _parameters[_parameterCount++] = _number; + for (p=0; p<_parameterCount; p++) { + termDebug("Set attribute %d", _parameters[p]); + x = _parameters[p]; + switch (x) { + case 0: + _bold = false; + _blink = false; + _reverse = false; + _fColor = 7; + _bColor = 0; + termUpdateColors(); + break; + case 1: + _bold = true; + termUpdateColors(); + break; + case 5: + case 6: + _blink = true; + break; + case 7: + if (!_reverse) { + x = _fColor; + _fColor = _bColor; + _bColor = x; + termUpdateColors(); + } + _reverse = true; + break; + case 21: + case 22: + _bold = false; + termUpdateColors(); + break; + case 25: + _blink = false; + break; + case 27: + if (_reverse) { + x = _fColor; + _fColor = _bColor; + _bColor = x; + termUpdateColors(); + } + _reverse = false; + break; + default: + if ((x > 29) && (x < 38)) { + _fColor = x - 30; + termUpdateColors(); + } else { + if ((x > 39) && (x < 48)) { + _bColor = x - 40; + termUpdateColors(); + } else { + termDebug("Unknown attribute: %d", (int)c); + } + } + break; + } + } + termResetSequence(); + break; + // Define scroll region. + case 'r': + //***TODO*** + termDebug("Unimplemented Scroll Region"); + termResetSequence(); + break; + // Cursor save. + case 's': + termDebug("Saving cursor"); + _cursorSave = cursor; + termResetSequence(); + break; + // Cursor restore. + case 'u': + termDebug("Restoring cursor"); + termMoveCursor(_cursorSave.x, _cursorSave.y); + termResetSequence(); + break; + // Something else. + default: + // Number? + if ((c >= '0') && (c <= '9')) { + _number = _number * 10 + (c - '0'); + } else { + termDebug("Unknown sequence: %d", (int)c); + termResetSequence(); + } + break; + } + } else { + switch (c) { + case (char)7: + // Beep! + termDebug("Unimplemented beep"); + break; + case (char)8: + case (char)127: + //***TODO*** Backspace + termDebug("Unimplemented backspace"); + break; + case (char)9: + //***TODO*** TAB + termDebug("Unimplemented tab"); + break; + case (char)10: + cursor.y++; + if (cursor.y > _rows) { + cursor.y--; + termScrollUp(); + updateDisplay = true; + } + termMoveCursor(cursor.x, cursor.y); + break; + case (char)12: + x = _bColor; + _bColor = 0; + termUpdateColors(); + termClearScreen(); + updateDisplay = true; + _bColor = x; + termUpdateColors(); + termMoveCursor(1, 1); + break; + case (char)13: + termMoveCursor(1, cursor.y); + break; + default: + updateDisplay = true; + termRenderCharacterAtCursor(c); + if (_blink) { + //***TODO*** Not handled + } + cursor.x++; + if (cursor.x > _columns) { + cursor.x = 1; + cursor.y++; + if (cursor.y > _rows) { + cursor.y--; + termScrollUp(); + } + } + termMoveCursor(cursor.x, cursor.y); + break; + } + } + + return updateDisplay; +} + + +void termPrint(char *string) { + int x; + int l = (int)strlen(string); + for (x=0; x= 0 ) + { + arg = ( h_type > V4 ) ? 0 : read_code_word( ); + stack[--sp] = ( --argc > 0 ) ? argv[i++] : arg; + } + + /* If the call is asynchronous then call the interpreter directly. + * We will return back here when the corresponding return frame is + * encountered in the ret call. */ + + if ( type == ASYNC ) + { + status = interpret( ); + interpreter_state = RUN; + interpreter_status = 1; + } + + return ( status ); + +} /* z_call */ + +/* + * z_ret + * + * Return from subroutine. Restore FP and PC from stack. + * + */ + +void z_ret( zword_t value ) +{ + zword_t argc; + + /* Clean stack */ + + sp = fp + 1; + + /* Restore argument count, FP and PC */ + + argc = stack[sp++]; + fp = stack[sp++]; + pc = stack[sp++]; + pc += ( unsigned long ) stack[sp++] * PAGE_SIZE; +#if defined(USE_QUETZAL) + --frame_count; +#endif + + /* If this was an async call then stop the interpreter and return + * the value from the async routine. This is slightly hacky using + * a global state variable, but ret can be called with conditional_jump + * which in turn can be called from all over the place, sigh. A + * better design would have all opcodes returning the status RUN, but + * this is too much work and makes the interpreter loop look ugly */ + + if ( ( argc & TYPE_MASK ) == ASYNC ) + { + interpreter_state = STOP; + interpreter_status = ( int ) value; + } + else + { + /* Return subroutine value for function call only */ + if ( ( argc & TYPE_MASK ) == FUNCTION ) + { + store_operand( value ); + } + } + +} /* z_ret */ + +/* + * z_jump + * + * Unconditional jump. Jump is PC relative. + * + */ + +void z_jump( zword_t offset ) +{ + + pc = ( unsigned long ) ( pc + ( ZINT16 ) offset - 2 ); + +} /* z_jump */ + +/* + * z_restart + * + * Restart game by initialising environment and reloading start PC. + * + */ + +void z_restart( void ) +{ + unsigned int i, j, restart_size, scripting_flag; + + /* Reset output buffer */ + + flush_buffer( TRUE ); + + /* Reset text control flags */ + + formatting = ON; + outputting = ON; + redirecting = OFF; + redirect_depth = 0; + scripting_disable = OFF; + + /* Randomise */ + + SRANDOM_FUNC( ( unsigned int ) time( NULL ) ); + + /* Remember scripting state */ + + scripting_flag = get_word( H_FLAGS ) & SCRIPTING_FLAG; + + /* Load restart size and reload writeable data area */ + + restart_size = ( h_restart_size / PAGE_SIZE ) + 1; + for ( i = 0; i < restart_size; i++ ) + { + read_page( i, &datap[i * PAGE_SIZE] ); + } + + /* Restart the screen */ + + z_split_window( 0 ); + set_colours( 1, 1 ); /* set default colors, added by JDH 8/6/95 */ + set_attribute( NORMAL ); + z_erase_window( Z_SCREEN ); + + restart_screen( ); + + /* Reset the interpreter state */ + + restart_interp( scripting_flag ); + + /* Initialise the character translation lookup tables */ + + for ( i = 0; i < 3; i++ ) + { + for ( j = 0; j < 26; j++ ) + { + if ( h_alternate_alphabet_offset ) + { + lookup_table[i][j] = get_byte( h_alternate_alphabet_offset + ( i * 26 ) + j ); + } + else + { + if ( h_type == V1 ) + { + lookup_table[i][j] = v1_lookup_table[i][j]; + } + else + { + lookup_table[i][j] = v3_lookup_table[i][j]; + } + } + } + } + + /* Load start PC, SP and FP */ + + pc = h_start_pc; + sp = STACK_SIZE; + fp = STACK_SIZE - 1; +#if defined (USE_QUETZAL) + frame_count = 0; +#endif + +} /* z_restart */ + + +/* + * restart_interp + * + * Do all the things which need to be done after startup, restart, and restore + * commands. + * + */ + +void restart_interp( int scripting_flag ) +{ + if ( scripting_flag ) + set_word( H_FLAGS, ( get_word( H_FLAGS ) | SCRIPTING_FLAG ) ); + + set_byte( H_INTERPRETER, h_interpreter ); + set_byte( H_INTERPRETER_VERSION, h_interpreter_version ); + set_byte( H_SCREEN_ROWS, screen_rows ); /* Screen dimension in characters */ + set_byte( H_SCREEN_COLUMNS, screen_cols ); + + set_byte( H_SCREEN_LEFT, 0 ); /* Screen dimension in smallest addressable units, ie. pixels */ + set_byte( H_SCREEN_RIGHT, screen_cols ); + set_byte( H_SCREEN_TOP, 0 ); + set_byte( H_SCREEN_BOTTOM, screen_rows ); + + set_byte( H_MAX_CHAR_WIDTH, 1 ); /* Size of a character in screen units */ + set_byte( H_MAX_CHAR_HEIGHT, 1 ); + + /* Initialise status region */ + + if ( h_type < V4 ) + { + z_split_window( 0 ); + blank_status_line( ); + } + + if ( h_type == V3 && fTandy ) + { + zbyte_t config_byte = get_byte( H_CONFIG ); + + config_byte |= CONFIG_TANDY; + set_byte( H_CONFIG, config_byte ); + } + +} /* restart_interp */ + +/* + * z_catch + * + * Return the value of the frame pointer (FP) for later use with throw. + * Before V5 games this was a simple pop. + * + */ + +void z_catch( void ) +{ + if ( h_type > V4 ) + { +#if defined (USE_QUETZAL) + store_operand( frame_count ); +#else + store_operand( fp ); +#endif + } + else + { + sp++; + } +} /* z_catch */ + +/* + * z_throw + * + * Remove one or more stack frames and return. Works like longjmp, see z_catch. + * + */ + +void z_throw( zword_t value, zword_t new_fp ) +{ + + if ( new_fp > fp ) + { + fatal( "z_throw(): nonexistant frame" ); + } +#if defined (USE_QUETZAL) + for ( ; new_fp < frame_count; --frame_count ) + { + sp = fp + 1; + fp = stack[sp + 1]; + } +#else + fp = new_fp; +#endif + + z_ret( value ); + +} /* z_throw */ diff --git a/extern.c b/extern.c new file mode 100644 index 0000000..29a26c4 --- /dev/null +++ b/extern.c @@ -0,0 +1,147 @@ + +/* $Id: extern.c,v 1.2 2000/10/04 23:07:57 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: extern.c,v 1.2 2000/10/04 23:07:57 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: extern.c,v $ + * Revision 1.2 2000/10/04 23:07:57 jholder + * fixed redirect problem with isolatin1 range chars + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * extern.c + * + * Global data. + * + */ + +#include "ztypes.h" + +unsigned char JTERP; +int GLOBALVER; + +/* Stuff for Latin-1 Charset */ +unsigned char zscii2latin1[69] = { + 0xe4, 0xf6, 0xfc, 0xc4, 0xd6, 0xdc, 0xdf, 0xbb, + 0xab, 0xeb, 0xef, 0xff, 0xcb, 0xcf, 0xe1, 0xe9, + 0xed, 0xf3, 0xfa, 0xfd, 0xc1, 0xc9, 0xcd, 0xd3, + 0xda, 0xdd, 0xe0, 0xe8, 0xec, 0xf2, 0xf9, 0xc0, + 0xc8, 0xcc, 0xd2, 0xd9, 0xe2, 0xea, 0xee, 0xf4, + 0xfb, 0xc2, 0xca, 0xce, 0xd4, 0xdb, 0xe5, 0xc5, + 0xf8, 0xd8, 0xe3, 0xf1, 0xf5, 0xc3, 0xd1, 0xd5, + 0xe6, 0xc6, 0xe7, 0xc7, 0xfe, 0xf0, 0xde, 0xd0, + 0xa3, 'o', 'O', 0xa1, 0xbf +}; + +/* Game header data */ + +zbyte_t h_type = 0; +zbyte_t h_config = 0; +zword_t h_version = 0; +zword_t h_data_size = 0; +zword_t h_start_pc = 0; +zword_t h_words_offset = 0; +zword_t h_objects_offset = 0; +zword_t h_globals_offset = 0; +zword_t h_restart_size = 0; +zword_t h_flags = 0; +zword_t h_synonyms_offset = 0; +zword_t h_file_size = 0; +zword_t h_checksum = 0; +zbyte_t h_interpreter = INTERP_MSDOS; +zbyte_t h_interpreter_version = 'B'; /* Interpreter version 2 */ +zword_t h_alternate_alphabet_offset = 0; +zword_t h_unicode_table = 0; + +/* Game version specific data */ + +int story_scaler = 0; +int story_shift = 0; +int property_mask = 0; +int property_size_mask = 0; + +/* Stack and PC data */ + +zword_t stack[STACK_SIZE]; +zword_t sp = STACK_SIZE; +zword_t fp = STACK_SIZE - 1; +zword_t frame_count = 0; /* frame pointer for get_fp */ +unsigned long pc = 0; +int interpreter_state = RUN; +int interpreter_status = 0; + +/* Data region data */ + +unsigned int data_size = 0; +zbyte_t *datap = NULL; +zbyte_t *undo_datap = NULL; + +/* Screen size data */ + +int screen_rows = 0; +int screen_cols = 0; +int right_margin = DEFAULT_RIGHT_MARGIN; +int top_margin = DEFAULT_TOP_MARGIN; +char bigscreen = 0; +char monochrome = 0; +int hist_buf_size; + +/* Current window data */ + +int screen_window = TEXT_WINDOW; +int interp_initialized = 0; + +/* Formatting and output control data */ + +int gFontNum = 0; +int formatting = ON; +int outputting = ON; +int redirecting = OFF; +int redirect_depth = 0; /* 1 or higher means ON */ +int scripting_disable = OFF; +int scripting = OFF; +int recording = OFF; +int replaying = OFF; +int font = 1; +int use_bg_color = 1; +ZINT16 default_fg = 9, default_bg = 6; + +/* Status region data */ + +int status_active = OFF; +int status_size = 0; + +/* Tandy bit requested */ + +char fTandy = 0; + +/* IBM (not international) glyphs requested */ + +char fIBMGraphics = 0; + +/* Text output buffer data */ + +int lines_written = 0; +int status_pos = 0; + +/* Dynamic buffer data */ + +char *line = NULL; +char *status_line = NULL; + +/* Character translation tables */ + +char lookup_table[3][26]; diff --git a/fileio.c b/fileio.c new file mode 100644 index 0000000..7af60ac --- /dev/null +++ b/fileio.c @@ -0,0 +1,1350 @@ + +/* $Id: fileio.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: fileio.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: fileio.c,v $ + * Revision 1.3 2000/07/05 15:20:34 jholder + * Updated code to remove warnings. + * + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * fileio.c + * + * File manipulation routines. Should be generic. + * + */ + +#include "ztypes.h" +//#include "jzexe.h" /* mol 951115 */ + +/* Static data */ + +extern int GLOBALVER; + +#ifdef USE_ZLIB +static gzFile *gfp = NULL; /* Zcode file pointer */ +#else +static FILE *gfp = NULL; /* Zcode file pointer */ +#endif + +static FILE *sfp = NULL; /* Script file pointer */ +static FILE *rfp = NULL; /* Record file pointer */ + +#if defined BUFFER_FILES +#ifndef USE_ZLIB +static char gfpbuffer[BUFSIZ]; +#endif +#endif +static char sfpbuffer[BUFSIZ]; +static char rfpbuffer[BUFSIZ]; + +char save_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1] = "story.sav"; +char script_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1] = "story.scr"; +char record_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1] = "story.rec"; +char auxilary_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1] = "story.aux"; + +static int undo_valid = FALSE; +static zword_t undo_stack[STACK_SIZE]; + +static int script_file_valid = FALSE; + +static long story_offset = 0; /* mol 951114 */ +//char *magic = ( char * ) MAGIC_STRING; /* mol */ + +static int save_restore( const char *, int ); + +/* + * set_names + * + * Set up the story names intelligently. + * John Holder, 28 Sept 1995 + */ +void set_names( const char *storyname ) +{ + char *per_pos = 0; + + strcpy( save_name, storyname ); + strcpy( script_name, storyname ); + strcpy( record_name, storyname ); + strcpy( auxilary_name, storyname ); + + /* experimental setting of save_name, added by John Holder 26 July 1995 */ + per_pos = strrchr( storyname, '.' ); /* find last '.' in storyname. */ + if ( per_pos ) /* The story file looks like "Odius.dat" or "odieus.z3" */ + { + per_pos = strrchr( save_name, '.' ); + *( per_pos ) = '\0'; + strcat( save_name, ".sav" ); + + per_pos = strrchr( script_name, '.' ); + *( per_pos ) = '\0'; + strcat( script_name, ".scr" ); + + per_pos = strrchr( record_name, '.' ); + *( per_pos ) = '\0'; + strcat( record_name, ".rec" ); + + per_pos = strrchr( auxilary_name, '.' ); + *( per_pos ) = '\0'; + strcat( auxilary_name, ".aux" ); + } + else /* The story file looks like: "OdieusQuest" */ + { + strcat( save_name, ".sav" ); + strcat( script_name, ".src" ); + strcat( record_name, ".rec" ); + strcat( auxilary_name, ".aux" ); + } +} /* set_names */ + +/* + * open_story + * + * Open game file for read. + * + */ + +void open_story( const char *storyname ) +{ + char *path, *p; + char tmp[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1]; + +// if ( !STANDALONE_FLAG ) +// { + story_offset = 0; +// } +// else +// { + /* standalone game; offset to story start is saved in low-endian */ + /* format after magic string */ +// story_offset = +// magic[MAGIC_END + 1] + magic[MAGIC_END + 2] * 256L + magic[MAGIC_END + 3] * 65536L; +// } + + strcpy( tmp, storyname ); + if ( ( gfp = jz_open( tmp, "rb" ) ) != NULL ) + { +#if defined BUFFER_FILES +#ifndef USE_ZLIB + setbuf( gfp, gfpbuffer ); +#endif +#endif + set_names( storyname ); + return; +#if defined MSDOS || defined OS2 + } + else + { + sprintf( tmp, "%s.exe", storyname ); + if ( ( gfp = jz_open( tmp, "rb" ) ) != NULL ) + { +#if defined BUFFER_FILES +#ifndef USE_ZLIB + setbuf( gfp, gfpbuffer ); +#endif +#endif + set_names( storyname ); + return; + } +#endif + } + + /* + if ( !STANDALONE_FLAG && ( path = getenv( "INFOCOM_PATH" ) ) == NULL ) + { + fprintf( stderr, "%s ", tmp ); + fatal( "open_story(): Zcode file not found" ); + } + else if ( STANDALONE_FLAG && ( path = getenv( "PATH" ) ) == NULL ) + { + fprintf( stderr, "%s ", tmp ); + fatal( "open_story(): Zcode file not found" ); + } + */ + + /* dos path will be like: */ + /* SET INFOCOM_PATH = C:\INFOCOM\LTOI1;C:\INFOCOM\LTOI2;C:\INFOCOM\INFORM */ +#if defined MSDOS || defined OS2 + p = strtok( path, ";" ); +#else + /* UNIX path will be like: */ + /* setenv INFOCOM_PATH /usr/local/lib/ltoi1:/usr/local/lib/ltoi2 */ + p = strtok( path, ":" ); +#endif + + while ( p ) + { + sprintf( tmp, "%s/%s", p, storyname ); + if ( ( gfp = jz_open( tmp, "rb" ) ) != NULL ) + { +#if defined BUFFER_FILES +#ifndef USE_ZLIB + setbuf( gfp, gfpbuffer ); +#endif +#endif + set_names( storyname ); + return; +#if defined MSDOS || defined OS2 + } + else + { + sprintf( tmp, "%s/%s.exe", p, storyname ); + if ( ( gfp = jz_open( tmp, "rb" ) ) != NULL ) + { +#if defined BUFFER_FILES +#ifndef USE_ZLIB + setbuf( gfp, gfpbuffer ); +#endif +#endif + set_names( storyname ); + return; + } +#endif + } +#if defined MSDOS || defined OS2 + p = strtok( NULL, ";" ); +#else + p = strtok( NULL, ":" ); +#endif + } + + fprintf( stderr, "%s ", tmp ); + fatal( "open_story(): Zcode file not found" ); +} /* open_story */ + + +/* + * close_story + * + * Close game file if open. + * + */ + +void close_story( void ) +{ + + if ( gfp != NULL ) + { + jz_close( gfp ); + } + +} /* close_story */ + +/* + * get_story_size + * + * Calculate the size of the game file. Only used for very old games that do not + * have the game file size in the header. + * + */ + +unsigned int get_story_size( void ) +{ + unsigned long file_length; + + /* Read whole file to calculate file size */ + jz_rewind( gfp ); + for ( file_length = 0; jz_getc( gfp ) != EOF; file_length++ ) + ; + jz_rewind( gfp ); + + /* Calculate length of file in game allocation units */ + file_length = + ( file_length + ( unsigned long ) ( story_scaler - 1 ) ) / ( unsigned long ) story_scaler; + + return ( ( unsigned int ) file_length ); + +} /* get_story_size */ + +/* + * read_page + * + * Read one game file page. + * + */ + +void read_page( int page, void *buffer ) +{ + unsigned long file_size; + unsigned int pages, offset; + + /* Seek to start of page */ + jz_seek( gfp, story_offset + ( long ) page * PAGE_SIZE, SEEK_SET ); + + /* Read the page */ + +#ifdef USE_ZLIB + if ( gzread( gfp, buffer, PAGE_SIZE ) == -1 ) +#else + if ( fread( buffer, PAGE_SIZE, 1, gfp ) != 1 ) +#endif + { + /* Read failed. Are we in the last page? */ + file_size = ( unsigned long ) h_file_size *story_scaler; + + pages = ( unsigned int ) ( ( unsigned long ) file_size / PAGE_SIZE ); + offset = ( unsigned int ) ( ( unsigned long ) file_size & PAGE_MASK ); + + if ( ( unsigned int ) page == pages ) + { + /* Read partial page if this is the last page in the game file */ + jz_seek( gfp, story_offset + ( long ) page * PAGE_SIZE, SEEK_SET ); +#ifdef USE_ZLIB + if ( gzread( gfp, buffer, offset ) == -1 ) +#else + if ( fread( buffer, offset, 1, gfp ) != 1 ) +#endif + { + fatal( "read_page(): Zcode file read error" ); + } + } + } + +} /* read_page */ + +/* + * z_verify + * + * Verify game ($verify verb). Add all bytes in game file except for bytes in + * the game file header. + * + */ + +void z_verify( void ) +{ + unsigned long file_size; + unsigned int pages, offset; + unsigned int start, end, i, j; + zword_t checksum = 0; + zbyte_t buffer[PAGE_SIZE] = { 0 }; + char szBuffer[6] = { 0 }; + + /* Print version banner */ + + z_new_line( ); + write_string( "Running on " ); + write_string( JZIPVER ); + write_string( " (" ); + switch ( JTERP ) + { + case INTERP_GENERIC: + write_string( "Generic" ); + break; + case INTERP_AMIGA: + write_string( "Amiga" ); + break; + case INTERP_ATARI_ST: + write_string( "Atari ST" ); + break; + case INTERP_MSDOS: + write_string( "DOS" ); + break; + case INTERP_UNIX: + write_string( "UNIX" ); + break; + case INTERP_VMS: + write_string( "VMS" ); + break; + } + write_string( "). Reporting Spec " ); + sprintf( szBuffer, "%d.%d", get_byte( H_STANDARD_HIGH ), get_byte( H_STANDARD_LOW ) ); + write_string( szBuffer ); + write_string( " Compliance." ); + z_new_line( ); + + write_string( "Compile options: " ); +#ifdef USE_QUETZAL + write_string( "USE_QUETZAL " ); +#endif +#ifdef STRICTZ + write_string( "STRICTZ " ); +#endif +#ifdef USE_ZLIB + write_string( "USE_ZLIB " ); +#endif +#ifdef LOUSY_RANDOM + write_string( "LOUSY_RANDOM " ); +#endif +#ifdef HARD_COLORS + write_string( "HARD_COLORS " ); +#endif + z_new_line( ); + + write_string( "Release " ); + write_string( JZIPRELDATE ); + write_string( "." ); + z_new_line( ); + + write_string( "Playing a Version " ); + z_print_num( (zword_t) GLOBALVER ); + write_string( " Story." ); + z_new_line( ); + + z_new_line( ); + + /* Calculate game file dimensions */ + + file_size = ( unsigned long ) h_file_size *story_scaler; + + pages = ( unsigned int ) ( ( unsigned long ) file_size / PAGE_SIZE ); + offset = ( unsigned int ) file_size & PAGE_MASK; + + /* Sum all bytes in game file, except header bytes */ + + for ( i = 0; i <= pages; i++ ) + { + read_page( i, buffer ); + start = ( i == 0 ) ? 64 : 0; + end = ( i == pages ) ? offset : PAGE_SIZE; + for ( j = start; j < end; j++ ) + { + checksum += buffer[j]; + } + } + + /* Make a conditional jump based on whether the checksum is equal */ + + conditional_jump( checksum == h_checksum ); + +} /* z_verify */ + + +/* + * get_default_name + * + * Read a default file name from the memory of the Z-machine and + * copy it to an array. + * + */ + +static void get_default_name( char *default_name, zword_t addr ) +{ + zbyte_t len; + zbyte_t c; + unsigned int i; + + if ( addr != 0 ) + { + len = get_byte( addr ); + + for ( i = 0; i < len; i++ ) + { + addr++; + c = get_byte( addr ); + default_name[i] = c; + } + default_name[i] = 0; + + if ( strchr( default_name, '.' ) == 0 ) + { + strcpy( default_name + i, ".aux" ); + } + + } + else + { + strcpy( default_name, auxilary_name ); + } + +} /* get_default_name */ + + +/* + * z_save + * + * Saves data to disk. Returns: + * 0 = save failed + * 1 = save succeeded + * + */ + +int z_save( int argc, zword_t table, zword_t bytes, zword_t name ) +{ + char new_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1]; + char default_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1]; + FILE *afp; + +#if defined BUFFER_FILES + char afpbuffer[BUFSIZ]; +#endif + int status = 0; + + if ( argc == 3 ) + { + get_default_name( default_name, name ); + if ( get_file_name( new_name, default_name, GAME_SAVE_AUX ) != 0 ) + { + goto finished; + } + + if ( ( afp = fopen( new_name, "wb" ) ) == NULL ) + { + goto finished; + } + +#if defined BUFFER_FILES + setbuf( afp, afpbuffer ); +#endif + + status = fwrite( datap + table, bytes, 1, afp ); + + fclose( afp ); + + if ( status != 0 ) + { + strcpy( auxilary_name, default_name ); + } + + status = !status; + } + else + { + /* Get the file name */ + status = 1; + + if ( get_file_name( new_name, save_name, GAME_SAVE ) == 0 ) + { + /* Do a save operation */ + if ( save_restore( new_name, GAME_SAVE ) == 0 ) + { + /* Cleanup file */ + file_cleanup( new_name, GAME_SAVE ); + + /* Save the new name as the default file name */ + strcpy( save_name, new_name ); + + /* Indicate success */ + status = 0; + } + } + } + + finished: + + /* Return result of save to Z-code */ + + if ( h_type < V4 ) + { + conditional_jump( status == 0 ); + } + else + { + store_operand( (zword_t)(( status == 0 ) ? 1 : 0) ); + } + + return ( status ); +} /* z_save */ + + +/* + * z_restore + * + * Restore game state from disk. Returns: + * 0 = restore failed + * 2 = restore succeeded + */ + +int z_restore( int argc, zword_t table, zword_t bytes, zword_t name ) +{ + char new_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1]; + char default_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1]; + FILE *afp; + +#if defined BUFFER_FILES + char afpbuffer[BUFSIZ]; +#endif + int status; + + status = 0; + + if ( argc == 3 ) + { + get_default_name( default_name, name ); + if ( get_file_name( new_name, default_name, GAME_LOAD_AUX ) == 0 ) + { + goto finished; + } + + if ( ( afp = fopen( new_name, "rb" ) ) == NULL ) + { + goto finished; + } + +#if defined BUFFER_FILES + setbuf( afp, afpbuffer ); +#endif + + status = fread( datap + table, bytes, 1, afp ); + + fclose( afp ); + + if ( status != 0 ) + { + strcpy( auxilary_name, default_name ); + } + + status = !status; + } + else + { + /* Get the file name */ + status = 1; + if ( get_file_name( new_name, save_name, GAME_RESTORE ) == 0 ) + { + /* Do the restore operation */ + if ( save_restore( new_name, GAME_RESTORE ) == 0 ) + { + /* Reset the status region (this is just for Seastalker) */ + if ( h_type < V4 ) + { + z_split_window( 0 ); + blank_status_line( ); + } + + /* Cleanup file */ + file_cleanup( new_name, GAME_SAVE ); + + /* Save the new name as the default file name */ + strcpy( save_name, new_name ); + + /* Indicate success */ + status = 0; + } + } + } + + finished: + /* Return result of save to Z-code */ + + if ( h_type < V4 ) + { + conditional_jump( status == 0 ); + } + else + { + store_operand( (zword_t)(( status == 0 ) ? 2 : 0) ); + } + + return ( status ); +} /* z_restore */ + +/* + * z_save_undo + * + * Save the current Z machine state in memory for a future undo. Returns: + * -1 = feature unavailable + * 0 = save failed + * 1 = save succeeded + * + */ + +void z_save_undo( void ) +{ + /* Check if undo is available first */ + if ( undo_datap != NULL ) + { + /* Save the undo data and return success */ + save_restore( NULL, UNDO_SAVE ); + undo_valid = TRUE; + store_operand( 1 ); + } + else + { + /* If no memory for data area then say undo is not available */ + store_operand( ( zword_t ) - 1 ); + } + +} /* z_save_undo */ + +/* + * z_restore_undo + * + * Restore the current Z machine state from memory. Returns: + * -1 = feature unavailable + * 0 = restore failed + * 2 = restore succeeded + * + */ + +void z_restore_undo( void ) +{ + /* Check if undo is available first */ + if ( undo_datap != NULL ) + { + /* If no undo save done then return an error */ + if ( undo_valid == TRUE ) + { + /* Restore the undo data and return success */ + save_restore( NULL, UNDO_RESTORE ); + store_operand( 2 ); + } + else + { + store_operand( 0 ); + } + } + else + { + /* If no memory for data area then say undo is not available */ + store_operand( ( zword_t ) - 1 ); + } + +} /* z_restore_undo */ + + +/* + * swap_bytes + * + * Swap the low and high bytes in every word of the specified array. + * The length is specified in BYTES! + * + * This routine added by Mark Phillips(msp@bnr.co.uk), Thanks Mark! + */ +#if !defined(USE_QUETZAL) +void swap_bytes( zword_t * ptr, int len ) +{ + unsigned char *pbyte; + unsigned char tmp; + + len /= 2; /* convert len into number of 2 byte words */ + + pbyte = ( unsigned char * ) ptr; + + while ( len ) + { + tmp = pbyte[0]; + pbyte[0] = pbyte[1]; + pbyte[1] = tmp; + pbyte += 2; + len--; + } + return; +} +#endif + +/* + * save_restore + * + * Common save and restore code. Just save or restore the game stack and the + * writeable data area. + * + */ + +static int save_restore( const char *file_name, int flag ) +{ + FILE *tfp = NULL; + +#if defined BUFFER_FILES + char tfpbuffer[BUFSIZ]; +#endif + int scripting_flag = 0, status = 0; + +#if !defined(USE_QUETZAL) + zword_t zw; + int little_endian = 0; + + /* Find out if we are big-endian */ + zw = 0x0001; + if ( *( zbyte_t * ) & zw ) + { /* We are little-endian, like an Intel 80x86 chip. */ + little_endian = 1; + } +#endif + + /* Open the save file and disable scripting */ + + if ( flag == GAME_SAVE || flag == GAME_RESTORE ) + { + if ( ( tfp = fopen( file_name, ( flag == GAME_SAVE ) ? "wb" : "rb" ) ) == NULL ) + { + output_line( "Cannot open SAVE file" ); + return ( 1 ); + } +#if defined BUFFER_FILES + setbuf( tfp, tfpbuffer ); +#endif + scripting_flag = get_word( H_FLAGS ) & SCRIPTING_FLAG; + set_word( H_FLAGS, get_word( H_FLAGS ) & ( ~SCRIPTING_FLAG ) ); + } + +#if defined(USE_QUETZAL) + if ( flag == GAME_SAVE ) + { + status = !save_quetzal( tfp, gfp ); + } + else if ( flag == GAME_RESTORE ) + { + status = !restore_quetzal( tfp, gfp ); + } + else + { +#endif /* defined(USE_QUETZAL) */ + /* Push PC, FP, version and store SP in special location */ + + stack[--sp] = ( zword_t ) ( pc / PAGE_SIZE ); + stack[--sp] = ( zword_t ) ( pc % PAGE_SIZE ); + stack[--sp] = fp; + stack[--sp] = h_version; + stack[0] = sp; + + /* Save or restore stack */ + +#if !defined(USE_QUETZAL) + if ( flag == GAME_SAVE ) + { + if ( little_endian ) + swap_bytes( stack, sizeof ( stack ) ); + if ( status == 0 && fwrite( stack, sizeof ( stack ), 1, tfp ) != 1 ) + status = 1; + if ( little_endian ) + swap_bytes( stack, sizeof ( stack ) ); + } + else if ( flag == GAME_RESTORE ) + { + if ( little_endian ) + swap_bytes( stack, sizeof ( stack ) ); + if ( status == 0 && fread( stack, sizeof ( stack ), 1, tfp ) != 1 ) + status = 1; + if ( little_endian ) + swap_bytes( stack, sizeof ( stack ) ); + } + else +#endif /* !defined(USE_QUETZAL) */ + { + if ( flag == UNDO_SAVE ) + { + memmove( undo_stack, stack, sizeof ( stack ) ); + } + else /* if (flag == UNDO_RESTORE) */ + { + memmove( stack, undo_stack, sizeof ( stack ) ); + } + } + + /* Restore SP, check version, restore FP and PC */ + + sp = stack[0]; + + if ( stack[sp++] != h_version ) + { + fatal( "save_restore(): Wrong game or version" ); + } + + fp = stack[sp++]; + pc = stack[sp++]; + pc += ( unsigned long ) stack[sp++] * PAGE_SIZE; + + /* Save or restore writeable game data area */ + +#if !defined(USE_QUETZAL) + if ( flag == GAME_SAVE ) + { + if ( status == 0 && fwrite( datap, h_restart_size, 1, tfp ) != 1 ) + status = 1; + } + else if ( flag == GAME_RESTORE ) + { + if ( status == 0 && fread( datap, h_restart_size, 1, tfp ) != 1 ) + status = 1; + } + else +#endif /* !defined(USE_QUETZAL) */ + { + if ( flag == UNDO_SAVE ) + { + memmove( undo_datap, datap, h_restart_size ); + } + else /* if (flag == UNDO_RESTORE) */ + { + memmove( datap, undo_datap, h_restart_size ); + } + } + +#if defined(USE_QUETZAL) + } +#endif /* defined(USE_QUETZAL) */ + + + /* Close the save file and restore scripting */ + + if ( flag == GAME_SAVE ) + { + fclose( tfp ); + if ( scripting_flag ) + { + set_word( H_FLAGS, get_word( H_FLAGS ) | SCRIPTING_FLAG ); + } + } + else if ( flag == GAME_RESTORE ) + { + fclose( tfp ); + restart_screen( ); + restart_interp( scripting_flag ); + } + + /* Handle read or write errors */ + + if ( status ) + { + if ( flag == GAME_SAVE ) + { + output_line( "Write to SAVE file failed" ); + remove( file_name ); + } + else + { + fatal( "save_restore(): Read from SAVE file failed" ); + } + } + + return ( status ); + +} /* save_restore */ + +/* + * open_script + * + * Open the scripting file. + * + */ + +void open_script( void ) +{ + char new_script_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1]; + + /* Open scripting file if closed */ + if ( scripting == OFF ) + { + if ( script_file_valid == TRUE ) + { + sfp = fopen( script_name, "a" ); + + /* Turn on scripting if open succeeded */ + if ( sfp != NULL ) + { +#if defined BUFFER_FILES + setbuf( sfp, sfpbuffer ); +#endif + scripting = ON; + } + else + { + output_line( "Script file open failed" ); + } + } + else + { /* Get scripting file name and record it */ + if ( get_file_name( new_script_name, script_name, GAME_SCRIPT ) == 0 ) + { + /* Open scripting file */ + sfp = fopen( new_script_name, "w" ); + + /* Turn on scripting if open succeeded */ + if ( sfp != NULL ) + { +#if defined BUFFER_FILES + setbuf( sfp, sfpbuffer ); +#endif + script_file_valid = TRUE; + + /* Make file name the default name */ + strcpy( script_name, new_script_name ); + + /* Turn on scripting */ + scripting = ON; + } + else + { + output_line( "Script file create failed" ); + } + } + } + } + + /* Set the scripting flag in the game file flags */ + if ( scripting == ON ) + { + set_word( H_FLAGS, get_word( H_FLAGS ) | SCRIPTING_FLAG ); + } + else + { + set_word( H_FLAGS, get_word( H_FLAGS ) & ( ~SCRIPTING_FLAG ) ); + } + +} /* open_script */ + +/* + * flush_script + * Flush the scripting file. + * + */ +void flush_script( void ) +{ +/* Flush scripting file if open */ + if ( scripting == ON ) + { + fflush( sfp ); + } +} + + +/* + * close_script + * + * Close the scripting file. + * + */ +void close_script( void ) +{ + /* Close scripting file if open */ + if ( scripting == ON ) + { + fclose( sfp ); +#if 0 + /* Cleanup */ + file_cleanup( script_name, GAME_SCRIPT ); +#endif + /* Turn off scripting */ + scripting = OFF; + } + + /* Set the scripting flag in the game file flags */ + if ( scripting == OFF ) + { + set_word( H_FLAGS, get_word( H_FLAGS ) & ( ~SCRIPTING_FLAG ) ); + } + else + { + set_word( H_FLAGS, get_word( H_FLAGS ) | SCRIPTING_FLAG ); + } + +} /* close_script */ + +/* + * script_char + * + * Write one character to scripting file. + * + * Check the state of the scripting flag first. Older games only set the + * scripting flag in the game flags instead of calling the set_print_modes + * function. This is because they expect a physically attached printer that + * doesn't need opening like a file. + */ + +void script_char( int c ) +{ + + /* Check the state of the scripting flag in the game flags. If it is on + * then check to see if the scripting file is open as well */ + + if ( ( get_word( H_FLAGS ) & SCRIPTING_FLAG ) != 0 && scripting == OFF ) + { + open_script( ); + } + + /* Check the state of the scripting flag in the game flags. If it is off + * then check to see if the scripting file is closed as well */ + + if ( ( get_word( H_FLAGS ) & SCRIPTING_FLAG ) == 0 && scripting == ON ) + { + close_script( ); + } + + /* If scripting file is open, we are in the text window and the character is + * printable then write the character */ + + if ( scripting == ON && scripting_disable == OFF && ( c == '\n' || ( isprint( c ) ) ) ) + { + putc( c, sfp ); + } + +} /* script_char */ + +/* + * script_string + * + * Write a string to the scripting file. + * + */ + +void script_string( const char *s ) +{ + /* Write string */ + while ( *s ) + { + script_char( *s++ ); + } + +} /* script_string */ + +/* + * script_line + * + * Write a string followed by a new line to the scripting file. + * + */ + +void script_line( const char *s ) +{ + + /* Write string */ + script_string( s ); + + /* Write new line */ + script_new_line( ); + +} /* script_line */ + +/* + * script_new_line + * + * Write a new line to the scripting file. + * + */ + +void script_new_line( void ) +{ + + script_char( '\n' ); + +} /* script_new_line */ + +/* + * open_record + * + * Turn on recording of all input to an output file. + * + */ + +void open_record( void ) +{ + char new_record_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1]; + + /* If recording or playback is already on then complain */ + + if ( recording == ON || replaying == ON ) + { + output_line( "Recording or playback are already active." ); + } + else + { /* Get recording file name */ + if ( get_file_name( new_record_name, record_name, GAME_RECORD ) == 0 ) + { + /* Open recording file */ + rfp = fopen( new_record_name, "w" ); + + /* Turn on recording if open succeeded */ + if ( rfp != NULL ) + { +#if defined BUFFER_FILES + setbuf( rfp, rfpbuffer ); +#endif + /* Make file name the default name */ + strcpy( record_name, new_record_name ); + + /* Set recording on */ + recording = ON; + } + else + { + output_line( "Record file create failed" ); + } + } + } + +} /* open_record */ + +/* + * record_line + * + * Write a string followed by a new line to the recording file. + * + */ + +void record_line( const char *s ) +{ + if ( recording == ON && replaying == OFF ) + { + /* Write string */ + fprintf( rfp, "%s\n", s ); + } + +} /* record_line */ + +/* + * record_key + * + * Write a key followed by a new line to the recording file. + * + */ + +void record_key( int c ) +{ + if ( recording == ON && replaying == OFF ) + { + /* Write the key */ + fprintf( rfp, "<%0o>\n", c ); + } + +} /* record_key */ + +/* + * close_record + * + * Turn off recording of all input to an output file. + * + */ + +void close_record( void ) +{ + /* Close recording file */ + if ( rfp != NULL ) + { + fclose( rfp ); + rfp = NULL; + + /* Cleanup */ + + if ( recording == ON ) + { + file_cleanup( record_name, GAME_RECORD ); + } + else /* (replaying == ON) */ + { + file_cleanup( record_name, GAME_PLAYBACK ); + } + } + + /* Set recording and replaying off */ + + recording = OFF; + replaying = OFF; + +} /* close_record */ + +/* + * z_input_stream + * + * Take input from command file instead of keyboard. + * + */ + +void z_input_stream( int arg ) +{ + char new_record_name[Z_FILENAME_MAX + Z_PATHNAME_MAX + 1]; + + UNUSEDVAR( arg ); + + /* If recording or replaying is already on then complain */ + + if ( recording == ON || replaying == ON ) + { + output_line( "Recording or replaying is already active." ); + } + else + { /* Get recording file name */ + + if ( get_file_name( new_record_name, record_name, GAME_PLAYBACK ) == 0 ) + { + /* Open recording file */ + rfp = fopen( new_record_name, "r" ); + + /* Turn on recording if open succeeded */ + if ( rfp != NULL ) + { +#if defined BUFFER_FILES + setbuf( rfp, rfpbuffer ); +#endif + /* Make file name the default name */ + strcpy( record_name, new_record_name ); + + /* Set replaying on */ + replaying = ON; + } + else + { + output_line( "Record file open failed" ); + } + } + } + +} /* z_input_stream */ + +/* + * playback_line + * + * Get a line of input from the command file. + * + */ + +int playback_line( int buflen, char *buffer, int *read_size ) +{ + char *cp; + + if ( recording == ON || replaying == OFF ) + { + return ( -1 ); + } + + if ( fgets( buffer, buflen, rfp ) == NULL ) + { + close_record( ); + return ( -1 ); + } + else + { + cp = strrchr( buffer, '\n' ); + if ( cp != NULL ) + { + *cp = '\0'; + } + *read_size = strlen( buffer ); + output_line( buffer ); + } + + return ( '\n' ); + +} /* playback_line */ + +/* + * playback_key + * + * Get a key from the command file. + * + */ + +int playback_key( void ) +{ + int c; + + if ( recording == ON || replaying == OFF ) + { + return ( -1 ); + } + + if ( fscanf( rfp, "<%o>\n", &c ) == EOF ) + { + close_record( ); + c = -1; + } + + return ( c ); + +} /* playback_key */ diff --git a/gamedata.z5 b/gamedata.z5 new file mode 100644 index 0000000000000000000000000000000000000000..474c9d9d73df6c1835974055ddb0f4bd85914023 GIT binary patch literal 188416 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'QUw`^Xbq^^vm;m$|P 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 z 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 V4 ) ? cbuf[1] : 0; + + /* Read the line then script and record it */ + + terminator = get_line( cbuf, argv[2], argv[3] ); + script_line( ( h_type > V4 ) ? &cbuf[2] : &cbuf[1] ); + record_line( ( h_type > V4 ) ? &cbuf[2] : &cbuf[1] ); + + /* Convert new text in line to lowercase */ + + if ( h_type > V4 ) + { + buffer = &cbuf[2]; + out_size = cbuf[1]; + } + else + { + buffer = &cbuf[1]; + out_size = strlen( buffer ); + } + + if ( out_size > in_size ) + for ( i = in_size; i < out_size; i++ ) + buffer[i] = ( char ) tolower( buffer[i] ); + + /* Tokenise the line, if a token buffer is present */ + + if ( argv[1] ) + tokenise_line( argv[0], argv[1], h_words_offset, 0 ); + + /* Return the line terminator */ + + if ( h_type > V4 ) + store_operand( ( zword_t ) terminator ); + +} /* z_sread_aread */ + +/* + * get_line + * + * Read a line of input and lower case it. + * + */ + +int get_line( char *cbuf, zword_t timeout, zword_t action_routine ) +{ + char *buffer; + int buflen, read_size, status, c; + zword_t arg_list[2]; + + /* Set maximum buffer size to width of screen minus any + * right margin and 1 character for a terminating NULL */ + + buflen = ( screen_cols > 127 ) ? 127 : screen_cols; + buflen -= right_margin + 1; + if ( ( int ) cbuf[0] <= buflen ) + buflen = cbuf[0]; + + /* Set read size and start of read buffer. The buffer may already be + * primed with some text in V5 games. The Z-code will have already + * displayed the text so we don't have to do that */ + + if ( h_type > V4 ) + { + read_size = cbuf[1]; + buffer = &cbuf[2]; + } + else + { + read_size = 0; + buffer = &cbuf[1]; + } + + /* Try to read input from command file */ + + c = playback_line( buflen, buffer, &read_size ); + + if ( c == -1 ) + { + + /* Setup the timeout routine argument list */ + + arg_list[0] = action_routine; + arg_list[1] = 0; /* as per spec.1.0 */ + /* arg_list[1] = timeout/10; */ + + /* Read a line with a timeout. If the input timed out then + * call the timeout action routine. If the return status from the + * timeout routine was 0 then try to read the line again */ + + do + { + c = input_line( buflen, buffer, timeout, &read_size ); + status = 0; + } + while ( c == -1 && ( status = z_call( 1, arg_list, ASYNC ) ) == 0 ); + + /* Throw away any input if timeout returns success */ + + if ( status ) + read_size = 0; + + + } + + /* Zero terminate line */ + + if ( h_type > V4 ) + { + cbuf[1] = ( char ) read_size; + } + else + { + /* Zero terminate line (V1-4 only) */ + buffer[read_size] = '\0'; + } + + return ( c ); + +} /* get_line */ + +/* + * tokenise_line + * + * Convert a typed input line into tokens. The token buffer needs some + * additional explanation. The first byte is the maximum number of tokens + * allowed. The second byte is set to the actual number of token read. Each + * token is composed of 3 fields. The first (word) field contains the word + * offset in the dictionary, the second (byte) field contains the token length, + * and the third (byte) field contains the start offset of the token in the + * character buffer. + * + */ + +static void tokenise_line( zword_t char_buf, zword_t token_buf, zword_t dictionary, zword_t flag ) +{ + int i, count, words, token_length; + long word_index, chop = 0; + int slen; + char *str_end; + char *cbuf, *tbuf, *tp; + const char *cp, *token; + char punctuation[16]; + zword_t word; + + /* Initialise character and token buffer pointers */ + + cbuf = ( char * ) &datap[char_buf]; + tbuf = ( char * ) &datap[token_buf]; + + /* Find the string length */ + + if ( h_type > V4 ) + { + slen = ( unsigned char ) ( cbuf[1] ); + str_end = cbuf + 2 + slen; + } + else + { + slen = strlen( cbuf + 1 ); + str_end = cbuf + 1 + slen; + } + + /* Initialise word count and pointers */ + + words = 0; + cp = ( h_type > V4 ) ? cbuf + 2 : cbuf + 1; + tp = tbuf + 2; + + /* Initialise dictionary */ + + count = get_byte( dictionary++ ); + for ( i = 0; i < count; i++ ) + punctuation[i] = get_byte( dictionary++ ); + punctuation[i] = '\0'; + entry_size = get_byte( dictionary++ ); + dictionary_size = ( ZINT16 ) get_word( dictionary ); + dictionary_offset = dictionary + 2; + + /* Calculate the binary chop start position */ + + if ( dictionary_size > 0 ) + { + word_index = dictionary_size / 2; + chop = 1; + do + chop *= 2; + while ( word_index /= 2 ); + } + + /* Tokenise the line */ + + do + { + + /* Skip to next token */ + + cp = next_token( cp, str_end, &token, &token_length, punctuation ); + if ( token_length ) + + /* If still space in token buffer then store word */ + + if ( words <= tbuf[0] ) + { + + /* Get the word offset from the dictionary */ + + word = find_word( token_length, token, chop ); + + /* Store the dictionary offset, token length and offset */ + + if ( word || flag == 0 ) + { + tp[0] = ( char ) ( word >> 8 ); + tp[1] = ( char ) ( word & 0xff ); + } + tp[2] = ( char ) token_length; + tp[3] = ( char ) ( token - cbuf ); + + /* Step to next token position and count the word */ + + tp += 4; + words++; + } + else + { + + /* Moan if token buffer space exhausted */ + + output_string( "Too many words typed, discarding: " ); + output_line( token ); + } + } + while ( token_length ); + + /* Store word count */ + + tbuf[1] = ( char ) words; + +} /* tokenise_line */ + +/* + * next_token + * + * Find next token in a string. The token (word) is delimited by a statically + * defined and a game specific set of word separators. The game specific set + * of separators look like real word separators, but the parser wants to know + * about them. An example would be: 'grue, take the axe. go north'. The + * parser wants to know about the comma and the period so that it can correctly + * parse the line. The 'interesting' word separators normally appear at the + * start of the dictionary, and are also put in a separate list in the game + * file. + * + */ + +static const char *next_token( const char *s, const char *str_end, const char **token, int *length, + const char *punctuation ) +{ + int i; + + /* Set the token length to zero */ + + *length = 0; + + /* Step through the string looking for separators */ + + for ( ; s < str_end; s++ ) + { + + /* Look for game specific word separators first */ + + for ( i = 0; punctuation[i] && *s != punctuation[i]; i++ ) + ; + + /* If a separator is found then return the information */ + + if ( punctuation[i] ) + { + + /* If length has been set then just return the word position */ + + if ( *length ) + return ( s ); + else + { + + /* End of word, so set length, token pointer and return string */ + + ( *length )++; + *token = s; + return ( ++s ); + } + } + + /* Look for statically defined separators last */ + + for ( i = 0; separators[i] && *s != separators[i]; i++ ) + ; + + /* If a separator is found then return the information */ + + if ( separators[i] ) + { + + /* If length has been set then just return the word position */ + + if ( *length ) + return ( ++s ); + } + else + { + + /* If first token character then remember its position */ + + if ( *length == 0 ) + *token = s; + ( *length )++; + } + } + + return ( s ); + +} /* next_token */ + +/* + * find_word + * + * Search the dictionary for a word. Just encode the word and binary chop the + * dictionary looking for it. + * + */ + +static zword_t find_word( int len, const char *cp, long chop ) +{ + ZINT16 word[3]; + long word_index, offset, status; + + /* Don't look up the word if there are no dictionary entries */ + + if ( dictionary_size == 0 ) + return ( 0 ); + + /* Encode target word */ + + encode_text( len, cp, word ); + + /* Do a binary chop search on the main dictionary, otherwise do + * a linear search */ + + word_index = chop - 1; + + if ( dictionary_size > 0 ) + { + + /* Binary chop until the word is found */ + + while ( chop ) + { + + chop /= 2; + + /* Calculate dictionary offset */ + + if ( word_index > ( dictionary_size - 1 ) ) + word_index = dictionary_size - 1; + + offset = dictionary_offset + ( word_index * entry_size ); + + /* If word matches then return dictionary offset */ + + if ( ( status = word[0] - ( ZINT16 ) get_word( offset + 0 ) ) == 0 && + ( status = word[1] - ( ZINT16 ) get_word( offset + 2 ) ) == 0 && + ( h_type < V4 || ( status = word[2] - ( ZINT16 ) get_word( offset + 4 ) ) == 0 ) ) + return ( ( zword_t ) offset ); + + /* Set next position depending on direction of overshoot */ + + if ( status > 0 ) + { + word_index += chop; + + /* Deal with end of dictionary case */ + + if ( word_index >= ( int ) dictionary_size ) + word_index = dictionary_size - 1; + } + else + { + word_index -= chop; + + /* Deal with start of dictionary case */ + + if ( word_index < 0 ) + word_index = 0; + } + } + } + else + { + + for ( word_index = 0; word_index < -dictionary_size; word_index++ ) + { + + /* Calculate dictionary offset */ + + offset = dictionary_offset + ( word_index * entry_size ); + + /* If word matches then return dictionary offset */ + + if ( ( status = word[0] - ( ZINT16 ) get_word( offset + 0 ) ) == 0 && + ( status = word[1] - ( ZINT16 ) get_word( offset + 2 ) ) == 0 && + ( h_type < V4 || ( status = word[2] - ( ZINT16 ) get_word( offset + 4 ) ) == 0 ) ) + return ( ( zword_t ) offset ); + } + } + + return ( 0 ); + +} /* find_word */ + +/* + * z_tokenise + * + * argv[0] = character buffer address + * argv[1] = token buffer address + * argv[2] = alternate vocabulary table + * argv[3] = ignore unknown words flag + * + */ + +void z_tokenise( int argc, zword_t * argv ) +{ + + /* Supply default parameters */ + + if ( argc < 4 ) + argv[3] = 0; + if ( argc < 3 ) + argv[2] = h_words_offset; + + /* Convert the line to tokens */ + + tokenise_line( argv[0], argv[1], argv[2], argv[3] ); + +} /* z_tokenise */ diff --git a/interpre.c b/interpre.c new file mode 100644 index 0000000..5fa77f4 --- /dev/null +++ b/interpre.c @@ -0,0 +1,483 @@ + +/* $Id: interpre.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: interpre.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: interpre.c,v $ + * Revision 1.3 2000/07/05 15:20:34 jholder + * Updated code to remove warnings. + * + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * interpre.c + * + * Main interpreter loop + * + */ + +#include "ztypes.h" + +/*#define DEBUG_TERPRE*/ + +static int halt = FALSE; + +/* + * interpret + * + * Interpret Z code + * + */ + +int interpret( void ) +{ + zbyte_t opcode; + zword_t specifier; + zword_t operand[8]; + int maxoperands; + int count; + int extend; + int i; + + interpreter_status = 1; + + /* Loop until HALT instruction executed */ + + for ( interpreter_state = RUN; interpreter_state == RUN && halt == FALSE; ) + { + + /* Load opcode and set operand count */ + + + opcode = read_code_byte( ); + if ( h_type > V4 && opcode == 0xbe ) + { + opcode = read_code_byte( ); + extend = TRUE; + } + else + extend = FALSE; + count = 0; + + /* Multiple operand instructions */ + + if ( ( opcode < 0x80 || opcode > 0xc0 ) || extend == TRUE ) + { + + /* Two operand class, load both operands */ + + if ( opcode < 0x80 && extend == FALSE ) + { + operand[count++] = load_operand( ( opcode & 0x40 ) ? 2 : 1 ); + operand[count++] = load_operand( ( opcode & 0x20 ) ? 2 : 1 ); + opcode &= 0x1f; + } + else + { + + /* Variable operand class, load operand specifier */ + + opcode &= 0x3f; + if ( opcode == 0x2c || opcode == 0x3a ) + { /* Extended CALL instruction */ + specifier = read_code_word( ); + maxoperands = 8; + } + else + { + specifier = read_code_byte( ); + maxoperands = 4; + } + + /* Load operands */ + + for ( i = ( maxoperands - 1 ) * 2; i >= 0; i -= 2 ) + if ( ( ( specifier >> i ) & 0x03 ) != 3 ) + operand[count++] = load_operand( ( specifier >> i ) & 0x03 ); + else + i = 0; + } + + if ( extend == TRUE ) + { +#ifdef DEBUG_TERPRE + fprintf( stderr, "PC = 0x%08lx Op%s = 0x%02x %d, %d, %d\n", pc, "(EX)", opcode, + operand[0], operand[1], operand[2] ); +#endif + switch ( ( char ) opcode ) + { + + /* Extended operand instructions */ + + case 0x00: + z_save( count, operand[0], operand[1], operand[2] ); + break; + case 0x01: + z_restore( count, operand[0], operand[1], operand[2] ); + break; + case 0x02: + z_log_shift( operand[0], operand[1] ); + break; + case 0x03: + z_art_shift( operand[0], operand[1] ); + break; + case 0x04: + z_set_font( operand[0] ); + break; + + case 0x09: + z_save_undo( ); + break; + case 0x0a: + z_restore_undo( ); + break; + + default: + fatal( "interpret(): Illegal extended operand instruction" ); + } + } + else + { +#ifdef DEBUG_TERPRE + fprintf( stderr, "PC = 0x%08lx Op%s = 0x%02x %d, %d, %d\n", pc, "(2+)", opcode, + operand[0], operand[1], operand[2] ); +#endif + switch ( ( char ) opcode ) + { + + /* Two or multiple operand instructions */ + case 0x01: + z_je( count, operand ); + break; + case 0x02: + z_jl( operand[0], operand[1] ); + break; + case 0x03: + z_jg( operand[0], operand[1] ); + break; + case 0x04: + z_dec_chk( operand[0], operand[1] ); + break; + case 0x05: + z_inc_chk( operand[0], operand[1] ); + break; + case 0x06: + z_jin( operand[0], operand[1] ); + break; + case 0x07: + z_test( operand[0], operand[1] ); + break; + case 0x08: + z_or( operand[0], operand[1] ); + break; + case 0x09: + z_and( operand[0], operand[1] ); + break; + case 0x0a: + z_test_attr( operand[0], operand[1] ); + break; + case 0x0b: + z_set_attr( operand[0], operand[1] ); + break; + case 0x0c: + z_clear_attr( operand[0], operand[1] ); + break; + case 0x0d: + z_store( operand[0], operand[1] ); + break; + case 0x0e: + z_insert_obj( operand[0], operand[1] ); + break; + case 0x0f: + z_loadw( operand[0], operand[1] ); + break; + case 0x10: + z_loadb( operand[0], operand[1] ); + break; + case 0x11: + z_get_prop( operand[0], operand[1] ); + break; + case 0x12: + z_get_prop_addr( operand[0], operand[1] ); + break; + case 0x13: + z_get_next_prop( operand[0], operand[1] ); + break; + case 0x14: + z_add( operand[0], operand[1] ); + break; + case 0x15: + z_sub( operand[0], operand[1] ); + break; + case 0x16: + z_mul( operand[0], operand[1] ); + break; + case 0x17: + z_div( operand[0], operand[1] ); + break; + case 0x18: + z_mod( operand[0], operand[1] ); + break; + case 0x19: + z_call( count, operand, FUNCTION ); + break; + case 0x1a: + z_call( count, operand, PROCEDURE ); + break; + case 0x1b: + z_set_colour( operand[0], operand[1] ); + break; + case 0x1c: + z_throw( operand[0], operand[1] ); + break; + + /* Multiple operand instructions */ + + case 0x20: + z_call( count, operand, FUNCTION ); + break; + case 0x21: + z_storew( operand[0], operand[1], operand[2] ); + break; + case 0x22: + z_storeb( operand[0], operand[1], operand[2] ); + break; + case 0x23: + z_put_prop( operand[0], operand[1], operand[2] ); + break; + case 0x24: + z_sread_aread( count, operand ); + break; + case 0x25: + z_print_char( operand[0] ); + break; + case 0x26: + z_print_num( operand[0] ); + break; + case 0x27: + z_random( operand[0] ); + break; + case 0x28: + z_push( operand[0] ); + break; + case 0x29: + z_pull( operand[0] ); + break; + case 0x2a: + z_split_window( operand[0] ); + break; + case 0x2b: + z_set_window( operand[0] ); + break; + case 0x2c: + z_call( count, operand, FUNCTION ); + break; + case 0x2d: + z_erase_window( operand[0] ); + break; + case 0x2e: + z_erase_line( operand[0] ); + break; + case 0x2f: + z_set_cursor( operand[0], operand[1] ); + break; + + case 0x31: + z_set_text_style( operand[0] ); + break; + case 0x32: + z_buffer_mode( operand[0] ); + break; + case 0x33: + z_output_stream( operand[0], operand[1] ); + break; + case 0x34: + z_input_stream( operand[0] ); + break; + case 0x35: + sound( count, operand ); + break; + case 0x36: + z_read_char( count, operand ); + break; + case 0x37: + z_scan_table( count, operand ); + break; + case 0x38: + z_not( operand[0] ); + break; + case 0x39: + z_call( count, operand, PROCEDURE ); + break; + case 0x3a: + z_call( count, operand, PROCEDURE ); + break; + case 0x3b: + z_tokenise( count, operand ); + break; + case 0x3c: + z_encode( operand[0], operand[1], operand[2], operand[3] ); + break; + case 0x3d: + z_copy_table( operand[0], operand[1], operand[2] ); + break; + case 0x3e: + z_print_table( count, operand ); + break; + case 0x3f: + z_check_arg_count( operand[0] ); + break; + + default: + fatal( "interpret(): Illegal 2 or more operand instruction" ); + } + } + } + else + { + + /* Single operand class, load operand and execute instruction */ + + if ( opcode < 0xb0 ) + { + operand[0] = load_operand( ( opcode >> 4 ) & 0x03 ); +#ifdef DEBUG_TERPRE + fprintf( stderr, "PC = 0x%08lx Op%s = 0x%02x %d\n", pc, "(1 )", opcode, + operand[0] ); +#endif + switch ( ( char ) opcode & 0x0f ) + { + case 0x00: + z_jz( operand[0] ); + break; + case 0x01: + z_get_sibling( operand[0] ); + break; + case 0x02: + z_get_child( operand[0] ); + break; + case 0x03: + z_get_parent( operand[0] ); + break; + case 0x04: + z_get_prop_len( operand[0] ); + break; + case 0x05: + z_inc( operand[0] ); + break; + case 0x06: + z_dec( operand[0] ); + break; + case 0x07: + z_print_addr( operand[0] ); + break; + case 0x08: + z_call( 1, operand, FUNCTION ); + break; + case 0x09: + z_remove_obj( operand[0] ); + break; + case 0x0a: + z_print_obj( operand[0] ); + break; + case 0x0b: + z_ret( operand[0] ); + break; + case 0x0c: + z_jump( operand[0] ); + break; + case 0x0d: + z_print_paddr( operand[0] ); + break; + case 0x0e: + z_load( operand[0] ); + break; + case 0x0f: + if ( h_type > V4 ) + z_call( 1, operand, PROCEDURE ); + else + z_not( operand[0] ); + break; + } + } + else + { + + /* Zero operand class, execute instruction */ +#ifdef DEBUG_TERPRE + fprintf( stderr, "PC = 0x%08lx Op%s = 0x%02x\n", pc, "(0 )", opcode ); +#endif + switch ( ( char ) opcode & 0x0f ) + { + case 0x00: + z_ret( TRUE ); + break; + case 0x01: + z_ret( FALSE ); + break; + case 0x02: + z_print( ); + break; + case 0x03: + z_print_ret( ); + break; + case 0x04: + /* z_nop */ + break; + case 0x05: + z_save( 0, 0, 0, 0 ); + break; + case 0x06: + z_restore( 0, 0, 0, 0 ); + break; + case 0x07: + z_restart( ); + break; + case 0x08: + z_ret( stack[sp++] ); + break; + case 0x09: + z_catch( ); + break; + case 0x0a: + halt = TRUE; /* z_quit */ + break; + case 0x0b: + z_new_line( ); + break; + case 0x0c: + z_show_status( ); + break; + case 0x0d: + z_verify( ); + break; + + case 0x0f: + z_piracy( TRUE ); + break; + + default: + fatal( "interpret(): Illegal zero operand instruction" ); + } + } + } + } + + return ( interpreter_status ); + +} /* interpret */ diff --git a/joeyio.c b/joeyio.c new file mode 100644 index 0000000..40219ce --- /dev/null +++ b/joeyio.c @@ -0,0 +1,223 @@ +#include "joey.h" +#include "ansiterm.h" +#include "ztypes.h" + + +static int head_col; + + +void clear_line(void) { + termPrint("\33[K"); +} + + +void clear_screen(void) { + termClearScreen(); + jlDisplayPresent(); + termMoveCursor(1, 1); +} + + +void clear_status_window(void) { + int i; + int row; + int col; + get_cursor_position(&row, &col); + for (i=status_size; i; i--) { + move_cursor(i, 1); + clear_line(); + } + move_cursor(row, col); +} + + +void clear_text_window(void) { + int i; + int row; + int col; + get_cursor_position(&row, &col); + for (i=status_size + 1; i<=screen_rows; i++) { + move_cursor(i, 1); + clear_line(); + } + move_cursor(row, col); +} + + +void create_status_window(void) { + printf("Create Status Window\n"); +} + + +void delete_status_window(void) { + printf("Delete Status Window\n"); +} + + +void display_char(int c) { + termPrintChar((char)c); +} + + +void get_cursor_position(int *row, int *col) { + termGetCursor(col, row); +} + + +void initialize_screen(void) { + // Handled in main +} + + +int input_character(int timeout) { + char k = 0; + (void)timeout; // In 1/10ths of a second + while (!jlKeyPressed() && !jlUtilMustExit()) { + //***TODO*** Animate cursor here? + } + if (jlKeyPressed()) { + while (jlKeyPressed()) { + // Wait for key to be released + } + k = jlKeyRead(); + } else { + interpreter_state = STOP; + } + return (int)k; +} + + +int input_line(int buflen, char *buffer, int timeout, int *read_size) { + int c = -1; + int col; + int row; + int init_char_pos; + int curr_char_pos; + int loop; + int tail_col; + + (void)timeout; // In 1/10ths of a second + + get_cursor_position(&row, &col); + head_col = tail_col = col; + init_char_pos = curr_char_pos = *read_size; + + while (!jlUtilMustExit()) { + c = input_character(0); + + if (c == 8) { + // Backspace + get_cursor_position(&row, &col); + if (col > head_col) { + move_cursor(row, --col); + for (loop = curr_char_pos; loop < *read_size; loop++) { + buffer[loop - 1] = buffer[loop]; + display_char(buffer[loop - 1]); + } + display_char(' '); + curr_char_pos--; + tail_col--; + (*read_size)--; + move_cursor(row, col); + } + } else { + // Normal key + if (*read_size == ( buflen - 1 )) { + //***TODO*** Buffer full - Flash border? + } else { + if (c == 13) { + // CR + scroll_line(); + return c; + } else { + // Used if the cursor is not at the end of the line + if (col < tail_col) { + // Moves the input line one character to the right + for (loop = *read_size; loop >= curr_char_pos; loop--) { + buffer[loop + 1] = buffer[loop]; + } + + // Puts the character into the space created by the "for" loop above + buffer[curr_char_pos] = (char)c; + + // Increment the end of the line values + (*read_size)++; + tail_col++; + + // Move the cursor back to its original position + move_cursor(row, col); + + // Redisplays the input line from the point of insertion + for (loop = curr_char_pos; loop < *read_size; loop++) { + display_char(buffer[loop]); + } + + // Moves the cursor to the next position + move_cursor(row, ++col); + curr_char_pos++; + } else { + // Used if the cursor is at the end of the line + buffer[curr_char_pos++] = (char)c; + display_char(c); + (*read_size)++; + tail_col++; + } + } + } + } + } + + return c; +} + + +void move_cursor(int row, int col) { + termMoveCursor(col, row); +} + + +void reset_screen(void) { + // Handled in main +} + + +void restart_screen(void) { + // I don't think we need this. +} + + +void restore_cursor_position(void) { + termRestoreCursor(); +} + + +void save_cursor_position(void) { + termSaveCursor(); +} + + +void scroll_line(void) { + //***TODO*** Should really not clobber the status window. + termPrintChar(13); + termPrintChar(10); +} + + +void select_status_window(void) { + save_cursor_position(); +} + + +void select_text_window(void) { + restore_cursor_position(); +} + + +void set_attribute(int attribute) { + if (attribute == NORMAL) { + termPrint("\33[0m"); + } + if (attribute & REVERSE) { + termPrint("\33[7m"); + } +} diff --git a/jzip.h b/jzip.h new file mode 100644 index 0000000..ba1497b --- /dev/null +++ b/jzip.h @@ -0,0 +1,37 @@ + +/* $Id: jzip.h,v 1.5 2000/10/10 14:46:22 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: jzip.h,v 1.5 2000/10/10 14:46:22 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: jzip.h,v $ + * Revision 1.5 2000/10/10 14:46:22 jholder + * Fixed text wrap bug when printing array w/ \r chars in it + * + * Revision 1.4 2000/10/05 17:09:12 jholder + * removed old email address + * + * Revision 1.3 2000/10/04 23:07:57 jholder + * fixed redirect problem with isolatin1 range chars + * + * Revision 1.2 2000/09/18 15:31:44 jholder + * Updated release date for package release + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +#define JZIPVER "Jzip V2.1" +#define JZIPRELDATE "Tue, Oct 10 2000" +#define JZIPAUTHOR "John Holder (j-holder@home.com)" +#define JZIPURL "http://jzip.sourceforge.net/" + diff --git a/main.c b/main.c new file mode 100644 index 0000000..a78b30f --- /dev/null +++ b/main.c @@ -0,0 +1,122 @@ +#include +#include + + +#include "joey.h" +#include "ansiterm.h" +#include "ztypes.h" + + +extern int GLOBALVER; + + +void configure(zbyte_t min_version, zbyte_t max_version); +void process_arguments(char *game); + + +void configure(zbyte_t min_version, zbyte_t max_version) { + zbyte_t header[PAGE_SIZE]; + + read_page(0, header); + datap = header; + + h_type = get_byte(H_TYPE); + GLOBALVER = h_type; + + if (h_type < min_version || h_type > max_version || (get_byte( H_CONFIG ) & CONFIG_BYTE_SWAPPED)) { + fatal( "Wrong game or version" ); + } + + if (h_type < V4) { + story_scaler = 2; + story_shift = 1; + property_mask = P3_MAX_PROPERTIES - 1; + property_size_mask = 0xe0; + } else if (h_type < V8) { + story_scaler = 4; + story_shift = 2; + property_mask = P4_MAX_PROPERTIES - 1; + property_size_mask = 0x3f; + } else { + story_scaler = 8; + story_shift = 3; + property_mask = P4_MAX_PROPERTIES - 1; + property_size_mask = 0x3f; + } + + h_config = get_byte(H_CONFIG); + h_version = get_word(H_VERSION); + h_data_size = get_word(H_DATA_SIZE); + h_start_pc = get_word(H_START_PC); + h_words_offset = get_word(H_WORDS_OFFSET); + h_objects_offset = get_word(H_OBJECTS_OFFSET); + h_globals_offset = get_word(H_GLOBALS_OFFSET); + h_restart_size = get_word(H_RESTART_SIZE); + h_flags = get_word(H_FLAGS); + h_synonyms_offset = get_word(H_SYNONYMS_OFFSET); + h_file_size = get_word(H_FILE_SIZE); + if (h_file_size == 0) { + h_file_size = (zword_t)get_story_size(); + } + h_checksum = get_word(H_CHECKSUM); + h_alternate_alphabet_offset = get_word(H_ALTERNATE_ALPHABET_OFFSET); + + if (h_type >= V5) { + h_unicode_table = get_word(H_UNICODE_TABLE); + } + datap = NULL; +} + + +void process_arguments(char *game) { + int size; + + monochrome = 0; + hist_buf_size = 1024; + bigscreen = 0; + screen_rows = 25; + screen_cols = 40; + right_margin = 0; + top_margin = 0; + size = 1024; + hist_buf_size = (hist_buf_size > size) ? hist_buf_size : size; + if (hist_buf_size > 16384) { + hist_buf_size = 16384; + } + fTandy = 0; + //default_fg = atoi( optarg ); + //default_bg = atoi( optarg ); + monochrome = 1; + + open_story(game); +} + + +int main(void) { + + jlStaT *font = NULL; + + jlUtilStartup("IF Engine"); + jlStaLoad(font, "8x8thin.sta"); + + termStart(font, 0, 0, 40, 25); + + process_arguments("gamedata.z5"); + configure(V1, V8); + initialize_screen(); + load_cache(); + z_restart(); + (void)interpret(); + unload_cache(); + close_story(); + close_script(); + reset_screen(); + + termStop(); + + jlDisplayPresent(); + jlKeyWaitForAny(); + jlStaFree(font); + + jlUtilShutdown(); +} diff --git a/math.c b/math.c new file mode 100644 index 0000000..df72b77 --- /dev/null +++ b/math.c @@ -0,0 +1,305 @@ + +/* $Id: math.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: math.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: math.c,v $ + * Revision 1.3 2000/07/05 15:20:34 jholder + * Updated code to remove warnings. + * + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * math.c + * + * Arithmetic, compare and logical instructions + * + */ + +#include "ztypes.h" + +/* + * z_add + * + * Add two operands + * + */ + +void z_add( zword_t a, zword_t b ) +{ + store_operand( (zword_t)(a + b) ); +} + +/* + * z_sub + * + * Subtract two operands + * + */ + +void z_sub( zword_t a, zword_t b ) +{ + store_operand( (zword_t)(a - b) ); +} + +/* + * mul + * + * Multiply two operands + * + */ + +void z_mul( zword_t a, zword_t b ) +{ + store_operand( (zword_t)(( ZINT16 ) a * ( ZINT16 ) b) ); +} + +/* + * z_div + * + * Divide two operands + * + */ + +void z_div( zword_t a, zword_t b ) +{ + /* The magic rule is: round towards zero. */ + ZINT16 sa = ( ZINT16 ) a; + ZINT16 sb = ( ZINT16 ) b; + ZINT16 res; + + if ( sb < 0 ) + { + sa = -sa; + sb = -sb; + } + if ( sb == 0 ) + { +#ifdef STRICTZ + report_strictz_error( STRZERR_DIV_ZERO, + "@div attempted with a divisor of zero. Result set to 32767 (0x7fff)." ); +#endif + res = 32767; + } + else if ( sa >= 0 ) + { + res = sa / sb; + } + else + { + res = -( ( -sa ) / ( sb ) ); + } + store_operand( res ); + +} + +/* + * z_mod + * + * Modulus divide two operands + * + */ + +void z_mod( zword_t a, zword_t b ) +{ + /* The magic rule is: be consistent with divide, because + * (a/b)*a + (a%b) == a. So (a%b) has the same sign as a. */ + ZINT16 sa = ( ZINT16 ) a; + ZINT16 sb = ( ZINT16 ) b; + ZINT16 res; + + if ( sb < 0 ) + { + sb = -sb; + } + if ( sb == 0 ) + { +#ifdef STRICTZ + report_strictz_error( STRZERR_DIV_ZERO, + "@mod attempted with a divisor of zero. Result set to 0." ); +#endif + res = 0; + } + if ( sa >= 0 ) + { + res = sa % sb; + } + else + { + res = -( ( -sa ) % ( sb ) ); + } + store_operand( res ); + +} + +/* + * z_log_shift + * + * Shift +/- n bits + * + */ + +void z_log_shift( zword_t a, zword_t b ) +{ + + if ( ( ZINT16 ) b > 0 ) + store_operand( (zword_t)( a << ( ZINT16 ) b ) ); + else + store_operand( (zword_t)( a >> -( ( ZINT16 ) b ) ) ); + +} + + +/* + * z_art_shift + * + * Aritmetic shift +/- n bits + * + */ + +void z_art_shift( zword_t a, zword_t b ) +{ + if ( ( ZINT16 ) b > 0 ) + store_operand( ( zword_t ) ( ( ZINT16 ) a << ( ZINT16 ) b ) ); + else + store_operand( ( zword_t ) ( ( ZINT16 ) a >> -( ( ZINT16 ) b ) ) ); +} + +/* + * z_or + * + * Logical OR + * + */ + +void z_or( zword_t a, zword_t b ) +{ + store_operand( (zword_t)(a | b) ); +} + +/* + * z_not + * + * Logical NOT + * + */ + +void z_not( zword_t a ) +{ + store_operand( (zword_t)(~a) ); +} + +/* + * z_and + * + * Logical AND + * + */ + +void z_and( zword_t a, zword_t b ) +{ + store_operand( (zword_t)(a & b) ); +} + +/* + * z_random + * + * Return random number between 1 and operand + * + */ + +void z_random( zword_t a ) +{ + if ( a == 0 ) + store_operand( 0 ); + else if ( a & 0x8000 ) + { /* (a < 0) - used to set seed with #RANDOM */ + SRANDOM_FUNC( ( unsigned int ) abs( a ) ); + store_operand( 0 ); + } + else /* (a > 0) */ + store_operand( (zword_t)(( RANDOM_FUNC( ) % a ) + 1) ); +} + +/* + * z_test + * + * Jump if operand 2 bit mask not set in operand 1 + * + */ + +void z_test( zword_t a, zword_t b ) +{ + conditional_jump( ( ( ~a ) & b ) == 0 ); +} + +/* + * z_jz + * + * Compare operand against zero + * + */ + +void z_jz( zword_t a ) +{ + conditional_jump( a == 0 ); +} + +/* + * z_je + * + * Jump if operand 1 is equal to any other operand + * + */ + +void z_je( int count, zword_t * operand ) +{ + int i; + + for ( i = 1; i < count; i++ ) + if ( operand[0] == operand[i] ) + { + conditional_jump( TRUE ); + return; + } + conditional_jump( FALSE ); +} + +/* + * z_jl + * + * Jump if operand 1 is less than operand 2 + * + */ + +void z_jl( zword_t a, zword_t b ) +{ + conditional_jump( ( ZINT16 ) a < ( ZINT16 ) b ); +} + +/* + * z_jg + * + * Jump if operand 1 is greater than operand 2 + * + */ + +void z_jg( zword_t a, zword_t b ) +{ + conditional_jump( ( ZINT16 ) a > ( ZINT16 ) b ); +} diff --git a/memory.c b/memory.c new file mode 100644 index 0000000..3dde03f --- /dev/null +++ b/memory.c @@ -0,0 +1,419 @@ + +/* $Id: memory.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: memory.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: memory.c,v $ + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * memory.c + * + * Code and data caching routines + * + */ + +#include "ztypes.h" + +/* A cache entry */ + +typedef struct cache_entry +{ + struct cache_entry *flink; + int page_number; + zbyte_t data[PAGE_SIZE]; +} +cache_entry_t; + +/* Cache chain anchor */ + +static cache_entry_t *cache = NULL; + +/* Pseudo translation buffer, one entry each for code and data pages */ + +static unsigned int current_code_page = 0; +static cache_entry_t *current_code_cachep = NULL; +static unsigned int current_data_page = 0; +static cache_entry_t *current_data_cachep = NULL; + +static unsigned int calc_data_pages( void ); +static cache_entry_t *update_cache( int ); + +/* + * load_cache + * + * Initialise the cache and any other dynamic memory objects. The memory + * required can be split into two areas. Firstly, three buffers are required for + * input, output and status line. Secondly, two data areas are required for + * writeable data and read only data. The writeable data is the first chunk of + * the file and is put into non-paged cache. The read only data is the remainder + * of the file which can be paged into the cache as required. Writeable data has + * to be memory resident because it cannot be written out to a backing store. + * + */ + +void load_cache( void ) +{ + unsigned long file_size; + unsigned int i, file_pages, data_pages; + cache_entry_t *cachep; + + /* Allocate output and status line buffers */ + + line = ( char * ) malloc( screen_cols + 1 ); + if ( line == NULL ) + { + fatal( "load_cache(): Insufficient memory to play game" ); + } + status_line = ( char * ) malloc( screen_cols + 1 ); + if ( status_line == NULL ) + { + fatal( "load_cache(): Insufficient memory to play game" ); + } + + /* Must have at least one cache page for memory calculation */ + cachep = ( cache_entry_t * ) malloc( sizeof ( cache_entry_t ) ); + + if ( cachep == NULL ) + { + fatal( "load_cache(): Insufficient memory to play game" ); + } + cachep->flink = cache; + cachep->page_number = 0; + cache = cachep; + + /* Calculate dynamic cache pages required */ + + if ( h_config & CONFIG_MAX_DATA ) + { + data_pages = calc_data_pages( ); + } + else + { + data_pages = ( h_data_size + PAGE_MASK ) >> PAGE_SHIFT; + } + data_size = data_pages * PAGE_SIZE; + file_size = ( unsigned long ) h_file_size *story_scaler; + + file_pages = ( unsigned int ) ( ( file_size + PAGE_MASK ) >> PAGE_SHIFT ); + + /* Allocate static data area and initialise it */ + + datap = ( zbyte_t * ) malloc( data_size ); + if ( datap == NULL ) + { + fatal( "load_cache(): Insufficient memory to play game" ); + } + for ( i = 0; i < data_pages; i++ ) + { + read_page( i, &datap[i * PAGE_SIZE] ); + } + + /* Allocate memory for undo */ + + undo_datap = ( zbyte_t * ) malloc( data_size ); + + /* Allocate cache pages and initialise them */ + + for ( i = data_pages; cachep != NULL && i < file_pages; i++ ) + { + cachep = ( cache_entry_t * ) malloc( sizeof ( cache_entry_t ) ); + + if ( cachep != NULL ) + { + cachep->flink = cache; + cachep->page_number = i; + read_page( cachep->page_number, cachep->data ); + cache = cachep; + } + } + +} /* load_cache */ + +/* + * unload_cache + * + * Deallocate cache and other memory objects. + * + */ + +void unload_cache( void ) +{ + cache_entry_t *cachep, *nextp; + + /* Make sure all output has been flushed */ + + z_new_line( ); + + /* Free output buffer, status line and data memory */ + + free( line ); + free( status_line ); + free( datap ); + free( undo_datap ); + + /* Free cache memory */ + + for ( cachep = cache; cachep->flink != NULL; cachep = nextp ) + { + nextp = cachep->flink; + free( cachep ); + } + +} /* unload_cache */ + +/* + * read_code_word + * + * Read a word from the instruction stream. + * + */ + +zword_t read_code_word( void ) +{ + zword_t w; + + w = ( zword_t ) read_code_byte( ) << 8; + w |= ( zword_t ) read_code_byte( ); + + return ( w ); + +} /* read_code_word */ + +/* + * read_code_byte + * + * Read a byte from the instruction stream. + * + */ + +zbyte_t read_code_byte( void ) +{ + unsigned int page_number, page_offset; + + /* Calculate page and offset values */ + + page_number = ( unsigned int ) ( pc >> PAGE_SHIFT ); + page_offset = ( unsigned int ) pc & PAGE_MASK; + + /* Load page into translation buffer */ + + if ( page_number != current_code_page ) + { + current_code_cachep = update_cache( page_number ); + current_code_page = page_number; + } + + /* Update the PC */ + + pc++; + + /* Return byte from page offset */ + + if ( !current_code_cachep ) + { + fatal + ( "read_code_byte(): read from non-existant page!\n\t(Your dynamic memory usage _may_ be over 64k in size!)" ); + } + + return ( current_code_cachep->data[page_offset] ); + +} /* read_code_byte */ + +/* + * read_data_word + * + * Read a word from the data area. + * + */ + +zword_t read_data_word( unsigned long *addr ) +{ + zword_t w; + + w = ( zword_t ) read_data_byte( addr ) << 8; + w |= ( zword_t ) read_data_byte( addr ); + + return ( w ); + +} /* read_data_word */ + +/* + * read_data_byte + * + * Read a byte from the data area. + * + */ + +zbyte_t read_data_byte( unsigned long *addr ) +{ + unsigned int page_number, page_offset; + zbyte_t value; + + /* Check if byte is in non-paged cache */ + + if ( *addr < ( unsigned long ) data_size ) + { + value = datap[*addr]; + } + else + { + /* Calculate page and offset values */ + + page_number = ( int ) ( *addr >> PAGE_SHIFT ); + page_offset = ( int ) *addr & PAGE_MASK; + + /* Load page into translation buffer */ + + if ( page_number != current_data_page ) + { + current_data_cachep = update_cache( page_number ); + current_data_page = page_number; + } + + /* Fetch byte from page offset */ + + if ( current_data_cachep ) + { + value = current_data_cachep->data[page_offset]; + } + else + { + fatal( "read_data_byte(): Fetching data from invalid page!" ); + } + } + + /* Update the address */ + + ( *addr )++; + + return ( value ); + +} /* read_data_byte */ + +/* + * calc_data_pages + * + * Compute the best size for the data area cache. Some games have the data size + * header parameter set too low. This causes a write outside of data area on + * some games. To alleviate this problem the data area size is set to the + * maximum of the restart size, the data size and the end of the dictionary. An + * attempt is made to put the dictionary in the data area to stop paging during + * a dictionary lookup. Some games have the dictionary end very close to the + * 64K limit which may cause problems for machines that allocate memory in + * 64K chunks. + * + */ + +static unsigned int calc_data_pages( void ) +{ + unsigned long offset, data_end, dictionary_end; + int separator_count, word_size, word_count; + unsigned int data_pages; + + /* Calculate end of data area, use restart size if data size is too low */ + + if ( h_data_size > h_restart_size ) + { + data_end = h_data_size; + } + else + { + data_end = h_restart_size; + } + + /* Calculate end of dictionary table */ + + offset = h_words_offset; + separator_count = read_data_byte( &offset ); + offset += separator_count; + word_size = read_data_byte( &offset ); + word_count = read_data_word( &offset ); + dictionary_end = offset + ( word_size * word_count ); + + /* If data end is too low then use end of dictionary instead */ + + if ( dictionary_end > data_end ) + { + data_pages = ( unsigned int ) ( ( dictionary_end + PAGE_MASK ) >> PAGE_SHIFT ); + } + else + { + data_pages = ( unsigned int ) ( ( data_end + PAGE_MASK ) >> PAGE_SHIFT ); + } + + return ( data_pages ); + +} /* calc_data_pages */ + +/* + * update_cache + * + * Called on a code or data page cache miss to find the page in the cache or + * read the page in from disk. The chain is kept as a simple LRU chain. If a + * page cannot be found then the page on the end of the chain is reused. If the + * page is found, or reused, then it is moved to the front of the chain. + * + */ + +static cache_entry_t *update_cache( int page_number ) +{ + cache_entry_t *cachep, *lastp; + + /* Search the cache chain for the page */ + + for ( lastp = cache, cachep = cache; + cachep->flink != NULL && cachep->page_number && cachep->page_number != page_number; + lastp = cachep, cachep = cachep->flink ) + ; + + /* If no page in chain then read it from disk */ + + if ( cachep->page_number != page_number ) + { + /* Reusing last cache page, so invalidate cache if page was in use */ + if ( cachep->flink == NULL && cachep->page_number ) + { + if ( current_code_page == ( unsigned int ) cachep->page_number ) + { + current_code_page = 0; + } + if ( current_data_page == ( unsigned int ) cachep->page_number ) + { + current_data_page = 0; + } + } + + /* Load the new page number and the page contents from disk */ + + cachep->page_number = page_number; + read_page( page_number, cachep->data ); + } + + /* If page is not at front of cache chain then move it there */ + + if ( lastp != cache ) + { + lastp->flink = cachep->flink; + cachep->flink = cache; + cache = cachep; + } + + return ( cachep ); + +} /* update_cache */ diff --git a/object.c b/object.c new file mode 100644 index 0000000..68550f7 --- /dev/null +++ b/object.c @@ -0,0 +1,454 @@ + +/* $Id: object.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: object.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: object.c,v $ + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * object.c + * + * Object manipulation routines. + * + */ + +#include "ztypes.h" + +#define PARENT 0 +#define NEXT 1 +#define CHILD 2 + +static zword_t read_object( zword_t objp, int field ); +static void write_object( zword_t objp, int field, zword_t value ); + +/* + * get_object_address + * + * Calculate the address of an object in the data area. + * + */ + +zword_t get_object_address( zword_t obj ) +{ + int offset; + + /* Address calculation is object table base + size of default properties area + + * object number-1 * object size */ + + if ( h_type < V4 ) + offset = h_objects_offset + ( ( P3_MAX_PROPERTIES - 1 ) * 2 ) + ( ( obj - 1 ) * O3_SIZE ); + else + offset = h_objects_offset + ( ( P4_MAX_PROPERTIES - 1 ) * 2 ) + ( ( obj - 1 ) * O4_SIZE ); + + return ( ( zword_t ) offset ); + +} /* get_object_address */ + +/* + * z_insert_obj + * + * Insert object 1 as the child of object 2 after first removing it from its + * previous parent. The object is inserted at the front of the child object + * chain. + * + */ + +void z_insert_obj( zword_t obj1, zword_t obj2 ) +{ + zword_t obj1p, obj2p, child2; + +#ifdef STRICTZ + if ( obj1 == 0 ) + { + report_strictz_error( STRZERR_MOVE_OBJECT, "@insert_obj called moving object 0" ); + } + if ( obj2 == 0 ) + { + report_strictz_error( STRZERR_MOVE_OBJECT_2, "@insert_obj called moving into object 0" ); + } +#endif + + + /* Get addresses of both objects */ + + obj1p = get_object_address( obj1 ); + obj2p = get_object_address( obj2 ); + + /* Remove object 1 from current parent */ + + z_remove_obj( obj1 ); + + /* Make object 2 object 1's parent */ + + write_object( obj1p, PARENT, obj2 ); + + /* Get current first child of object 2 */ + + child2 = read_object( obj2p, CHILD ); + + /* Make object 1 first child of object 2 */ + + write_object( obj2p, CHILD, obj1 ); + + /* If object 2 had children then link them into the next child field of object 1 */ + + if ( child2 ) + write_object( obj1p, NEXT, child2 ); + +} /* z_insert_obj */ + +/* + * z_remove_obj + * + * Remove an object by unlinking from the its parent object and from its + * siblings. + * + */ + +void z_remove_obj( zword_t obj ) +{ + zword_t objp, parentp, childp, parent, child; + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_REMOVE_OBJECT, "@remove_obj called with object 0" ); + } +#endif + + /* Get address of object to be removed */ + + objp = get_object_address( obj ); + + /* Get parent of object, and return if no parent */ + + if ( ( parent = read_object( objp, PARENT ) ) == 0 ) + return; + + /* Get address of parent object */ + + parentp = get_object_address( parent ); + + /* Find first child of parent */ + + child = read_object( parentp, CHILD ); + + /* If object is first child then just make the parent child pointer + * equal to the next child */ + + if ( child == obj ) + write_object( parentp, CHILD, read_object( objp, NEXT ) ); + else + { + + /* Walk down the child chain looking for this object */ + + do + { + childp = get_object_address( child ); + child = read_object( childp, NEXT ); + } + while ( child != obj ); + + /* Set the next pointer thre previous child to the next pointer + * of the current object child pointer */ + + write_object( childp, NEXT, read_object( objp, NEXT ) ); + } + + /* Set the parent and next child pointers to NULL */ + + write_object( objp, PARENT, 0 ); + write_object( objp, NEXT, 0 ); + +} /* z_remove_obj */ + +/* + * z_get_parent + * + * Load the parent object pointer of an object + * + */ + +void z_get_parent( zword_t obj ) +{ + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_GET_PARENT, "@get_parent called with object 0" ); + store_operand( 0 ); + return; + } +#endif + + store_operand( read_object( get_object_address( obj ), PARENT ) ); + +} /* z_get_parent */ + +/* + * z_get_child + * + * Load the child object pointer of an object and jump if the child pointer is + * not NULL. + * + */ + +void z_get_child( zword_t obj ) +{ + zword_t child; + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_GET_CHILD, "@get_child called with object 0" ); + store_operand( 0 ); + conditional_jump( FALSE ); + return; + } +#endif + + child = read_object( get_object_address( obj ), CHILD ); + + store_operand( child ); + + conditional_jump( child != 0 ); + +} /* z_get_child */ + +/* + * z_get_sibling + * + * Load the next child object pointer of an object and jump if the next child + * pointer is not NULL. + * + */ + +void z_get_sibling( zword_t obj ) +{ + zword_t next; + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_GET_SIBLING, "@get_sibling called with object 0" ); + store_operand( 0 ); + conditional_jump( FALSE ); + return; + } +#endif + + next = read_object( get_object_address( obj ), NEXT ); + + store_operand( next ); + + conditional_jump( next != 0 ); + +} /* z_get_sibling */ + +/* + * z_jin + * + * Jump if object 2 is the parent of object 1 + * + */ + +void z_jin( zword_t obj1, zword_t obj2 ) +{ + +#ifdef STRICTZ + if ( obj1 == 0 ) + { + report_strictz_error( STRZERR_JIN, "@jin called with object 0" ); + conditional_jump( 0 == obj2 ); + return; + } +#endif + + conditional_jump( read_object( get_object_address( obj1 ), PARENT ) == obj2 ); + +} /* z_jin */ + +/* + * z_test_attr + * + * Test if an attribute bit is set. + * + */ + +void z_test_attr( zword_t obj, zword_t bit ) +{ + zword_t objp; + zbyte_t value; + + assert( O3_ATTRIBUTES == O4_ATTRIBUTES ); + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_TEST_ATTR, "@test_attr called with object 0" ); + conditional_jump( FALSE ); + return; + } +#endif + + /* Get attribute address */ + + objp = get_object_address( obj ) + ( bit >> 3 ); + + /* Load attribute byte */ + + value = get_byte( objp ); + + /* Test attribute */ + + conditional_jump( ( value >> ( 7 - ( bit & 7 ) ) ) & 1 ); + +} /* z_test_attr */ + +/* + * z_set_attr + * + * Set an attribute bit. + * + */ + +void z_set_attr( zword_t obj, zword_t bit ) +{ + zword_t objp; + zbyte_t value; + + assert( O3_ATTRIBUTES == O4_ATTRIBUTES ); + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_SET_ATTR, "@set_attr called with object 0" ); + return; + } +#endif + + /* Get attribute address */ + + objp = get_object_address( obj ) + ( bit >> 3 ); + + /* Load attribute byte */ + + value = get_byte( objp ); + + /* Set attribute bit */ + + value |= ( zbyte_t ) ( 1 << ( 7 - ( bit & 7 ) ) ); + + /* Store attribute byte */ + + set_byte( objp, value ); + +} /* z_set_attr */ + +/* + * z_clear_attr + * + * Clear an attribute bit + * + */ + +void z_clear_attr( zword_t obj, zword_t bit ) +{ + zword_t objp; + zbyte_t value; + + assert( O3_ATTRIBUTES == O4_ATTRIBUTES ); + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_CLEAR_ATTR, "@clear_attr called with object 0" ); + return; + } +#endif + + /* Get attribute address */ + + objp = get_object_address( obj ) + ( bit >> 3 ); + + /* Load attribute byte */ + + value = get_byte( objp ); + + /* Clear attribute bit */ + + value &= ( zbyte_t ) ~ ( 1 << ( 7 - ( bit & 7 ) ) ); + + /* Store attribute byte */ + + set_byte( objp, value ); + +} /* z_clear_attr */ + +static zword_t read_object( zword_t objp, int field ) +{ + zword_t value; + + if ( h_type < V4 ) + { + if ( field == PARENT ) + value = ( zword_t ) get_byte( PARENT3( objp ) ); + else if ( field == NEXT ) + value = ( zword_t ) get_byte( NEXT3( objp ) ); + else + value = ( zword_t ) get_byte( CHILD3( objp ) ); + } + else + { + if ( field == PARENT ) + value = get_word( PARENT4( objp ) ); + else if ( field == NEXT ) + value = get_word( NEXT4( objp ) ); + else + value = get_word( CHILD4( objp ) ); + } + + return ( value ); + +} /* read_object */ + +static void write_object( zword_t objp, int field, zword_t value ) +{ + + if ( h_type < V4 ) + { + if ( field == PARENT ) + set_byte( PARENT3( objp ), value ); + else if ( field == NEXT ) + set_byte( NEXT3( objp ), value ); + else + set_byte( CHILD3( objp ), value ); + } + else + { + if ( field == PARENT ) + set_word( PARENT4( objp ), value ); + else if ( field == NEXT ) + set_word( NEXT4( objp ), value ); + else + set_word( CHILD4( objp ), value ); + } + +} /* write_object */ diff --git a/operand.c b/operand.c new file mode 100644 index 0000000..ef2eed5 --- /dev/null +++ b/operand.c @@ -0,0 +1,234 @@ + +/* $Id: operand.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: operand.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: operand.c,v $ + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * operand.c + * + * Operand manipulation routines + * + */ + +#include "ztypes.h" + +/* + * load_operand + * + * Load an operand, either: a variable, popped from the stack or a literal. + * + */ + +zword_t load_operand( int type ) +{ + zword_t operand; + + if ( type ) + { + + /* Type 1: byte literal, or type 2: operand specifier */ + + operand = ( zword_t ) read_code_byte( ); + if ( type == 2 ) + { + + /* If operand specifier non-zero then it's a variable, otherwise + * it's the top of the stack */ + + if ( operand ) + { + return load_variable( operand ); + } + else + { + return stack[sp++]; + } + } + } + else + { + /* Type 0: word literal */ + return read_code_word( ); + } + return ( operand ); + +} /* load_operand */ + +/* + * store_operand + * + * Store an operand, either as a variable pushed on the stack. + * + */ + +void store_operand( zword_t operand ) +{ + zbyte_t specifier; + + /* Read operand specifier byte */ + + specifier = read_code_byte( ); + + /* If operand specifier non-zero then it's a variable, otherwise it's the + * pushed on the stack */ + + if ( specifier ) + z_store( specifier, operand ); + else + stack[--sp] = operand; + +} /* store_operand */ + +/* + * load_variable + * + * Load a variable, either: a stack local variable, a global variable or + * the top of the stack. + * + */ + +zword_t load_variable( int number ) +{ + if ( number ) + { + if ( number < 16 ) + { + /* number in range 1 - 15, it's a stack local variable */ + return stack[fp - ( number - 1 )]; + } + else + { + /* number > 15, it's a global variable */ + return get_word( h_globals_offset + ( ( number - 16 ) * 2 ) ); + } + } + else + { + /* number = 0, get from top of stack */ + return stack[sp]; + } + +} /* load_variable */ + +/* + * z_store + * + * Store a variable, either: a stack local variable, a global variable or the + * top of the stack. + * + */ + +void z_store( int number, zword_t variable ) +{ + + if ( number ) + { + if ( number < 16 ) + + /* number in range 1 - 15, it's a stack local variable */ + + stack[fp - ( number - 1 )] = variable; + else + /* number > 15, it's a global variable */ + + set_word( h_globals_offset + ( ( number - 16 ) * 2 ), variable ); + } + else + /* number = 0, get from top of stack */ + + stack[sp] = variable; + +} /* z_store */ + +/* + * z_piracy + * + * Supposed to jump if the game thinks that it is pirated. + */ +void z_piracy( int flag ) +{ + conditional_jump( flag ); +} + +/* + * conditional_jump + * + * Take a jump after an instruction based on the flag, either true or false. The + * jump can be modified by the change logic flag. Normally jumps are taken + * when the flag is true. When the change logic flag is set then the jump is + * taken when flag is false. A PC relative jump can also be taken. This jump can + * either be a positive or negative byte or word range jump. An additional + * feature is the return option. If the jump offset is zero or one then that + * literal value is passed to the return instruction, instead of a jump being + * taken. Complicated or what! + * + */ + +void conditional_jump( int flag ) +{ + zbyte_t specifier; + zword_t offset; + + /* Read the specifier byte */ + + specifier = read_code_byte( ); + + /* If the reverse logic flag is set then reverse the flag */ + + if ( specifier & 0x80 ) + flag = ( flag ) ? 0 : 1; + + /* Jump offset is in bottom 6 bits */ + + offset = ( zword_t ) specifier & 0x3f; + + /* If the byte range jump flag is not set then load another offset byte */ + + if ( ( specifier & 0x40 ) == 0 ) + { + + /* Add extra offset byte to existing shifted offset */ + + offset = ( offset << 8 ) + read_code_byte( ); + + /* If top bit of offset is set then propogate the sign bit */ + + if ( offset & 0x2000 ) + offset |= 0xc000; + } + + /* If the flag is false then do the jump */ + + if ( flag == 0 ) + { + + /* If offset equals 0 or 1 return that value instead */ + + if ( offset == 0 || offset == 1 ) + { + z_ret( offset ); + } + else + { /* Add offset to PC */ + pc = ( unsigned long ) ( pc + ( ZINT16 ) offset - 2 ); + } + } +} /* conditional_jump */ diff --git a/osdepend.c b/osdepend.c new file mode 100644 index 0000000..0bd0b3f --- /dev/null +++ b/osdepend.c @@ -0,0 +1,525 @@ + +/* $Id: osdepend.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: osdepend.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: osdepend.c,v $ + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * osdepend.c + * + * All non screen specific operating system dependent routines. + * + * Olaf Barthel 28-Jul-1992 + * Modified John Holder(j-holder@home.com) 25-July-1995 + * Support for standalone storyfiles by Magnu Olsson (mol@df.lth.se) Nov.1995 + * + */ + +#include "ztypes.h" +//#include "jzexe.h" + +/* File names will be O/S dependent */ + +#if defined(AMIGA) +#define SAVE_NAME "Story.Save" /* Default save name */ +#define SCRIPT_NAME "PRT:" /* Default script name */ +#define RECORD_NAME "Story.Record" /* Default record name */ +#define AUXILARY_NAME "Story.Aux" /* Default auxilary name */ +#else /* defined(AMIGA) */ +#define SAVE_NAME "story.sav" /* Default save name */ +#define SCRIPT_NAME "story.scr" /* Default script name */ +#define RECORD_NAME "story.rec" /* Default record name */ +#define AUXILARY_NAME "story.aux" /* Default auxilary name */ +#endif /* defined(AMIGA) */ + + +#ifdef STRICTZ + +/* Define stuff for stricter Z-code error checking, for the generic + * Unix/DOS/etc terminal-window interface. Feel free to change the way + * player prefs are specified, or replace report_zstrict_error() + * completely if you want to change the way errors are reported. + */ + +/* 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; or treat all errors as fatal + * errors, killing the interpreter. I strongly recommend + * "report once" as the default. But you can compile in a + * different default by changing the definition of + * STRICTZ_DEFAULT_REPORT_MODE. In any case, the player can + * specify a report mode on the command line by typing "-s 0" + * through "-s 3". + */ + +#define STRICTZ_REPORT_NEVER (0) +#define STRICTZ_REPORT_ONCE (1) +#define STRICTZ_REPORT_ALWAYS (2) +#define STRICTZ_REPORT_FATAL (3) + +#define STRICTZ_DEFAULT_REPORT_MODE STRICTZ_REPORT_NEVER + +static int strictz_report_mode; +static int strictz_error_count[STRICTZ_NUM_ERRORS]; + +#endif /* STRICTZ */ + + +#if !defined(AMIGA) + +/* getopt linkages */ + +extern int optind; +extern const char *optarg; +extern ZINT16 default_fg, default_bg; + +#endif /* !defined(AMIGA) */ + + +#if defined OS2 || defined __MSDOS__ +int iPalette; +#endif + + +#if !defined(AMIGA) + +/* + * file_cleanup + * + * Perform actions when a file is successfully closed. Flag can be one of: + * GAME_SAVE, GAME_RESTORE, GAME_SCRIPT. + * + */ + +void file_cleanup( const char *file_name, int flag ) +{ + UNUSEDVAR( file_name ); + UNUSEDVAR( flag ); +} /* file_cleanup */ + +#endif /* !defined(AMIGA) */ + +#if !defined(AMIGA) + +/* + * sound + * + * Play a sound file or a note. + * + * argc = 1: argv[0] = note# (range 1 - 3) + * + * Play note. + * + * argc = 2: argv[0] = 0 + * argv[1] = 3 + * + * Stop playing current sound. + * + * argc = 2: argv[0] = 0 + * argv[1] = 4 + * + * Free allocated resources. + * + * argc = 3: argv[0] = ID# of sound file to replay. + * argv[1] = 2 + * argv[2] = Volume to replay sound with, this value + * can range between 1 and 8. + * + * argc = 4: argv[0] = ID# of sound file to replay. + * argv[1] = 2 + * argv[2] = Control information + * argv[3] = Volume information + * + * Volume information: + * + * 0x34FB -> Fade sound in + * 0x3507 -> Fade sound out + * other -> Replay sound at maximum volume + * + * Control information: + * + * This word is divided into two bytes, + * the upper byte determines the number of + * cycles to play the sound (e.g. how many + * times a clock chimes or a dog barks). + * The meaning of the lower byte is yet to + * be discovered :) + * + */ + +void sound( int argc, zword_t * argv ) +{ + + /* Supply default parameters */ + + if ( argc < 4 ) + argv[3] = 0; + if ( argc < 3 ) + argv[2] = 0xff; + if ( argc < 2 ) + argv[1] = 2; + + /* Generic bell sounder */ + + if ( argc == 1 || argv[1] == 2 ) + display_char( '\007' ); + +} /* sound */ + +#endif /* !defined(AMIGA) */ + +/* + * get_file_name + * + * Return the name of a file. Flag can be one of: + * GAME_SAVE - Save file (write only) + * GAME_RESTORE - Save file (read only) + * GAME_SCRIPT - Script file (write only) + * GAME_RECORD - Keystroke record file (write only) + * GAME_PLABACK - Keystroke record file (read only) + * GAME_SAVE_AUX - Auxilary (preferred settings) file (write only) + * GAME_LOAD_AUX - Auxilary (preferred settings) file (read only) + */ + +int get_file_name( char *file_name, char *default_name, int flag ) +{ + char buffer[127 + 2]; /* 127 is the biggest positive char */ + int status = 0; + + /* If no default file name then supply the standard name */ + + if ( default_name[0] == '\0' ) + { + if ( flag == GAME_SCRIPT ) + strcpy( default_name, SCRIPT_NAME ); + else if ( flag == GAME_RECORD || flag == GAME_PLAYBACK ) + strcpy( default_name, RECORD_NAME ); + else if ( flag == GAME_SAVE_AUX || GAME_LOAD_AUX ) + strcpy( default_name, AUXILARY_NAME ); + else /* (flag == GAME_SAVE || flag == GAME_RESTORE) */ + strcpy( default_name, SAVE_NAME ); + } + + /* Prompt for the file name */ + + output_line( "Enter a file name." ); + output_string( "(Default is \"" ); + output_string( default_name ); + output_string( "\"): " ); + + buffer[0] = 127; + buffer[1] = 0; + ( void ) get_line( buffer, 0, 0 ); + + /* Copy file name from the input buffer */ + + if ( h_type > V4 ) + { + unsigned char len = buffer[1]; + + memcpy( file_name, &buffer[2], len ); + file_name[len] = '\0'; + } + else + strcpy( file_name, &buffer[1] ); + + /* If nothing typed then use the default name */ + + if ( file_name[0] == '\0' ) + strcpy( file_name, default_name ); + +#if !defined(VMS) /* VMS has file version numbers, so cannot overwrite */ + + /* Check if we are going to overwrite the file */ + + if ( flag == GAME_SAVE || flag == GAME_SCRIPT || flag == GAME_RECORD || flag == GAME_SAVE_AUX ) + { + FILE *tfp; + +#if defined BUFFER_FILES + char tfpbuffer[BUFSIZ]; +#endif + char c; + + /* Try to access the file */ + + tfp = fopen( file_name, "r" ); + if ( tfp != NULL ) + { + /* If it succeeded then prompt to overwrite */ + +#if defined BUFFER_FILES + setbuf( tfp, tfpbuffer ); +#endif + + output_line( "You are about to write over an existing file." ); + output_string( "Proceed? (Y/N) " ); + + do + { + c = ( char ) input_character( 0 ); + c = ( char ) toupper( c ); + } + while ( c != 'Y' && c != 'N' ); + + output_char( c ); + output_new_line( ); + + /* If no overwrite then fail the routine */ + + if ( c == 'N' ) + status = 1; + + fclose( tfp ); + } + } +#endif /* !defined(VMS) */ + + /* Record the file name if it was OK */ + + if ( status == 0 ) + record_line( file_name ); + + return ( status ); + +} /* get_file_name */ + +#if !defined(AMIGA) + +/* + * fatal + * + * Display message and stop interpreter. + * + */ + +void fatal( const char *s ) +{ + + reset_screen( ); + fprintf( stderr, "\nFatal error: %s (PC = 0x%08lX)\n", s, pc ); +#ifdef DEBUG_TERPRE + fprintf( stdout, "\nFatal error: %s (PC = 0x%08lX)\n", s, pc ); +#endif + exit( 1 ); + +} /* fatal */ + +#endif /* !defined(AMIGA) */ + +/* + * report_strictz_error + * + * This handles Z-code error conditions which ought to be fatal errors, + * but which players might want to ignore for the sake of finishing the + * game. + * + * The error is provided as both a numeric code and a string. This allows + * us to print a warning the first time a particular error occurs, and + * ignore it thereafter. + * + * errnum : Numeric code for error (0 to STRICTZ_NUM_ERRORS-1) + * errstr : Text description of error + * + */ + +#ifdef STRICTZ + +void report_strictz_error( int errnum, const char *errstr ) +{ + int wasfirst; + char buf[256] = { 0 }; + + if ( errnum <= 0 || errnum >= STRICTZ_NUM_ERRORS ) + return; + + if ( strictz_report_mode == STRICTZ_REPORT_FATAL ) + { + sprintf( buf, "STRICTZ: %s (PC = %lx)", errstr, pc ); + fatal( buf ); + return; + } + + wasfirst = ( strictz_error_count[errnum] == 0 ); + strictz_error_count[errnum]++; + + if ( ( strictz_report_mode == STRICTZ_REPORT_ALWAYS ) || + ( strictz_report_mode == STRICTZ_REPORT_ONCE && wasfirst ) ) + { + sprintf( buf, "STRICTZ Warning: %s (PC = %lx)", errstr, pc ); + write_string( buf ); + + if ( strictz_report_mode == STRICTZ_REPORT_ONCE ) + { + write_string( " (will ignore further occurrences)" ); + } + else + { + sprintf( buf, " (occurrence %d)", strictz_error_count[errnum] ); + write_string( buf ); + } + z_new_line( ); + } + +} /* report_strictz_error */ + +#endif /* STRICTZ */ + + +#if !defined(AMIGA) + +/* + * fit_line + * + * This routine determines whether a line of text will still fit + * on the screen. + * + * line : Line of text to test. + * pos : Length of text line (in characters). + * max : Maximum number of characters to fit on the screen. + * + */ +int fit_line( const char *line_buffer, int pos, int max ) +{ + UNUSEDVAR( line_buffer ); + + return ( pos < max ); +} /* fit_line */ + +#endif /* !defined(AMIGA) */ + +#if !defined(AMIGA) + +/* + * print_status + * + * Print the status line (type 3 games only). + * + * argv[0] : Location name + * argv[1] : Moves/Time + * argv[2] : Score + * + * Depending on how many arguments are passed to this routine + * it is to print the status line. The rendering attributes + * and the status line window will be have been activated + * when this routine is called. It is to return FALSE if it + * cannot render the status line in which case the interpreter + * will use display_char() to render it on its own. + * + * This routine has been provided in order to support + * proportional-spaced fonts. + * + */ + +int print_status( int argc, char *argv[] ) +{ + UNUSEDVAR( argc ); + UNUSEDVAR( argv ); + + return ( FALSE ); +} /* print_status */ + +#endif /* !defined(AMIGA) */ + +#if !defined(AMIGA) + +/* + * set_font + * + * Set a new character font. Font can be either be: + * + * TEXT_FONT (1) = normal text character font + * GRAPHICS_FONT (3) = graphical character font + * + */ + +void set_font( int font_type ) +{ + UNUSEDVAR( font_type ); +} /* set_font */ + +#endif /* !defined(AMIGA) */ + +#if !defined MSDOS && !defined OS2 && !defined AMIGA && !defined HARD_COLORS && !defined ATARIST + +/* + * set_colours + * + * Sets screen foreground and background colours. + * + */ + +void set_colours( zword_t foreground, zword_t background ) +{ + +} /* set_colours */ + +#endif /* !defined MSDOS && !defined OS2 && !defined AMIGA !defined HARD_COLORS && !defined ATARIST */ + +#if !defined VMS && !defined MSDOS && !defined OS2 && !defined POSIX + +/* + * codes_to_text + * + * Translate Z-code characters to machine specific characters. These characters + * include line drawing characters and international characters. + * + * The routine takes one of the Z-code characters from the following table and + * writes the machine specific text replacement. The target replacement buffer + * is defined by MAX_TEXT_SIZE in ztypes.h. The replacement text should be in a + * normal C, zero terminated, string. + * + * Return 0 if a translation was available, otherwise 1. + * + * Arrow characters (0x18 - 0x1b): + * + * 0x18 Up arrow + * 0x19 Down arrow + * 0x1a Right arrow + * 0x1b Left arrow + * + * International characters (0x9b - 0xa3): + * + * 0x9b a umlaut (ae) + * 0x9c o umlaut (oe) + * 0x9d u umlaut (ue) + * 0x9e A umlaut (Ae) + * 0x9f O umlaut (Oe) + * 0xa0 U umlaut (Ue) + * 0xa1 sz (ss) + * 0xa2 open quote (>>) + * 0xa3 close quota (<<) + * + * Line drawing characters (0xb3 - 0xda): + * + * 0xb3 vertical line (|) + * 0xba double vertical line (#) + * 0xc4 horizontal line (-) + * 0xcd double horizontal line (=) + * all other are corner pieces (+) + * + */ + +int codes_to_text( int c, char *s ) +{ + return 1; +} /* codes_to_text */ + +#endif /* !defined VMS && !defined MSDOS && !defined OS2 && !defined POSIX */ diff --git a/property.c b/property.c new file mode 100644 index 0000000..6bfcc94 --- /dev/null +++ b/property.c @@ -0,0 +1,583 @@ + +/* $Id: property.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: property.c,v 1.2 2000/05/25 22:28:56 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: property.c,v $ + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * property.c + * + * Property manipulation routines + * + */ + +#include "ztypes.h" + +/* + * get_property_addr + * + * Calculate the address of the start of the property list associated with an + * object. + * + */ + +static zword_t get_property_addr( zword_t obj ) +{ + zword_t object_addr; + zword_t prop_addr; + zbyte_t size; + + /* Calculate the address of the property pointer in the object */ + + object_addr = get_object_address( obj ); + object_addr += ( h_type <= V3 ) ? O3_PROPERTY_OFFSET : O4_PROPERTY_OFFSET; + + /* Read the property address */ + prop_addr = get_word( object_addr ); + + /* Skip past object description which is an ASCIC of encoded words */ + size = get_byte( prop_addr ); + + return prop_addr + ( size * 2 ) + 1; + +} /* get_property_addr */ + +/* + * get_next_property + * + * Calculate the address of the next property in a property list. + * + */ + +static zword_t get_next_property( zword_t prop_addr ) +{ + zbyte_t value; + + /* Load the current property id */ + value = get_byte( prop_addr ); + prop_addr++; + + /* Calculate the length of this property */ + + if ( h_type <= V3 ) + value >>= 5; + else if ( !( value & 0x80 ) ) + value >>= 6; + else + { + value = get_byte( prop_addr ); + value &= property_size_mask; + + if ( value == 0 ) + value = 64; /* spec 1.0 */ + } + + /* Address property length to current property pointer */ + return prop_addr + value + 1; + +} /* get_next_property */ + +/* + * z_get_prop + * + * Load a property from a property list. Properties are held in list sorted by + * property id, with highest ids first. There is also a concept of a default + * property for loading only. The default properties are held in a table pointed + * to be the object pointer, and occupy the space before the first object. + * + */ + +void z_get_prop( zword_t obj, zword_t prop ) +{ + zword_t prop_addr; + zword_t wprop_val; + zbyte_t bprop_val; + zbyte_t value; + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_GET_PROP, "@get_prop called with object 0" ); + store_operand( 0 ); + return; + } +#endif + + /* Load address of first property */ + prop_addr = get_property_addr( obj ); + + /* Scan down the property list */ + for ( ;; ) + { + value = get_byte( prop_addr ); + if ( ( zbyte_t ) ( value & property_mask ) <= ( zbyte_t ) prop ) + break; + prop_addr = get_next_property( prop_addr ); + } + + /* If the property ids match then load the first property */ + + if ( ( zbyte_t ) ( value & property_mask ) == ( zbyte_t ) prop ) /* property found */ + { + prop_addr++; + /* Only load first property if it is a byte sized property */ + if ( h_type <= V3 && !( value & 0xe0 ) || h_type >= V4 && !( value & 0xc0 ) ) + { + bprop_val = get_byte( prop_addr ); + wprop_val = bprop_val; + } + else + { + wprop_val = get_word( prop_addr ); + } + } + else /* property not found */ + { + /* Calculate the address of the default property */ + prop_addr = h_objects_offset + ( ( prop - 1 ) * 2 ); + wprop_val = get_word( prop_addr ); + } + + /* store the property value */ + + store_operand( wprop_val ); + +} /* z_get_prop */ + +/* + * z_put_prop + * + * Store a property value in a property list. The property must exist in the + * property list to be replaced. + * + */ + +void z_put_prop( zword_t obj, zword_t prop, zword_t setvalue ) +{ + zword_t prop_addr; + zword_t value; + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_PUT_PROP, "@put_prop called with object 0" ); + return; + } +#endif + + /* Load address of first property */ + prop_addr = get_property_addr( obj ); + + /* Scan down the property list */ + for ( ;; ) + { + value = get_byte( prop_addr ); + if ( ( value & property_mask ) <= prop ) + break; + prop_addr = get_next_property( prop_addr ); + } + + /* If the property id was found, store a new value, otherwise complain */ + + if ( ( value & property_mask ) != prop ) + { + fatal( "store_property(): No such property" ); + } + + /* Determine if this is a byte or word sized property */ + + prop_addr++; + + if ( h_type <= V3 && !( value & 0xe0 ) || h_type >= V4 && !( value & 0xc0 ) ) + { + set_byte( prop_addr, ( zbyte_t ) setvalue ); + } + else + { + set_word( prop_addr, ( zword_t ) setvalue ); + } + +} /* z_put_prop */ + +/* + * z_get_next_prop + * + * Load the property after the current property. If the current property is zero + * then load the first property. + * + */ + +void z_get_next_prop( zword_t obj, zword_t prop ) +{ + zword_t prop_addr; + zbyte_t value; + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_GET_NEXT_PROP, "@get_next_prop called with object 0" ); + store_operand( 0 ); + return; + } +#endif + + /* Load address of first property */ + prop_addr = get_property_addr( obj ); + + /* If the property id is non zero then find the next property */ + if ( prop != 0 ) + { + /* Scan down the property list while the target property id is less + * than the property id in the list */ + do + { + value = get_byte( prop_addr ); + prop_addr = get_next_property( prop_addr ); + } + while ( ( zbyte_t ) ( value & property_mask ) > ( zbyte_t ) prop ); + + /* If the property id wasn't found then complain */ + if ( ( zbyte_t ) ( value & property_mask ) != ( zbyte_t ) prop ) + { + fatal( "load_next_property(): No such property" ); + } + } + + /* Return the next property id */ + value = get_byte( prop_addr ); + store_operand( ( zword_t ) ( value & property_mask ) ); + +} /* z_get_next_prop */ + +/* + * z_get_prop_addr + * + * Load the address address of the data associated with a property. + * + */ + +void z_get_prop_addr( zword_t obj, zword_t prop ) +{ + zword_t prop_addr; + zbyte_t value; + +#ifdef STRICTZ + if ( obj == 0 ) + { + report_strictz_error( STRZERR_GET_PROP_ADDR, "@get_prop_addr called with object 0" ); + store_operand( 0 ); + return; + } +#endif + + /* Load address of first property */ + prop_addr = get_property_addr( obj ); + + /* Scan down the property list */ + for ( ;; ) + { + value = get_byte( prop_addr ); + if ( ( zbyte_t ) ( value & property_mask ) <= ( zbyte_t ) prop ) + break; + prop_addr = get_next_property( prop_addr ); + } + + /* If the property id was found, calc the prop addr, else return zero */ + + if ( ( zbyte_t ) ( value & property_mask ) == ( zbyte_t ) prop ) + { + /* Skip past property id, can be a byte or a word */ + + if ( h_type >= V4 && ( value & 0x80 ) ) + { + prop_addr++; + } + store_operand( ( zword_t ) ( prop_addr + 1 ) ); + } + else + { + /* No property found, just return 0 */ + store_operand( 0 ); + } + +} /* z_get_prop_addr */ + +/* + * z_get_prop_len + * + * Load the length of a property. + * + */ + +void z_get_prop_len( zword_t prop_addr ) +{ + zbyte_t value; + + /* This is proper according to an email to the Zmachine list by Graham*/ + if ( prop_addr == 0 ) + { + store_operand( ( zword_t ) 0 ); + return; + } + + /* Back up the property pointer to the property id */ + prop_addr--; + value = get_byte( prop_addr ); + + if ( h_type <= V3 ) + { + value = ( zbyte_t ) ( ( value >> ( zbyte_t ) 5 ) + ( zbyte_t ) 1 ); + } + else if ( !( value & 0x80 ) ) + { + value = ( zbyte_t ) ( ( value >> ( zbyte_t ) 6 ) + ( zbyte_t ) 1 ); + } + else + { + value &= ( zbyte_t ) property_size_mask; + + if ( value == 0 ) + value = ( zbyte_t ) 64; /* spec 1.0 */ + } + + store_operand( value ); + +} /* z_get_prop_len */ + +/* + * z_scan_table + * + * Scan an array of bytes or words looking for a target byte or word. The + * optional 4th parameter can set the address step and also whether to scan a + * byte array. + * + */ + +void z_scan_table( int argc, zword_t * argv ) +{ + unsigned long address; + unsigned int i, step; + + /* Supply default parameters */ + + if ( argc < 4 ) + argv[3] = 0x82; + + address = argv[1]; + step = argv[3]; + + /* Check size bit (bit 7 of step, 1 = word, 0 = byte) */ + + if ( step & 0x80 ) + { + + step &= 0x7f; + + /* Scan down an array for count words looking for a match */ + + for ( i = 0; i < argv[2]; i++ ) + { + + /* If the word was found store its address and jump */ + + if ( read_data_word( &address ) == argv[0] ) + { + store_operand( ( zword_t ) ( address - 2 ) ); + conditional_jump( TRUE ); + return; + } + + /* Back up address then step by increment */ + + address = ( address - 2 ) + step; + + } + + } + else + { + + step &= 0x7f; + + /* Scan down an array for count bytes looking for a match */ + + for ( i = 0; i < argv[2]; i++ ) + { + + /* If the byte was found store its address and jump */ + + if ( ( zword_t ) read_data_byte( &address ) == ( zword_t ) argv[0] ) + { + store_operand( ( zword_t ) ( address - 1 ) ); + conditional_jump( TRUE ); + return; + } + + /* Back up address then step by increment */ + + address = ( address - 1 ) + step; + + } + + } + + /* If the data was not found store zero and jump */ + + store_operand( 0 ); + conditional_jump( FALSE ); + +} /* z_scan_table */ + +/* + * z_copy_table + * + */ + +void z_copy_table( zword_t src, zword_t dst, zword_t count ) +{ + unsigned long address; + unsigned int i; + + /* Catch no-op move case */ + + if ( src == dst || count == 0 ) + return; + + /* If destination address is zero then fill source with zeros */ + + if ( dst == 0 ) + { + for ( i = 0; i < count; i++ ) + z_storeb( src++, 0, 0 ); + return; + } + + address = src; + + if ( ( ZINT16 ) count < 0 ) + { + while ( count++ ) + z_storeb( dst++, 0, read_data_byte( &address ) ); + } + else + { + address += ( unsigned long ) count; + dst += count; + while ( count-- ) + { + address--; + z_storeb( --dst, 0, read_data_byte( &address ) ); + address--; + } + } + +} /* z_copy_table */ + +/* + * z_loadw + * + * Load a word from an array of words + * + */ + +void z_loadw( zword_t addr, zword_t offset ) +{ + unsigned long address; + + /* Calculate word array index address */ + + address = addr + ( offset * 2 ); + + /* Store the byte */ + + store_operand( read_data_word( &address ) ); + +} /* z_loadw */ + +/* + * z_loadb + * + * Load a byte from an array of bytes + * + */ + +void z_loadb( zword_t addr, zword_t offset ) +{ + unsigned long address; + + /* Calculate byte array index address */ + + address = addr + offset; + + /* Load the byte */ + + store_operand( read_data_byte( &address ) ); + +} /* z_loadb */ + +/* + * z_storew + * + * Store a word in an array of words + * + */ + +void z_storew( zword_t addr, zword_t offset, zword_t value ) +{ + + /* Calculate word array index address */ + + addr += offset * 2; + + /* Check we are not writing outside of the writeable data area */ + + if ( addr > data_size ) + fatal( "z_storew(): Attempted write outside of data area" ); + + /* Store the word */ + + set_word( addr, value ); + +} /* z_storew */ + +/* + * z_storeb + * + * Store a byte in an array of bytes + * + */ + +void z_storeb( zword_t addr, zword_t offset, zword_t value ) +{ + + /* Calculate byte array index address */ + + addr += offset; + + /* Check we are not writing outside of the writeable data area */ + + if ( addr > data_size ) + fatal( "z_storeb(): Attempted write outside of data area" ); + + /* Store the byte */ + + set_byte( addr, value ); + +} /* z_storeb */ diff --git a/quetzal.c b/quetzal.c new file mode 100644 index 0000000..291caf3 --- /dev/null +++ b/quetzal.c @@ -0,0 +1,573 @@ + +/* $Id: quetzal.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: quetzal.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: quetzal.c,v $ + * Revision 1.3 2000/07/05 15:20:34 jholder + * Updated code to remove warnings. + * + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* quetzal.c + * + * routines to handle QUETZAL save format + */ + +#include +#include "ztypes.h" + +/* You may want to define these as getc and putc, but then the code gets + * quite big (especially for put_c). + */ +#define get_c getc +#define put_c fputc + +/* Some systems appear to have this in funny places, rather than in + * where it should be. + */ +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#if defined(USE_QUETZAL) /* don't compile anything otherwise */ + +typedef unsigned long ul_t; + +/* IDs of chunks we understand */ +#define ID_FORM 0x464f524d +#define ID_IFZS 0x49465a53 +#define ID_IFhd 0x49466864 +#define ID_UMem 0x554d656d +#define ID_CMem 0x434d656d +#define ID_Stks 0x53746b73 +#define ID_ANNO 0x414e4e4f + +/* macros to write QUETZAL files */ +#define write_byte(fp,b) (put_c ((unsigned)(b),fp) != EOF) +#define write_bytx(fp,b) write_byte(fp,(b) & 0xFF) +#define write_word(fp,w) \ + (write_bytx(fp,(w)>> 8) && write_bytx(fp,(w))) +#define write_long(fp,l) \ + (write_bytx(fp,(ul_t)(l)>>24) && write_bytx(fp,(ul_t)(l)>>16) && \ + write_bytx(fp,(ul_t)(l)>> 8) && write_bytx(fp,(ul_t)(l))) +#define write_chnk(fp,id,len) \ + (write_long(fp,id) && write_long(fp,len)) +#define write_run(fp,run) \ + (write_byte(fp,0) && write_byte(fp,(run))) + +/* save_quetzal + * + * attempt to save game in QUETZAL format; return TRUE on success + */ + +#ifdef USE_ZLIB +int save_quetzal( FILE * sfp, gzFile * gfp ) +#else +int save_quetzal( FILE * sfp, FILE * gfp ) +#endif +{ + ul_t ifzslen = 0, cmemlen = 0, stkslen = 0, tmp_pc; + int c; + zword_t i, j, n, init_fp, tmp_fp, nstk, nvars, args; + zword_t frames[STACK_SIZE / 4 + 1]; + zbyte_t var; + long cmempos, stkspos; + + /* write IFZS header */ + if ( !write_chnk( sfp, ID_FORM, 0 ) ) + return FALSE; + if ( !write_long( sfp, ID_IFZS ) ) + return FALSE; + + /* write IFhd chunk */ + if ( !write_chnk( sfp, ID_IFhd, 13 ) ) + return FALSE; + if ( !write_word( sfp, h_version ) ) + return FALSE; + for ( i = 0; i < 6; ++i ) + if ( !write_byte( sfp, get_byte( H_RELEASE_DATE + i ) ) ) + return FALSE; + if ( !write_word( sfp, h_checksum ) ) + return FALSE; + if ( !write_long( sfp, ( ( ul_t ) pc ) << 8 ) ) /* includes pad byte */ + return FALSE; + + /* write CMem chunk */ + /* j is current run length */ + if ( ( cmempos = ftell( sfp ) ) < 0 ) + return FALSE; + if ( !write_chnk( sfp, ID_CMem, 0 ) ) + return FALSE; + jz_rewind( gfp ); + for ( i = 0, j = 0, cmemlen = 0; i < h_restart_size; ++i ) + { + if ( ( c = jz_getc( gfp ) ) == EOF ) + return FALSE; + c ^= get_byte( i ); + if ( c == 0 ) + ++j; + else + { + /* write any run there may be */ + if ( j > 0 ) + { + for ( ; j > 0x100; j -= 0x100 ) + { + if ( !write_run( sfp, 0xFF ) ) + return FALSE; + cmemlen += 2; + } + if ( !write_run( sfp, j - 1 ) ) + return FALSE; + cmemlen += 2; + j = 0; + } + /* write this byte */ + if ( !write_byte( sfp, c ) ) + return FALSE; + ++cmemlen; + } + } + /* there may be a run here, which we ignore */ + if ( cmemlen & 1 ) /* chunk length must be even */ + if ( !write_byte( sfp, 0 ) ) + return FALSE; + + /* write Stks chunk */ + if ( ( stkspos = ftell( sfp ) ) < 0 ) + return FALSE; + if ( !write_chnk( sfp, ID_Stks, 0 ) ) + return FALSE; + + /* frames is a list of FPs, most recent first */ + frames[0] = sp - 5; /* what FP would be if we did a call now */ + for ( init_fp = fp, n = 0; init_fp <= STACK_SIZE - 5; init_fp = stack[init_fp + 2] ) + frames[++n] = init_fp; + init_fp = frames[n] + 4; + + if ( h_type != 6 ) + { /* write a dummy frame for stack used before first call */ + for ( i = 0; i < 6; ++i ) + if ( !write_byte( sfp, 0 ) ) + return FALSE; + nstk = STACK_SIZE - 1 - init_fp; + if ( !write_word( sfp, nstk ) ) + return FALSE; + for ( i = STACK_SIZE - 1; i > init_fp; --i ) + if ( !write_word( sfp, stack[i] ) ) + return FALSE; + stkslen = 8 + 2 * nstk; + } + for ( i = n; i > 0; --i ) + { + /* write out one stack frame. + * + * tmp_fp : FP when this frame was current + * tmp_pc : PC on return from this frame, plus 000pvvvv + * nvars : number of local vars for this frame + * args : argument mask for this frame + * nstk : words of evaluation stack used for this frame + * var : variable to store result + */ + tmp_fp = frames[i]; + nvars = ( stack[tmp_fp + 1] & VARS_MASK ) >> VAR_SHIFT; + args = stack[tmp_fp + 1] & ARGS_MASK; + nstk = tmp_fp - frames[i - 1] - nvars - 4; + tmp_pc = stack[tmp_fp + 3] + ( ul_t ) stack[tmp_fp + 4] * PAGE_SIZE; + switch ( stack[tmp_fp + 1] & TYPE_MASK ) + { + case FUNCTION: + var = read_data_byte( &tmp_pc ); /* also increments tmp_pc */ + tmp_pc = ( tmp_pc << 8 ) | nvars; + break; + case PROCEDURE: + var = 0; + tmp_pc = ( tmp_pc << 8 ) | 0x10 | nvars; /* set procedure flag */ + break; + /* case ASYNC: */ + default: + output_line( "Illegal Z-machine operation: can't save while in interrupt." ); + return FALSE; + } + if ( args != 0 ) + args = ( 1 << args ) - 1; /* make args into bitmap */ + if ( !write_long( sfp, tmp_pc ) ) + return FALSE; + if ( !write_byte( sfp, var ) ) + return FALSE; + if ( !write_byte( sfp, args ) ) + return FALSE; + if ( !write_word( sfp, nstk ) ) + return FALSE; + for ( j = 0; j < nvars + nstk; ++j, --tmp_fp ) + if ( !write_word( sfp, stack[tmp_fp] ) ) + return FALSE; + stkslen += 8 + 2 * ( nvars + nstk ); + } + + /* fill in lengths for variable-sized chunks */ + ifzslen = 3 * 8 + 4 + 14 + cmemlen + stkslen; + if ( cmemlen & 1 ) + ++ifzslen; + ( void ) fseek( sfp, ( long ) 4, SEEK_SET ); + if ( !write_long( sfp, ifzslen ) ) + return FALSE; + ( void ) fseek( sfp, cmempos + 4, SEEK_SET ); + if ( !write_long( sfp, cmemlen ) ) + return FALSE; + ( void ) fseek( sfp, stkspos + 4, SEEK_SET ); + if ( !write_long( sfp, stkslen ) ) + return FALSE; + + return TRUE; +} + +/* end save_quetzal */ + +/* read_word + * + * attempt to read a word; return TRUE on success + */ + +static int read_word( FILE * fp, zword_t * result ) +{ + int a, b; + + if ( ( a = get_c( fp ) ) == EOF ) + return FALSE; + if ( ( b = get_c( fp ) ) == EOF ) + return FALSE; + *result = ( ( zword_t ) a << 8 ) | b; + return TRUE; +} + +/* read_long + * + * attempt to read a longword; return TRUE on success + */ + +static int read_long( FILE * fp, ul_t * result ) +{ + int a, b, c, d; + + if ( ( a = get_c( fp ) ) == EOF ) + return FALSE; + if ( ( b = get_c( fp ) ) == EOF ) + return FALSE; + if ( ( c = get_c( fp ) ) == EOF ) + return FALSE; + if ( ( d = get_c( fp ) ) == EOF ) + return FALSE; + *result = ( ( ul_t ) a << 24 ) | ( ( ul_t ) b << 16 ) | ( ( ul_t ) c << 8 ) | d; +#ifdef QDEBUG + printf( "%c%c%c%c", a, b, c, d ); +#endif + return TRUE; +} + +/* restore_quetzal + * + * attempt to restore game in QUETZAL format; return TRUE on success + */ + +#define GOT_HEADER 0x01 +#define GOT_STACK 0x02 +#define GOT_MEMORY 0x04 +#define GOT_NONE 0x00 +#define GOT_ALL 0x07 +#define GOT_ERROR 0x80 + +#ifdef USE_ZLIB +int restore_quetzal( FILE * sfp, gzFile * gfp ) +#else +int restore_quetzal( FILE * sfp, FILE * gfp ) +#endif +{ + ul_t ifzslen, currlen, tmpl, skip = 0; + zword_t i, tmpw; + zbyte_t progress = GOT_NONE; + int x, y; + + /* check for IFZS file */ + if ( !read_long( sfp, &tmpl ) || !read_long( sfp, &ifzslen ) ) + return FALSE; + if ( !read_long( sfp, &currlen ) ) + return FALSE; + if ( tmpl != ID_FORM || currlen != ID_IFZS ) + { + output_line( "This is not a saved game file!" ); + return FALSE; + } + if ( ( ifzslen & 1 ) || ifzslen < 4 ) + return FALSE; + ifzslen -= 4; + + /* read a chunk and process it */ + while ( ifzslen > 0 ) + { + /* read chunk header */ + if ( ifzslen < 8 ) + return FALSE; + if ( !read_long( sfp, &tmpl ) || !read_long( sfp, &currlen ) ) + return FALSE; + ifzslen -= 8; + + /* body of chunk */ + if ( ifzslen < currlen ) + return FALSE; + skip = currlen & 1; + ifzslen -= currlen + skip; + switch ( tmpl ) + { + case ID_IFhd: + if ( progress & GOT_HEADER ) + { + output_line( "Save file has two IFhd chunks!" ); + return FALSE; + } + progress |= GOT_HEADER; + if ( currlen < 13 || !read_word( sfp, &i ) ) + return FALSE; + if ( i != h_version ) + progress = GOT_ERROR; + for ( i = H_RELEASE_DATE; i < H_RELEASE_DATE + 6; ++i ) + { + if ( ( x = get_c( sfp ) ) == EOF ) + return FALSE; + if ( x != ( int ) get_byte( i ) ) + progress = GOT_ERROR; + } + if ( !read_word( sfp, &i ) ) + return FALSE; + if ( i != h_checksum ) + progress = GOT_ERROR; + if ( progress == GOT_ERROR ) + { + output_line( "File was not saved from this story!" ); + return FALSE; + } + if ( ( x = get_c( sfp ) ) == EOF ) + return FALSE; + pc = ( ul_t ) x << 16; + if ( ( x = get_c( sfp ) ) == EOF ) + return FALSE; + pc |= ( ul_t ) x << 8; + if ( ( x = get_c( sfp ) ) == EOF ) + return FALSE; + pc |= ( ul_t ) x; + for ( i = 13; ( ul_t ) i < currlen; ++i ) + ( void ) get_c( sfp ); /* skip rest of chunk */ + break; + case ID_Stks: + if ( progress & GOT_STACK ) + { + output_line( "File contains two stack chunks!" ); + break; + } + progress |= GOT_STACK; + sp = STACK_SIZE; + if ( h_type != 6 ) + { + /* dummy stack frame for stack used before call */ + if ( currlen < 8 ) + return FALSE; + for ( i = 0; i < 6; ++i ) + if ( get_c( sfp ) != 0 ) + return FALSE; + if ( !read_word( sfp, &tmpw ) ) + return FALSE; + currlen -= 8; + if ( currlen < (unsigned long)(tmpw * 2) ) + return FALSE; + for ( i = 0; i < tmpw; ++i ) + if ( !read_word( sfp, stack + ( --sp ) ) ) + return FALSE; + currlen -= tmpw * 2; + } + for ( fp = STACK_SIZE - 1, frame_count = 0; currlen > 0; currlen -= 8 ) + { + if ( currlen < 8 ) + return FALSE; + if ( sp < 4 ) + { + output_line( "error: this save-file has too much stack, and I can't cope." ); + return FALSE; + } + /* read PC, procedure flag, and arg count */ + if ( !read_long( sfp, &tmpl ) ) + return FALSE; + y = ( zword_t ) tmpl & 0x0F; + tmpw = y << VAR_SHIFT; /* number of variables */ + /* read result variable */ + if ( ( x = get_c( sfp ) ) == EOF ) + return FALSE; + + if ( tmpl & 0x10 ) + { + tmpw |= PROCEDURE; + tmpl >>= 8; + } + else + { + tmpw |= FUNCTION; + tmpl >>= 8; + --tmpl; + /* sanity check on result variable */ + if ( read_data_byte( &tmpl ) != ( zbyte_t ) x ) + { + output_line( "error: wrong variable number on stack (wrong story file?)." ); + return FALSE; + } + --tmpl; /* read_data_byte increments it */ + } + stack[--sp] = ( zword_t ) ( tmpl / PAGE_SIZE ); + stack[--sp] = ( zword_t ) ( tmpl % PAGE_SIZE ); + stack[--sp] = fp; + + if ( ( x = get_c( sfp ) ) == EOF ) + return FALSE; + ++x; /* hopefully x now contains a single set bit */ + + for ( i = 0; i < 8; ++i ) + if ( x & ( 1 << i ) ) + break; + if ( x ^ ( 1 << i ) ) /* if more than 1 bit set */ + { + output_line + ( "error: this game uses incomplete argument lists (which I can't handle)." ); + return FALSE; + } + tmpw |= i; + stack[--sp] = tmpw; + fp = sp - 1; /* FP for next frame */ + if ( !read_word( sfp, &tmpw ) ) + return FALSE; + tmpw += y; /* local vars plus eval stack used */ + if ( tmpw >= sp ) + { + output_line( "error: this save-file uses more stack than I can cope with." ); + return FALSE; + } + if ( currlen < (unsigned long)(tmpw * 2) ) + return FALSE; + for ( i = 0; i < tmpw; ++i ) + if ( !read_word( sfp, stack + ( --sp ) ) ) + return FALSE; + currlen -= tmpw * 2; + } + break; + case ID_ANNO: + z_buffer_mode( ON ); + for ( ; currlen > 0; --currlen ) + if ( ( x = get_c( sfp ) ) == EOF ) + return FALSE; + else + write_char( x ); + write_char( ( char ) 13 ); + break; + case ID_CMem: + if ( !( progress & GOT_MEMORY ) ) + { + jz_rewind( gfp ); + i = 0; /* bytes written to data area */ + for ( ; currlen > 0; --currlen ) + { + if ( ( x = get_c( sfp ) ) == EOF ) + return FALSE; + if ( x == 0 ) /* start run */ + { + if ( currlen < 2 ) + { + output_line( "File contains bogus CMem chunk" ); + for ( ; currlen > 0; --currlen ) + ( void ) get_c( sfp ); /* skip rest */ + currlen = 1; + i = 0xFFFF; + break; /* keep going in case there's a UMem */ + } + --currlen; + if ( ( x = get_c( sfp ) ) == EOF ) + return FALSE; + for ( ; x >= 0 && i < h_restart_size; --x, ++i ) + if ( ( y = jz_getc( gfp ) ) == EOF ) + return FALSE; + else + set_byte( i, y ); + } + else /* not a run */ + { + if ( ( y = jz_getc( gfp ) ) == EOF ) + return FALSE; + set_byte( i, x ^ y ); + ++i; + } + if ( i > h_restart_size ) + { + output_line( "warning: CMem chunk too long!" ); + for ( ; currlen > 1; --currlen ) + ( void ) get_c( sfp ); /* skip rest */ + break; /* keep going in case there's a UMem */ + } + } + /* if chunk is short, assume a run */ + for ( ; i < h_restart_size; ++i ) + if ( ( y = jz_getc( gfp ) ) == EOF ) + return FALSE; + else + set_byte( i, y ); + if ( currlen == 0 ) + progress |= GOT_MEMORY; /* only if succeeded */ + break; + } + /* Fall thru (to default) if already got memory */ + case ID_UMem: + if ( !( progress & GOT_MEMORY ) ) + { + if ( currlen == h_restart_size ) + { + if ( fread( datap, h_restart_size, 1, sfp ) == 1 ) + { + progress |= GOT_MEMORY; /* only if succeeded */ + break; + } + } + else + output_line( "warning: UMem chunk wrong size!" ); + /* and fall thru into default */ + } + /* Fall thru (to default) if already got memory */ + default: + ( void ) fseek( sfp, currlen, SEEK_CUR ); /* skip chunk */ + break; + } + if ( skip ) + ( void ) get_c( sfp ); /* skip pad byte */ + } + + if ( !( progress & GOT_HEADER ) ) + output_line( "error: no header chunk in file." ); + if ( !( progress & GOT_STACK ) ) + output_line( "error: no stack chunk in file." ); + if ( !( progress & GOT_MEMORY ) ) + output_line( "error: no memory chunk in file." ); + return ( progress == GOT_ALL ); +} + + +#endif /* defined(USE_QUETZAL) */ diff --git a/screen.c b/screen.c new file mode 100644 index 0000000..825ba7d --- /dev/null +++ b/screen.c @@ -0,0 +1,577 @@ + +/* $Id: screen.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: screen.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: screen.c,v $ + * Revision 1.3 2000/07/05 15:20:34 jholder + * Updated code to remove warnings. + * + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * screen.c + * + * Generic screen manipulation routines. Most of these routines call the machine + * specific routines to do the actual work. + * + */ + +#include "ztypes.h" + +/* + * z_set_window + * + * Put the cursor in the text or status window. The cursor is free to move in + * the status window, but is fixed to the input line in the text window. + * + */ + +void z_set_window( zword_t w ) +{ + int row, col; + + flush_buffer( FALSE ); + + screen_window = w; + + if ( screen_window == STATUS_WINDOW ) + { + + /* Status window: disable formatting and select status window */ + + formatting = OFF; + scripting_disable = ON; + select_status_window( ); + + /* Put cursor at top of status area */ + + if ( h_type < V4 ) + move_cursor( 2, 1 ); + else + move_cursor( 1, 1 ); + + } + else + { + + /* Text window: enable formatting and select text window */ + + select_text_window( ); + scripting_disable = OFF; + formatting = ON; + + /* Move cursor if it has been left in the status area */ + + get_cursor_position( &row, &col ); + if ( row <= status_size ) + move_cursor( status_size + 1, 1 ); + + } + + /* Force text attribute to normal rendition */ + + set_attribute( NORMAL ); + +} /* z_set_window */ + +/* + * z_split_window + * + * Set the size of the status window. The default size for the status window is + * zero lines for both type 3 and 4 games. The status line is handled specially + * for type 3 games and always occurs the line immediately above the status + * window. + * + */ + +void z_split_window( zword_t lines ) +{ + /* Maximum status window size is 255 */ + + lines &= 0xff; + + /* The top line is always set for V1 to V3 games, so account for it here. */ + + if ( h_type < V4 ) + lines++; + + if ( lines ) + { + + /* If size is non zero the turn on the status window */ + + status_active = ON; + + /* Bound the status size to one line less than the total screen height */ + + if ( lines > ( zword_t ) ( screen_rows - 1 ) ) + status_size = ( zword_t ) ( screen_rows - 1 ); + else + status_size = lines; + + /* Create the status window, or resize it */ + + create_status_window( ); + + /* Need to clear the status window for type 3 games */ + + if ( h_type < V4 ) + z_erase_window( STATUS_WINDOW ); + + } + else + { + + /* Lines are zero so turn off the status window */ + + status_active = OFF; + + /* Reset the lines written counter and status size */ + + lines_written = 0; + status_size = 0; + + /* Delete the status window */ + + delete_status_window( ); + + /* Return cursor to text window */ + + select_text_window( ); + } + +} /* z_split_window */ + +/* + * z_erase_window + * + * Clear one or all windows on the screen. + * + */ + +void z_erase_window( zword_t w ) +{ + flush_buffer( TRUE ); + + if ( ( zbyte_t ) w == ( zbyte_t ) Z_SCREEN ) + { + clear_screen( ); + } + else if ( ( zbyte_t ) w == TEXT_WINDOW ) + { + clear_text_window( ); + } + else if ( ( zbyte_t ) w == STATUS_WINDOW ) + { + clear_status_window( ); + return; + } + + if ( h_type > V4 ) + move_cursor( 1, 1 ); + else + move_cursor( screen_rows, 1 ); + +} /* z_erase_window */ + +/* + * z_erase_line + * + * Clear one line on the screen. + * + */ + +void z_erase_line( zword_t flag ) +{ + if ( flag == TRUE ) + clear_line( ); +} /* z_erase_line */ + +/* + * z_set_cursor + * + * Set the cursor position in the status window only. + * + */ + +void z_set_cursor( zword_t row, zword_t column ) +{ + /* Can only move cursor if format mode is off and in status window */ + + if ( formatting == OFF && screen_window == STATUS_WINDOW ) + { + move_cursor( row, column ); + } +#ifdef STRICTZ + else + { + report_strictz_error( STRZERR_MOV_CURSOR, "@set_cursor called outside the status window!" ); + } +#endif +} /* z_set_cursor */ + +/* + * pad_line + * + * Pad the status line with spaces up to a column position. + * + */ + +static void pad_line( int column ) +{ + int i; + + for ( i = status_pos; i < column; i++ ) + write_char( ' ' ); + status_pos = column; +} /* pad_line */ + +/* + * z_show_status + * + * Format and output the status line for type 3 games only. + * + */ + +void z_show_status( void ) +{ + int i, count = 0, end_of_string[3]; + char *status_part[3]; + + /* Move the cursor to the top line of the status window, set the reverse + * rendition and print the status line */ + + z_set_window( STATUS_WINDOW ); + move_cursor( 1, 1 ); + set_attribute( REVERSE ); + + /* Redirect output to the status line buffer */ + + z_output_stream( 3, 0 ); + + /* Print the object description for global variable 16 */ + + pad_line( 1 ); + status_part[count] = &status_line[status_pos]; + if ( load_variable( 16 ) != 0 ) + z_print_obj( load_variable( 16 ) ); + end_of_string[count++] = status_pos; + status_line[status_pos++] = '\0'; + + if ( get_byte( H_CONFIG ) & CONFIG_TIME ) + { + + /* If a time display print the hours and minutes from global + * variables 17 and 18 */ + + pad_line( screen_cols - 21 ); + status_part[count] = &status_line[status_pos]; + write_string( " Time: " ); + print_time( load_variable( 17 ), load_variable( 18 ) ); + end_of_string[count++] = status_pos; + status_line[status_pos++] = '\0'; + } + else + { + + /* If a moves/score display print the score and moves from global + * variables 17 and 18 */ + + pad_line( screen_cols - 31 ); + status_part[count] = &status_line[status_pos]; + write_string( " Score: " ); + z_print_num( load_variable( 17 ) ); + end_of_string[count++] = status_pos; + status_line[status_pos++] = '\0'; + + pad_line( screen_cols - 15 ); + status_part[count] = &status_line[status_pos]; + write_string( " Moves: " ); + z_print_num( load_variable( 18 ) ); + end_of_string[count++] = status_pos; + status_line[status_pos++] = '\0'; + } + + /* Pad the end of status line with spaces then disable output redirection */ + + pad_line( screen_cols ); + z_output_stream( ( zword_t ) - 3, 0 ); + + /* Try and print the status line for a proportional font screen. If this + * fails then remove embedded nulls in status line buffer and just output + * it to the screen */ + + if ( print_status( count, status_part ) == FALSE ) + { + for ( i = 0; i < count; i++ ) + status_line[end_of_string[i]] = ' '; + status_line[status_pos] = '\0'; + write_string( status_line ); + } + + set_attribute( NORMAL ); + z_set_window( TEXT_WINDOW ); + +} /* z_show_status */ + +/* + * blank_status_line + * + * Output a blank status line for type 3 games only. + * + */ + +void blank_status_line( void ) +{ + + /* Move the cursor to the top line of the status window, set the reverse + * rendition and print the status line */ + + z_set_window( STATUS_WINDOW ); + move_cursor( 1, 1 ); + set_attribute( REVERSE ); + + /* Redirect output to the status line buffer and pad the status line with + * spaces then disable output redirection */ + + z_output_stream( 3, 0 ); + pad_line( screen_cols ); + status_line[status_pos] = '\0'; + z_output_stream( ( zword_t ) - 3, 0 ); + + /* Write the status line */ + + write_string( status_line ); + + /* Turn off attributes and return to text window */ + + set_attribute( NORMAL ); + z_set_window( TEXT_WINDOW ); + +} /* blank_status_line */ + +/* + * output_string + * + * Output a string of characters. + * + */ + +void output_string( const char *s ) +{ + while ( *s ) + output_char( *s++ ); +} /* output_string */ + +/* + * output_line + * + * Output a string of characters followed by a new line. + * + */ + +void output_line( const char *s ) +{ + output_string( s ); + output_new_line( ); +} /* output_line */ + +/* + * output_char + * + * Output a character. + * + */ + +void output_char( int c ) +{ + /* If output is enabled then either select the rendition attribute + * or just display the character */ + + if ( outputting == ON ) + { + display_char( (unsigned int)(c & 0xff) ); + } +} /* output_char */ + +/* + * output_new_line + * + * Scroll the text window up one line and pause the window if it is full. + * + */ + +void output_new_line( void ) +{ + int row, col; + + /* Don't print if output is disabled or replaying commands */ + + if ( outputting == ON ) + { + + if ( formatting == ON && screen_window == TEXT_WINDOW ) + { + + /* If this is the text window then scroll it up one line */ + + scroll_line( ); + + /* See if we have filled the screen. The spare line is for + * the [MORE] message + */ + + if ( ++lines_written >= ( ( screen_rows - top_margin ) - status_size - 1 ) ) + { + + /* Display the new status line while the screen in paused */ + + if ( h_type < V4 ) + z_show_status( ); + + /* Reset the line count and display the more message */ + + lines_written = 0; + + if ( replaying == OFF ) + { + get_cursor_position( &row, &col ); + output_string( "[MORE]" ); + ( void ) input_character( 0 ); + move_cursor( row, col ); + output_string( " " ); + move_cursor( row, col ); + /* clear_line (); */ + } + } + } + else + /* If this is the status window then just output a new line */ + + output_char( '\n' ); + } + +} /* output_new_line */ + +/* + * z_print_table + * + * Writes text into a rectangular window on the screen. + * + * argv[0] = start of text address + * argv[1] = rectangle width + * argv[2] = rectangle height (default = 1) + * + */ + +void z_print_table( int argc, zword_t * argv ) +{ + unsigned long address; + unsigned int width, height; + unsigned int row, column; + + /* Supply default arguments */ + + if ( argc < 3 ) + argv[2] = 1; + + /* Don't do anything if the window is zero high or wide */ + + if ( argv[1] == 0 || argv[2] == 0 ) + return; + + /* Get coordinates of top left corner of rectangle */ + + get_cursor_position( ( int * ) &row, ( int * ) &column ); + + address = argv[0]; + + /* Write text in width * height rectangle */ + + for ( height = 0; height < argv[2]; height++ ) + { + + for ( width = 0; width < argv[1]; width++ ) + write_char( read_data_byte( &address ) ); + + /* Put cursor back to lefthand side of rectangle on next line */ + + if ( height != (unsigned)( argv[2] - 1 ) ) + move_cursor( ++row, column ); + + } + +} /* z_print_table */ + +/* + * z_set_font + * + * Set text or graphic font. 1 = text font, 3 = graphics font. + * + */ + +void z_set_font( zword_t new_font ) +{ + zword_t old_font = font; + + if ( new_font != old_font ) + { + font = new_font; + set_font( font ); + } + + store_operand( old_font ); + +} /* z_set_font */ + +/* + * z_set_colour + * + * Set the colour of the screen. Colour can be set on four things: + * Screen background + * Text typed by player + * Text written by game + * Graphics characters + * + * Colors can be set to 1 of 9 values: + * 1 = machine default (IBM/PC = blue background, everything else white) + * 2 = black + * 3 = red + * 4 = green + * 5 = brown + * 6 = blue + * 7 = magenta + * 8 = cyan + * 9 = white + * + */ + +void z_set_colour( zword_t foreground, zword_t background ) +{ + if ( ( ZINT16 ) foreground < -1 || ( ZINT16 ) foreground > 9 || ( ZINT16 ) background < -1 || + ( ZINT16 ) background > 9 ) + fatal( "Bad colour!" ); + + + flush_buffer( FALSE ); + + set_colours( foreground, background ); + + return; +} /* z_set_colour */ diff --git a/text.c b/text.c new file mode 100644 index 0000000..b1ae0a0 --- /dev/null +++ b/text.c @@ -0,0 +1,1145 @@ + +/* $Id: text.c,v 1.5 2000/10/10 14:46:22 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: text.c,v 1.5 2000/10/10 14:46:22 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: text.c,v $ + * Revision 1.5 2000/10/10 14:46:22 jholder + * Fixed text wrap bug when printing array w/ \r chars in it + * + * Revision 1.4 2000/10/04 23:07:57 jholder + * fixed redirect problem with isolatin1 range chars + * + * Revision 1.3 2000/07/05 15:20:34 jholder + * Updated code to remove warnings. + * + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * text.c + * + * Text manipulation routines + * + */ + +#include "ztypes.h" + +static int saved_formatting = ON; +static int story_buffer = 0; +static int story_pos = 0; +static int story_count = 0; + +static int line_pos = 0; +static int char_count = 0; + +/* + * decode_text + * + * Convert ZSCII encoded text to ASCII. Text is encoded by squeezing each character + * into 5 bits. 3 x 5 bit encoded characters can fit in one word with a spare + * bit left over. The spare bit is used to signal to end of a string. The 5 bit + * encoded characters can either be actual character codes or prefix codes that + * modifier the following code. + * + */ + +void decode_text( unsigned long *address ) +{ + int i, synonym_flag, synonym = 0, zscii_flag, zscii = 0; + int data, code, shift_state, shift_lock; + unsigned long addr; + + /* Set state variables */ + + shift_state = 0; + shift_lock = 0; + zscii_flag = 0; + synonym_flag = 0; + + do + { + + /* + * Read one 16 bit word. Each word contains three 5 bit codes. If the + * high bit is set then this is the last word in the string. + */ + + data = read_data_word( address ); + + for ( i = 10; i >= 0; i -= 5 ) + { + + /* Get code, high bits first */ + + code = ( data >> i ) & 0x1f; + + /* Synonym codes */ + + if ( synonym_flag ) + { + + synonym_flag = 0; + synonym = ( synonym - 1 ) * 64; + addr = ( unsigned long ) get_word( h_synonyms_offset + synonym + ( code * 2 ) ) * 2; + decode_text( &addr ); + shift_state = shift_lock; + + } + /* ZSCII codes */ + else if ( zscii_flag ) + { + + /* + * If this is the first part ZSCII ten-bit code then remember it. + * Because the codes are only 5 bits you need two codes to make + * one eight bit ASCII character. The first code contains the + * top 5 bits (although only 3 bits are used at the moment). + * The second code contains the bottom 5 bits. + */ + + if ( zscii_flag++ == 1 ) + { + zscii = code << 5; + } + /* + * If this is the second part of a ten-bit ZSCII code then assemble the + * character from the two codes and output it. + */ + else + { + zscii_flag = 0; + write_zchar( ( unsigned char ) ( zscii | code ) ); + } + + } + + /* Character codes */ + else if ( code > 5 ) + { + + code -= 6; + + /* + * If this is character 0 in the punctuation set then the next two + * codes make a ten-bit ZSCII character. (Std. Sec. 3.4) + */ + + if ( shift_state == 2 && code == 0 ) + { + zscii_flag = 1; + } + + /* + * If this is character 1 in the punctuation set then this + * is a new line. + */ + + else if ( shift_state == 2 && code == 1 && h_type > V1 ) + { + z_new_line( ); + } + /* + * This is a normal character so select it from the character + * table appropriate for the current shift state. + */ + + else + { + write_zchar( lookup_table[shift_state][code] ); + } + shift_state = shift_lock; + + } + + /* Special codes 0 to 5 */ + else + { + + /* Space: 0 Output a space character. */ + + if ( code == 0 ) + { + write_zchar( ' ' ); + } + else + { + /* The use of the synonym and shift codes is the only + * difference between the different versions. + */ + + if ( h_type < V3 ) + { + + /* Newline or synonym: 1 + * Output a newline character or set synonym flag. + */ + + if ( code == 1 ) + { + if ( h_type == V1 ) + { + z_new_line( ); + } + else + { + synonym_flag = 1; + synonym = code; + } + } + else + { + /* + * Shift keys: 2, 3, 4 or 5 + * + * Shift keys 2 & 3 only shift the next character and can be used regardless of + * the state of the shift lock. Shift keys 4 & 5 lock the shift until reset. + * + * The following code implements the the shift code state transitions: + * +-------------+-------------+-------------+-------------+ + * | Shift State | Lock State | + * +-------------+-------------+-------------+-------------+-------------+ + * | Code | 2 | 3 | 4 | 5 | + * +-------------+-------------+-------------+-------------+-------------+ + * | lowercase | uppercase | punctuation | uppercase | punctuation | + * | uppercase | punctuation | lowercase | punctuation | lowercase | + * | punctuation | lowercase | uppercase | lowercase | uppercase | + * +-------------+-------------+-------------+-------------+-------------+ + */ + if ( code < 4 ) + { + shift_state = ( shift_lock + code + 2 ) % 3; + } + else + { + shift_lock = shift_state = ( shift_lock + code ) % 3; + } + } + + } + else /* not V3 */ + { + + /* + * Synonym table: 1, 2 or 3 + * + * Selects which of three synonym tables the synonym + * code following in the next code is to use. + */ + if ( code < 4 ) + { + synonym_flag = 1; + synonym = code; + } + /* + * Shift key: 4 or 5 + * + * Selects the shift state for the next character, + * either uppercase (4) or punctuation (5). The shift + * state automatically gets reset back to lowercase for + * V3+ games after the next character is output. + * + */ + else + { + shift_state = code - 3; + shift_lock = 0; + } + } + } + } + } + } + while ( ( data & 0x8000 ) == 0 ); + +} /* decode_text */ + +/* + * encode_text + * + * Pack a string into up to 9 codes or 3 words. + * + */ + +void encode_text( int len, const char *s, ZINT16 * buffer ) +{ + int i, j, prev_table, table, next_table, shift_state, code, codes_count; + char codes[9]; + + /* Initialise codes count and prev_table number */ + + codes_count = 0; + prev_table = 0; + + /* Scan do the string one character at a time */ + + while ( len-- ) + { + + /* + * Set the table and code to be the ASCII character inducer, then + * look for the character in the three lookup tables. If the + * character isn't found then it will be an ASCII character. + */ + + table = 2; + code = 0; + for ( i = 0; i < 3; i++ ) + { + for ( j = 0; j < 26; j++ ) + { + if ( lookup_table[i][j] == *s ) + { + table = i; + code = j; + } + } + } + + /* + * Type 1 and 2 games differ on how the shift keys are used. Switch + * now depending on the game version. + */ + + if ( h_type < V3 ) + { + + /* + * If the current table is the same as the previous table then + * just store the character code, otherwise switch tables. + */ + + if ( table != prev_table ) + { + + /* Find the table for the next character */ + + next_table = 0; + if ( len ) + { + next_table = 2; + for ( i = 0; i < 3; i++ ) + { + for ( j = 0; j < 26; j++ ) + { + if ( lookup_table[i][j] == s[1] ) + next_table = i; + } + } + } + + /* + * Calculate the shift key. This magic. See the description in + * decode_text for more information on version 1 and 2 shift + * key changes. + */ + + shift_state = ( table + ( prev_table * 2 ) ) % 3; + + /* Only store the shift key if there is a change in table */ + + if ( shift_state ) + { + + /* + * If the next character as the uses the same table as + * this character then change the shift from a single + * shift to a shift lock. Also remember the current + * table for the next iteration. + */ + + if ( next_table == table ) + { + shift_state += 2; + prev_table = table; + } + else + prev_table = 0; + + /* Store the code in the codes buffer */ + + if ( codes_count < 9 ) + codes[codes_count++] = ( char ) ( shift_state + 1 ); + } + } + } + else + { + + /* + * For V3 games each uppercase or punctuation table is preceded + * by a separate shift key. If this is such a shift key then + * put it in the codes buffer. + */ + + if ( table && codes_count < 9 ) + codes[codes_count++] = ( char ) ( table + 3 ); + } + + /* Put the character code in the code buffer */ + + if ( codes_count < 9 ) + codes[codes_count++] = ( char ) ( code + 6 ); + + /* + * Cannot find character in table so treat it as a literal ASCII + * code. The ASCII code inducer (code 0 in table 2) is followed by + * the high 3 bits of the ASCII character followed by the low 5 + * bits to make 8 bits in total. + */ + + if ( table == 2 && code == 0 ) + { + if ( codes_count < 9 ) + codes[codes_count++] = ( char ) ( ( *s >> 5 ) & 0x07 ); + if ( codes_count < 9 ) + codes[codes_count++] = ( char ) ( *s & 0x1f ); + } + + /* Advance to next character */ + + s++; + + } + + /* Pad out codes with shift 5's */ + + while ( codes_count < 9 ) + codes[codes_count++] = 5; + + /* Pack codes into buffer */ + + buffer[0] = ( ( ZINT16 ) codes[0] << 10 ) | ( ( ZINT16 ) codes[1] << 5 ) | ( ZINT16 ) codes[2]; + buffer[1] = ( ( ZINT16 ) codes[3] << 10 ) | ( ( ZINT16 ) codes[4] << 5 ) | ( ZINT16 ) codes[5]; + buffer[2] = ( ( ZINT16 ) codes[6] << 10 ) | ( ( ZINT16 ) codes[7] << 5 ) | ( ZINT16 ) codes[8]; + + /* Terminate buffer at 6 or 9 codes depending on the version */ + + if ( h_type < V4 ) + buffer[1] |= 0x8000; + else + buffer[2] |= 0x8000; + +} /* encode_text */ + +/* + * write_zchar + * + * High level Z-code character output routine. Translates Z-code characters to + * machine specific character(s) before output. If it cannot translate it then + * use the default translation. If the character is still unknown then display + * a '?'. + * + */ + +void write_zchar( int c ) +{ + char xlat_buffer[MAX_TEXT_SIZE + 1]; + int i; + + c = ( unsigned int ) ( c & 0xff ); + + /* If character is not special character then just write it */ + + if ( c >= ' ' && c <= '~' ) + { + write_char( c ); + } + else if ( c == 13 ) + { + write_char( '\r' ); + } + else + { + /* Put default character in translation buffer */ + xlat_buffer[0] = '?'; + xlat_buffer[1] = '\0'; + + /* If translation fails then supply a default */ + if ( codes_to_text( c, xlat_buffer ) ) + { + if ( c > 23 && c < 28 ) + { + /* Arrow keys - these must the keyboard keys used for input */ + static char xlat[4] = { '\\', '/', '+', '-' }; + + xlat_buffer[0] = xlat[c - 24]; + xlat_buffer[1] = '\0'; + } + else if ( c == 0 ) + { + /* Null - print nothing */ + xlat_buffer[0] = '\0'; + } + else if ( c < 32 ) + { + /* Some other control character: print an octal escape. */ + xlat_buffer[0] = '\\'; + xlat_buffer[1] = ( char ) ( '0' + ( ( c >> 6 ) & 7 ) ); + xlat_buffer[2] = ( char ) ( '0' + ( ( c >> 3 ) & 7 ) ); + xlat_buffer[3] = ( char ) ( '0' + ( c & 7 ) ); + xlat_buffer[4] = '\0'; + } + else if ( c > 178 && c < 219 ) + { + /* IBM line drawing characters to ASCII characters */ + if ( c == 179 ) + xlat_buffer[0] = '|'; + else if ( c == 186 ) + xlat_buffer[0] = '#'; + else if ( c == 196 ) + xlat_buffer[0] = '-'; + else if ( c == 205 ) + xlat_buffer[0] = '='; + else + xlat_buffer[0] = '+'; + xlat_buffer[1] = '\0'; + } + else if ( c > 154 && c < 164 ) + { + /* German character replacements */ + static char xlat[] = "aeoeueAeOeUess>><<"; + + xlat_buffer[0] = xlat[( ( c - 155 ) * 2 ) + 0]; + xlat_buffer[1] = xlat[( ( c - 155 ) * 2 ) + 1]; + xlat_buffer[2] = '\0'; + } + } + + /* Substitute translated characters */ + + for ( i = 0; xlat_buffer[i] != '\0'; i++ ) + { + write_char( ( unsigned char ) xlat_buffer[i] ); + } + + } +} /* write_zchar */ + +/* + * translate_to_zscii + * + * + */ +zbyte_t translate_to_zscii(int c) +{ + int i; + + if( c>= 0xa0 ) + { + if( h_unicode_table !=0 ) + { + fprintf(stderr,"[[ Unicode support not enabled yet. ]]"); + } + else + { + for (i = 0x9b; i <= 0xdf; i++) + { + if (c == zscii2latin1[i - 0x9b]) + { + return (zbyte_t) i; + } + } + return '?'; + } + } + return (zbyte_t) c; +} + +/* + * write_char + * + * High level character output routine. The write_char routine is slightly + * complicated by the fact that the output can be limited by a fixed character + * count, as well as, filling up the buffer. + * + */ +void write_char( int c ) +{ + char *cp; + int right_len; + + /* Only do if text formatting is turned on */ + + if ( redirect_depth ) + { + /* If redirect is on then write the character to the status line + * for V1 to V3 games or into the writeable data area for V4+ games */ + if ( h_type < V4 ) + { + status_line[status_pos++] = ( char ) c; + } + else + { + set_byte( story_pos++, translate_to_zscii(c) ); + story_count++; + } + } + else if ( formatting == ON && screen_window == TEXT_WINDOW ) + { + if ( fit_line( line, line_pos, screen_cols - right_margin ) == 0 || char_count < 1 ) + { + /* Null terminate the line */ + line[line_pos] = '\0'; + + /* If the next character is a space then no wrap is neccessary */ + if ( c == ' ' ) + { + z_new_line( ); + c = '\0'; + } + else + { + /* Wrap the line. First find the last space */ + cp = strrchr( line, ' ' ); + + /* If no spaces in the lines then cannot do wrap */ + if ( cp == NULL ) + { + /* Output the buffer and a new line */ + z_new_line( ); + } + + if (cp != NULL) + { + /* Terminate the line at the last space */ + *cp++ = '\0'; + + /* Calculate the text length after the last space */ + right_len = &line[line_pos] - cp; + + /* Output the buffer and a new line */ + z_new_line( ); + + /* If any text to wrap then move it to the start of the line */ + if ( right_len > 0 ) + { + memmove( line, cp, right_len ); + line_pos = right_len; + } + } + } + } + /* Put the character into the buffer and count it. + * Decrement line width if the character is visible */ + if ( c ) + { + line[line_pos++] = ( char ) c; + + /* Wrap the line when there is a newline in the stream. */ + cp = strrchr( line, 13 ); + if ( cp!= NULL ) + { + /* Terminate the line at the last space */ + *cp++ = '\0'; + + /* Calculate the text length after the last space */ + right_len = &line[line_pos] - cp; + + /* Output the buffer and a new line */ + z_new_line( ); + + /* If any text to wrap then move it to the start of the line */ + if ( right_len > 0 ) + { + memmove( line, cp, right_len ); + line_pos = right_len; + } + } + + if ( isprint( c ) ) + { + char_count--; + } + } + } + else + { + /* No formatting or output redirection, so just output the character */ + script_char( c ); + output_char( c ); + } + +} /* write_char */ + +/* + * z_set_text_style + * + * Set a video attribute. Write the video mode, from 0 to 8, incremented. + * This is so the output routines don't confuse video attribute 0 as the + * end of the string. + * + */ + +void z_set_text_style( zword_t mode ) +{ + if ( mode >= MIN_ATTRIBUTE && mode <= MAX_ATTRIBUTE ) + { + set_attribute( mode ); + } + else + { + fatal( "@set_text_style called with invalid mode." ); + } +} /* z_set_text_style */ + +/* + * write_string + * + * Output a string + * + */ + +void write_string( const char *s ) +{ + while ( *s ) + write_char( *s++ ); + +} /* write_string */ + +/* + * flush_buffer + * + * Send output buffer to the screen. + * + */ + +void flush_buffer( int flag ) +{ + /* Terminate the line */ + line[line_pos] = '\0'; + + /* Send the line buffer to the printer */ + script_string( line ); + flush_script( ); + + /* Send the line buffer to the screen */ + output_string( line ); + + /* Reset the character count only if a carriage return is expected */ + if ( flag == TRUE ) + { + char_count = screen_cols - right_margin; + } + + /* Reset the buffer pointer */ + line_pos = 0; + +} /* flush_buffer */ + +/* + * z_buffer_mode + * + * Set the format mode flag. Formatting disables writing into the output buffer. + * If set to 1, text output in the lower window on stream one is buffered so that + * it can be word-wrapped properly. If set to 0, it isn't. + * + */ + +void z_buffer_mode( zword_t flag ) +{ + /* Flush any current output */ + flush_buffer( FALSE ); + + /* Set formatting depending on the flag */ + if ( flag ) + formatting = ON; + else + formatting = OFF; + +} /* z_buffer_mode */ + +/* + * z_output_stream + * + * Set various printing modes. These can be: disabling output, scripting and + * redirecting output. Redirection is peculiar. I use it to format the status + * line for V1 to V3 games, otherwise it wasn't used. V4 games format the + * status line themselves in an internal buffer in the writeable data area. + * To use the normal text decoding routines they have to redirect output to + * the writeable data area. This is done by passing in a buffer pointer. + * The first word of the buffer will receive the number of characters + * written since the output was redirected. The remainder of the buffer + * will contain the redirected text. + * + */ + +typedef struct redirect_stash_struct +{ + zword_t count; + zword_t buffer; + zword_t pos; +} +redirect_stash_t; + +void z_output_stream( zword_t type, zword_t option ) +{ + static int redirect_size = 0; + static redirect_stash_t *stash = NULL; + + if ( ( ZINT16 ) type == 1 ) + { + /* Turn on text output */ + outputting = ON; + } + else if ( ( ZINT16 ) type == 2 ) + { + /* Turn on scripting */ + open_script( ); + } + else if ( ( ZINT16 ) type == 3 ) + { + /* Turn on output redirection */ + if ( redirect_depth == 0 ) + { + /* Disable text formatting during redirection */ + saved_formatting = formatting; + formatting = OFF; + + /* Enable text redirection */ + redirect_depth = 1; + } + else + { + if ( redirect_size == 0 ) + { + redirect_size = 4; + stash = ( redirect_stash_t * ) malloc( redirect_size * sizeof ( redirect_stash_t ) ); + } + if ( redirect_depth > redirect_size ) + { + redirect_size *= 2; + stash = ( redirect_stash_t * ) realloc( stash, redirect_size * sizeof ( redirect_stash_t ) ); + } + + if ( h_type < V4 ) + { + stash[redirect_depth - 1].pos = status_pos; + } + else + { + stash[redirect_depth - 1].pos = story_pos; + stash[redirect_depth - 1].buffer = story_buffer; + stash[redirect_depth - 1].count = story_count; + } + + redirect_depth++; + } + + /* Set up the redirection pointers */ + + if ( h_type < V4 ) + { + status_pos = 0; + } + else + { + story_count = 0; + story_buffer = option; + story_pos = option + 2; + } + + } + else if ( ( ZINT16 ) type == 4 ) + { + /* Turn on input recording */ + open_record( ); + } + else if ( ( ZINT16 ) type == -1 ) + { + /* Turn off text output */ + outputting = OFF; + } + else if ( ( ZINT16 ) type == -2 ) + { + /* Turn off scripting */ + close_script( ); + } + else if ( ( ZINT16 ) type == -3 ) + { + /* Turn off output redirection */ + if ( redirect_depth ) + { + if ( redirect_depth == 1 ) + { + /* Restore the format mode and turn off redirection */ + formatting = saved_formatting; + redirect_depth = 0; + + /* Terminate the redirection buffer and store the count of + * character in the buffer into the first word of the buffer */ + if ( h_type > V3 ) + { + set_word( story_buffer, story_count ); + } + } + else + { + if ( h_type > V3 ) + { + set_word( story_buffer, story_count ); + } + + redirect_depth--; + + if ( h_type < V4 ) + { + status_pos = stash[redirect_depth - 1].pos; + } + else + { + story_pos = stash[redirect_depth - 1].pos; + story_buffer = stash[redirect_depth - 1].buffer; + story_count = stash[redirect_depth - 1].count; + } + + } + } + + } + else if ( ( ZINT16 ) type == -4 ) + { + /* Turn off input recording */ + close_record( ); + } +} /* z_output_stream */ + +/* + * z_print_char + * + * Write a character. + * + */ + +void z_print_char( zword_t c ) +{ + write_zchar( ( char ) c ); +} /* z_print_char */ + +/* + * z_print_num + * + * Write a signed number. + * + */ + +void z_print_num( zword_t num ) +{ + int i, count; + char buffer[10]; + + i = ( ZINT16 ) num; + sprintf( buffer, "%d", i ); + count = strlen( buffer ); + for ( i = 0; i < count; i++ ) + write_char( buffer[i] ); + +} /* z_print_num */ + +/* + * z_print_paddr + * + * Print using a packed address. Packed addresses are used to save space and + * reference addresses outside of the data region. + * + */ + +void z_print_paddr( zword_t packed_address ) +{ + unsigned long address; + + /* Convert packed address to real address */ + address = ( unsigned long ) packed_address * story_scaler; + + /* Decode and output text at address */ + decode_text( &address ); + +} /* z_print_paddr */ + +/* + * z_print_addr + * + * Print using a real address. Real addresses are just offsets into the + * data region. + * + */ + +void z_print_addr( zword_t offset ) +{ + unsigned long address; + + address = offset; + + /* Decode and output text at address */ + decode_text( &address ); + +} /* z_print_addr */ + +/* + * z_print_obj + * + * Print an object description. Object descriptions are stored as ASCII + * strings at the front of the property list for the object. + * + */ + +void z_print_obj( zword_t obj ) +{ + zword_t offset; + unsigned long address; + + /* Check for NULL object */ + if ( obj == 0 ) + return; + + /* Calculate address of property list */ + offset = get_object_address( obj ); + offset += ( h_type < V4 ) ? O3_PROPERTY_OFFSET : O4_PROPERTY_OFFSET; + + /* Read the property list address and skip the count byte */ + address = ( unsigned long ) get_word( offset ) + 1; + + /* Decode and output text at address */ + decode_text( &address ); + +} /* z_print_obj */ + +/* + * z_print + * + * Print the string embedded in the instruction stream at this point. All + * strings that do not need to be referenced by address are embedded in the + * instruction stream. All strings that can be refered to by address are placed + * at the end of the code region and referenced by packed address. + * + */ + +void z_print( void ) +{ + + /* Decode and output text at PC */ + decode_text( &pc ); + +} /* z_print */ + +/* + * z_print_ret + * + * Print a string embedded in the instruction stream as with print_literal, + * except flush the output buffer and write a new line. After this return from + * the current subroutine with a status of true. + * + */ + +void z_print_ret( void ) +{ + + z_print( ); + z_new_line( ); + z_ret( TRUE ); + +} /* z_print_ret */ + +/* + * z_new_line + * + * Simply flush the current contents of the output buffer followed by a new + * line. + * + */ + +void z_new_line( void ) +{ + + /* Only flush buffer if story redirect is off */ + if ( redirect_depth == 0 ) + { + flush_buffer( TRUE ); + script_new_line( ); + output_new_line( ); + } + else + { + write_char( '\r' ); + } + +} /* z_new_line */ + +/* + * print_time + * + * Print the time as HH:MM [am|pm]. This is a bit language dependent and can + * quite easily be changed. If you change the size of the time string output + * then adjust the status line position in display_status_line. + * + */ + +void print_time( int hours, int minutes ) +{ + int pm_indicator; + + /* Remember if time is pm */ + pm_indicator = ( hours < 12 ) ? OFF : ON; + + /* Convert 24 hour clock to 12 hour clock */ + hours %= 12; + if ( hours == 0 ) + hours = 12; + + /* Write hour right justified */ + if ( hours < 10 ) + write_char( ' ' ); + z_print_num( (zword_t)hours ); + + /* Write hours/minutes separator */ + write_char( ':' ); + + /* Write minutes zero filled */ + if ( minutes < 10 ) + write_char( '0' ); + z_print_num( (zword_t)minutes ); + + /* Write the am or pm string */ + if ( pm_indicator == ON ) + write_string( " pm" ); + else + write_string( " am" ); + +} /* print_time */ + +/* + * z_encode + * + * Convert text to packed text. + * + */ + +void z_encode( zword_t word_addr, zword_t word_length, zword_t word_offset, zword_t dest_addr ) +{ + ZINT16 word[3]; + int i; + + /* Encode the word */ + + encode_text( word_length, ( const char * ) &datap[word_addr + word_offset], word ); + + /* Move the encoded word, byte swapped, into the destination buffer */ + + for ( i = 0; i < 3; i++, dest_addr += 2 ) + set_word( dest_addr, word[i] ); + +} /* z_encode */ diff --git a/variable.c b/variable.c new file mode 100644 index 0000000..2764e78 --- /dev/null +++ b/variable.c @@ -0,0 +1,126 @@ + +/* $Id: variable.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: variable.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: variable.c,v $ + * Revision 1.3 2000/07/05 15:20:34 jholder + * Updated code to remove warnings. + * + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * variable.c + * + * Variable manipulation routines + * + */ + +#include "ztypes.h" + +/* + * z_load + * + * Load and store a variable value on stack. + * + */ + +void z_load( zword_t variable ) +{ + store_operand( load_variable( variable ) ); +} /* load */ + +/* + * z_push + * + * Push a value onto the stack + * + */ + +void z_push( zword_t value ) +{ + stack[--sp] = value; +} /* push_var */ + +/* + * z_pull + * + * Pop a variable from the stack. + * + */ + +void z_pull( zword_t variable ) +{ + z_store( variable, stack[sp++] ); +} /* pop_var */ + +/* + * z_inc + * + * Increment a variable. + * + */ + +void z_inc( zword_t variable ) +{ + z_store( variable, (zword_t)(load_variable( variable ) + 1) ); +} /* increment */ + +/* + * z_dec + * + * Decrement a variable. + * + */ + +void z_dec( zword_t variable ) +{ + z_store( variable, (zword_t)(load_variable( variable ) - 1) ); +} /* decrement */ + +/* + * z_inc_chk + * + * Increment a variable and then check its value against a target. + * + */ + +void z_inc_chk( zword_t variable, zword_t target ) +{ + ZINT16 value; + + value = ( ZINT16 ) load_variable( variable ); + z_store( variable, ++value ); + conditional_jump( value > ( ZINT16 ) target ); +} /* increment_check */ + +/* + * z_dec_chk + * + * Decrement a variable and then check its value against a target. + * + */ + +void z_dec_chk( zword_t variable, zword_t target ) +{ + ZINT16 value; + + value = ( ZINT16 ) load_variable( variable ); + z_store( variable, --value ); + conditional_jump( value < ( ZINT16 ) target ); +} /* decrement_check */ diff --git a/ztypes.h b/ztypes.h new file mode 100644 index 0000000..81b4432 --- /dev/null +++ b/ztypes.h @@ -0,0 +1,799 @@ + +/* $Id: ztypes.h,v 1.4 2000/10/04 23:07:57 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: ztypes.h,v 1.4 2000/10/04 23:07:57 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: ztypes.h,v $ + * Revision 1.4 2000/10/04 23:07:57 jholder + * fixed redirect problem with isolatin1 range chars + * + * Revision 1.3 2000/07/05 15:20:34 jholder + * Updated code to remove warnings. + * + * Revision 1.2 2000/05/25 22:28:56 jholder + * changes routine names to reflect zmachine opcode names per spec 1.0 + * + * Revision 1.1.1.1 2000/05/10 14:21:34 jholder + * + * imported + * + * + * -------------------------------------------------------------------- + */ + +/* + * ztypes.h + * + * Any global stuff required by the C modules. + * + */ + +#if !defined(__ZTYPES_INCLUDED) +#define __ZTYPES_INCLUDED + +// IIgs stuff - everyone gets it +#include "joey.h" +#define LOUSY_RANDOM + +/* AIX likes to see this define... */ +#if defined(AIX) +#define _POSIX_SOURCE +#define POSIX +#endif + +/* for Turbo C & MSC */ +#if defined(__MSDOS__) +#ifndef MSDOS +#define MSDOS +#endif +#define LOUSY_RANDOM +#define Z_FILENAME_MAX FILENAME_MAX +#endif + + +#if defined OS2 +#define LOUSY_RANDOM +#define Z_FILENAME_MAX FILENAME_MAX +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(MSDOS) +#include +#endif /* MSDOS */ + +/* Set Version of JZIP */ + +#include "jzip.h" +extern unsigned char JTERP; + +/* Configuration options */ +#ifdef USE_ZLIB +#include +#define jz_rewind gzrewind +#define jz_seek gzseek +#define jz_open gzopen +#define jz_close gzclose +#define jz_getc gzgetc +#else +#define jz_rewind rewind +#define jz_seek fseek +#define jz_open fopen +#define jz_close fclose +#define jz_getc getc +#endif + +#define USE_QUETZAL + +#define DEFAULT_ROWS 25 /* Default screen height */ +#define DEFAULT_COLS 40 /* Deafult screen width */ + +#define DEFAULT_RIGHT_MARGIN 1 /* # of characters in rt margin (UNIX likes 1)*/ +#define DEFAULT_TOP_MARGIN 0 /* # of lines on screen before [MORE] message */ + +#ifdef LOUSY_RANDOM +#define RANDOM_FUNC rand +#define SRANDOM_FUNC srand +#else +#define RANDOM_FUNC random +#define SRANDOM_FUNC srandom +#endif + +/* Perform stricter z-code error checking. If STRICTZ is #defined, + * the interpreter will check for common opcode errors, such as reading + * or writing properties of the "nothing" (0) object. When such an + * error occurs, the opcode will call report_zstrict_error() and + * then continue in some safe manner. This may mean doing nothing, + * returning 0, or something like that. + * + * See osdepend.c for the definition of report_zstrict_error(). Note that + * this function may call fatal() to shut down the interpreter. + * + * If STRICTZ is not #defined, the STRICTZ patch has no effect at all. + * It does not even check to continue safely when an error occurs; + * it just behaves the way ZIP has always behaved. This typically + * means calling get_property_addr(0) or get_object_address(0), + * which will return a meaningless value, and continuing on with + * that. + */ +//#define STRICTZ + + +/* Global defines */ + +#ifndef UNUSEDVAR +#define UNUSEDVAR(a) a=a; +#endif + +/* number of bits in a byte. needed by AIX!!! ;^) */ +#ifndef NBBY +#define NBBY 8 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef Z_FILENAME_MAX +#define Z_FILENAME_MAX 255 +#endif + +#ifndef Z_PATHNAME_MAX +#define Z_PATHNAME_MAX 1024 +#endif + +#ifndef EXIT_SUCCESS +#define EXIT_SUCCESS 0 +#endif + +#ifndef EXIT_FAILURE +#define EXIT_FAILURE 1 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifdef unix + +#if defined (HAVE_BCOPY) +#define memmove(a, b, c) bcopy (b, a, c) +#else +#define memmove(a, b, c) memcpy ((a), (b), (c)) +#endif + +#ifndef const +#define const +#endif + +#endif /* unix */ + + +/* Z types */ + +typedef unsigned char zbyte_t; /* unsigned 1 byte quantity */ +typedef unsigned short zword_t; /* unsigned 2 byte quantity */ +typedef short ZINT16; /* signed 2 byte quantity */ + +/* Data file header format */ + +typedef struct zheader +{ + zbyte_t type; + zbyte_t config; + zword_t version; + zword_t data_size; + zword_t start_pc; + zword_t words_offset; + zword_t objects_offset; + zword_t globals_offset; + zword_t restart_size; + zword_t flags; + zbyte_t release_date[6]; + zword_t synonyms_offset; + zword_t file_size; + zword_t checksum; + zbyte_t interpreter; + zbyte_t interpreter_version; + zbyte_t screen_rows; + zbyte_t screen_columns; + zbyte_t screen_left; + zbyte_t screen_right; + zbyte_t screen_top; + zbyte_t screen_bottom; + zbyte_t max_char_width; + zbyte_t max_char_height; + zword_t filler1[3]; + zword_t function_keys_offset; + zword_t filler2[2]; + zword_t alternate_alphabet_offset; + zword_t mouse_position_offset; + zword_t filler3[4]; +} +zheader_t; + +#define H_TYPE 0 +#define H_CONFIG 1 + +#define CONFIG_BYTE_SWAPPED 0x01 /* Game data is byte swapped - V3 */ +#define CONFIG_TIME 0x02 /* Status line displays time - V3 */ +#define CONFIG_MAX_DATA 0x04 /* Data area should 64K if possible - V3 */ +#define CONFIG_TANDY 0x08 /* Tandy licensed game - V3 */ +#define CONFIG_NOSTATUSLINE 0x10 /* Interp can't support a status line - V3 */ +#define CONFIG_WINDOWS 0x20 /* Interpr supports split screen mode - V3 */ + +#define CONFIG_COLOUR 0x01 /* Game supports colour - V5+ */ +#define CONFIG_PICTURES 0x02 /* Picture displaying available? - V4+ */ +#define CONFIG_BOLDFACE 0x04 /* Interpr supports boldface style - V4+ */ +#define CONFIG_EMPHASIS 0x08 /* Interpreter supports text emphasis - V4+ */ +#define CONFIG_FIXED 0x10 /* Interpr supports fixed width style - V4+ */ +#define CONFIG_SFX 0x20 /* Interpr supports sound effects - V4+ */ +#define CONFIG_TIMEDINPUT 0x80 /* Interpr supports timed input - V4+ */ + +#define CONFIG_PROPORTIONAL 0x40 /* Interpr uses proportional font - V3+ */ + + +#define H_VERSION 2 +#define H_DATA_SIZE 4 +#define H_START_PC 6 +#define H_WORDS_OFFSET 8 +#define H_OBJECTS_OFFSET 10 +#define H_GLOBALS_OFFSET 12 +#define H_RESTART_SIZE 14 +#define H_FLAGS 16 + +#define SCRIPTING_FLAG 0x01 +#define FIXED_FONT_FLAG 0x02 +#define REFRESH_FLAG 0x04 +#define GRAPHICS_FLAG 0x08 +#define SOUND_FLAG 0x10 /* V4 */ +#define UNDO_AVAILABLE_FLAG 0x10 /* V5 */ +#define COLOUR_FLAG 0x40 +#define NEW_SOUND_FLAG 0x80 + +#define H_RELEASE_DATE 18 +#define H_SYNONYMS_OFFSET 24 +#define H_FILE_SIZE 26 +#define H_CHECKSUM 28 +#define H_INTERPRETER 30 +#define H_UNICODE_TABLE 34 + +#define INTERP_GENERIC 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 +#define INTERP_UNIX 12 +#define INTERP_VMS 13 + +#define H_INTERPRETER_VERSION 31 +#define H_SCREEN_ROWS 32 +#define H_SCREEN_COLUMNS 33 +#define H_SCREEN_LEFT 34 +#define H_SCREEN_RIGHT 35 +#define H_SCREEN_TOP 36 +#define H_SCREEN_BOTTOM 37 +#define H_MAX_CHAR_WIDTH 38 +#define H_MAX_CHAR_HEIGHT 39 +#define H_FILLER1 40 + +#define H_FUNCTION_KEYS_OFFSET 46 +#define H_FILLER2 48 + +#define H_STANDARD_HIGH 50 +#define H_STANDARD_LOW 51 + +#define H_ALTERNATE_ALPHABET_OFFSET 52 +#define H_MOUSE_POSITION_OFFSET 54 +#define H_FILLER3 56 + +#define V1 1 + +#define V2 2 + +/* Version 3 object format */ + +#define V3 3 + +typedef struct zobjectv3 +{ + zword_t attributes[2]; + zbyte_t parent; + zbyte_t next; + zbyte_t child; + zword_t property_offset; +} +zobjectv3_t; + +#define O3_ATTRIBUTES 0 +#define O3_PARENT 4 +#define O3_NEXT 5 +#define O3_CHILD 6 +#define O3_PROPERTY_OFFSET 7 + +#define O3_SIZE 9 + +#define PARENT3(offset) (offset + O3_PARENT) +#define NEXT3(offset) (offset + O3_NEXT) +#define CHILD3(offset) (offset + O3_CHILD) + +#define P3_MAX_PROPERTIES 0x20 + +/* Version 4 object format */ + +#define V4 4 + +typedef struct zobjectv4 +{ + zword_t attributes[3]; + zword_t parent; + zword_t next; + zword_t child; + zword_t property_offset; +} +zobjectv4_t; + +#define O4_ATTRIBUTES 0 +#define O4_PARENT 6 +#define O4_NEXT 8 +#define O4_CHILD 10 +#define O4_PROPERTY_OFFSET 12 + +#define O4_SIZE 14 + +#define PARENT4(offset) (offset + O4_PARENT) +#define NEXT4(offset) (offset + O4_NEXT) +#define CHILD4(offset) (offset + O4_CHILD) + +#define P4_MAX_PROPERTIES 0x40 + +#define V5 5 +#define V6 6 +#define V7 7 +#define V8 8 + +/* Interpreter states */ + +#define STOP 0 +#define RUN 1 + +/* Call types */ + +#define FUNCTION 0x0000 + +#if defined(USE_QUETZAL) +#define PROCEDURE 0x1000 +#define ASYNC 0x2000 +#else +#define PROCEDURE 0x0100 +#define ASYNC 0x0200 +#endif + +#if defined(USE_QUETZAL) +#define ARGS_MASK 0x00FF +#define VARS_MASK 0x0F00 +#define TYPE_MASK 0xF000 +#define VAR_SHIFT 8 +#else +#define ARGS_MASK 0x00ff +#define TYPE_MASK 0xff00 +#endif + +/* Local defines */ + +#define PAGE_SIZE 0x200 +#define PAGE_MASK 0x1FF +#define PAGE_SHIFT 9 + +#define STACK_SIZE 1024 + +#define ON 1 +#define OFF 0 +#define RESET -1 + +#define Z_SCREEN 255 +#define TEXT_WINDOW 0 +#define STATUS_WINDOW 1 + +#define MIN_ATTRIBUTE 0 +#define NORMAL 0 +#define REVERSE 1 +#define BOLD 2 +#define EMPHASIS 4 +#define FIXED_FONT 8 +#define MAX_ATTRIBUTE 8 + +#define TEXT_FONT 1 +#define GRAPHICS_FONT 3 + +#define FOREGROUND 0 +#define BACKGROUND 1 + +#define GAME_RESTORE 0 +#define GAME_SAVE 1 +#define GAME_SCRIPT 2 +#define GAME_RECORD 3 +#define GAME_PLAYBACK 4 +#define UNDO_SAVE 5 +#define UNDO_RESTORE 6 +#define GAME_SAVE_AUX 7 +#define GAME_LOAD_AUX 8 + +#define MAX_TEXT_SIZE 8 + +/* Data access macros */ + +#define get_byte(offset) ((zbyte_t) datap[offset]) +#define get_word(offset) ((zword_t) (((zword_t) datap[offset] << 8) + (zword_t) datap[offset + 1])) +#define set_byte(offset,value) datap[offset] = (zbyte_t) (value) +#define set_word(offset,value) datap[offset] = (zbyte_t) ((zword_t) (value) >> 8), datap[offset + 1] = (zbyte_t) ((zword_t) (value) & 0xff) + +/* External data */ + +extern int GLOBALVER; +extern zbyte_t h_type; +extern zbyte_t h_config; +extern zword_t h_version; +extern zword_t h_data_size; +extern zword_t h_start_pc; +extern zword_t h_words_offset; +extern zword_t h_objects_offset; +extern zword_t h_globals_offset; +extern zword_t h_restart_size; +extern zword_t h_flags; +extern zword_t h_synonyms_offset; +extern zword_t h_file_size; +extern zword_t h_checksum; +extern zbyte_t h_interpreter; +extern zbyte_t h_interpreter_version; +extern zword_t h_alternate_alphabet_offset; +extern zword_t h_unicode_table; + +extern int story_scaler; +extern int story_shift; +extern int property_mask; +extern int property_size_mask; + +extern zword_t stack[STACK_SIZE]; +extern zword_t sp; +extern zword_t fp; +extern zword_t frame_count; +extern unsigned long pc; +extern int interpreter_state; +extern int interpreter_status; + +extern unsigned int data_size; +extern zbyte_t *datap; +extern zbyte_t *undo_datap; + +extern int screen_rows; +extern int screen_cols; +extern int right_margin; +extern int top_margin; + +extern int screen_window; +extern int interp_initialized; + +extern int formatting; +extern int outputting; +extern int redirect_depth; +extern int redirecting; +extern int scripting; +extern int scripting_disable; +extern int recording; +extern int replaying; +extern int font; + +extern int status_active; +extern int status_size; + +extern char fTandy; +extern char fIBMGraphics; + +extern int lines_written; +extern int status_pos; + +extern char *line; +extern char *status_line; + +extern char lookup_table[3][26]; + +extern char monochrome; +extern int hist_buf_size; +extern char bigscreen; + +extern unsigned char zscii2latin1[69]; + +#ifdef STRICTZ + +/* Definitions for STRICTZ functions and error codes. */ + +void report_strictz_error( int, const char * ); + +/* Error codes */ +#define STRZERR_NO_ERROR (0) +#define STRZERR_JIN (1) +#define STRZERR_GET_CHILD (2) +#define STRZERR_GET_PARENT (3) +#define STRZERR_GET_SIBLING (4) +#define STRZERR_GET_PROP_ADDR (5) +#define STRZERR_GET_PROP (6) +#define STRZERR_PUT_PROP (7) +#define STRZERR_CLEAR_ATTR (8) +#define STRZERR_SET_ATTR (9) +#define STRZERR_TEST_ATTR (10) +#define STRZERR_MOVE_OBJECT (11) +#define STRZERR_MOVE_OBJECT_2 (12) +#define STRZERR_REMOVE_OBJECT (13) +#define STRZERR_GET_NEXT_PROP (14) +#define STRZERR_DIV_ZERO (15) +#define STRZERR_MOV_CURSOR (16) +#define STRICTZ_NUM_ERRORS (17) + +#endif /* STRICTZ */ + +/* Global routines */ + +/* control.c */ + +void z_check_arg_count( zword_t ); +int z_call( int, zword_t *, int ); +void z_jump( zword_t ); +void z_restart( void ); +void restart_interp( int ); +void z_ret( zword_t ); +void z_catch( void ); +void z_throw( zword_t, zword_t ); + + +/* fileio.c */ + +void z_verify( void ); +int z_restore( int, zword_t, zword_t, zword_t ); +int z_save( int, zword_t, zword_t, zword_t ); +void z_restore_undo( void ); +void z_save_undo( void ); +void z_open_playback( int ); +void close_record( void ); +void close_script( void ); +void close_story( void ); +void flush_script( void ); +unsigned int get_story_size( void ); +void open_record( void ); +void open_script( void ); +void open_story( const char * ); +int playback_key( void ); +int playback_line( int, char *, int * ); +void read_page( int, void * ); +void record_key( int ); +void record_line( const char * ); +void script_char( int ); +void script_string( const char * ); +void script_line( const char * ); +void script_new_line( void ); + + +/* getopt.c */ + +#ifndef HAVE_GETOPT +int getopt( int, char *[], const char * ); +#endif + + +/* input.c */ + +int get_line( char *, zword_t, zword_t ); +void z_read_char( int, zword_t * ); +void z_sread_aread( int, zword_t * ); +void z_tokenise( int, zword_t * ); + + +/* interpre.c */ + +int interpret( void ); + + +/* license.c */ + +void print_license( void ); + + +/* math.c */ + +void z_add( zword_t, zword_t ); +void z_div( zword_t, zword_t ); +void z_mul( zword_t, zword_t ); +void z_sub( zword_t, zword_t ); +void z_mod( zword_t, zword_t ); +void z_or( zword_t, zword_t ); +void z_and( zword_t, zword_t ); +void z_not( zword_t ); +void z_art_shift( zword_t, zword_t ); +void z_log_shift( zword_t, zword_t ); +void z_je( int, zword_t * ); +void z_jg( zword_t, zword_t ); +void z_jl( zword_t, zword_t ); +void z_jz( zword_t ); +void z_random( zword_t ); +void z_test( zword_t, zword_t ); + + +/* memory.c */ + +void load_cache( void ); +void unload_cache( void ); +zbyte_t read_code_byte( void ); +zbyte_t read_data_byte( unsigned long * ); +zword_t read_code_word( void ); +zword_t read_data_word( unsigned long * ); + + +/* object.c */ + +zword_t get_object_address( zword_t ); +void z_insert_obj( zword_t, zword_t ); +void z_remove_obj( zword_t ); +void z_get_child( zword_t ); +void z_get_sibling( zword_t ); +void z_get_parent( zword_t ); +void z_jin( zword_t, zword_t ); +void z_clear_attr( zword_t, zword_t ); +void z_set_attr( zword_t, zword_t ); +void z_test_attr( zword_t, zword_t ); + + +/* operand.c */ + +void z_piracy( int ); +void z_store( int, zword_t ); +void conditional_jump( int ); +void store_operand( zword_t ); +zword_t load_operand( int ); +zword_t load_variable( int ); + + +/* osdepend.c */ + +int codes_to_text( int, char * ); +void fatal( const char * ); +void file_cleanup( const char *, int ); +int fit_line( const char *, int, int ); +int get_file_name( char *, char *, int ); +int print_status( int, char *[] ); +//void process_arguments( int, char *[] ); +void set_colours( zword_t, zword_t ); +void set_font( int ); +void sound( int, zword_t * ); + + +/* property.c */ + +void z_get_next_prop( zword_t, zword_t ); +void z_get_prop( zword_t, zword_t ); +void z_get_prop_addr( zword_t, zword_t ); +void z_get_prop_len( zword_t ); +void z_put_prop( zword_t, zword_t, zword_t ); +void z_copy_table( zword_t, zword_t, zword_t ); +void z_scan_table( int, zword_t * ); +void z_loadb( zword_t, zword_t ); +void z_loadw( zword_t, zword_t ); +void z_storeb( zword_t, zword_t, zword_t ); +void z_storew( zword_t, zword_t, zword_t ); + + +/* quetzal.c */ + +#ifdef USE_ZLIB +int save_quetzal( FILE *, gzFile * ); +int restore_quetzal( FILE *, gzFile * ); +#else +int save_quetzal( FILE *, FILE * ); +int restore_quetzal( FILE *, FILE * ); +#endif + + +/* screen.c */ + +void z_show_status( void ); +void z_set_cursor( zword_t, zword_t ); +void z_set_font( zword_t ); +void z_split_window( zword_t ); +void z_set_window( zword_t ); +void z_set_colour( zword_t, zword_t ); +void z_erase_line( zword_t ); +void z_erase_window( zword_t ); +void z_print_table( int, zword_t * ); +void blank_status_line( void ); +void output_char( int ); +void output_new_line( void ); +void output_string( const char * ); +void output_line( const char * ); + + +/* screenio.c */ + +int input_character( int ); +void clear_line( void ); +void clear_screen( void ); +void clear_status_window( void ); +void clear_text_window( void ); +void create_status_window( void ); +void delete_status_window( void ); +void display_char( int ); +int fit_line( const char *, int, int ); +void get_cursor_position( int *, int * ); +void initialize_screen( void ); +int input_line( int, char *, int, int * ); +void move_cursor( int, int ); +int print_status( int, char *[] ); +void reset_screen( void ); +void restart_screen( void ); +void restore_cursor_position( void ); +void save_cursor_position( void ); +void scroll_line( void ); +void select_status_window( void ); +void select_text_window( void ); +void set_attribute( int ); + + +/* text.c */ + +void z_encode( zword_t, zword_t, zword_t, zword_t ); +void z_new_line( void ); +void z_print_char( zword_t ); +void z_print_num( zword_t ); +void z_print( void ); +void z_print_addr( zword_t ); +void z_print_paddr( zword_t ); +void z_print_obj( zword_t ); +void z_print_ret( void ); +void z_buffer_mode( zword_t ); +void z_output_stream( zword_t, zword_t ); +void z_input_stream( int ); +void z_set_text_style( zword_t ); +void decode_text( unsigned long * ); +void encode_text( int, const char *, ZINT16 * ); +void flush_buffer( int ); +void print_time( int, int ); +void write_char( int ); +void write_string( const char * ); +void write_zchar( int ); + + +/* variable.c */ + +void z_inc( zword_t ); +void z_dec( zword_t ); +void z_inc_chk( zword_t, zword_t ); +void z_dec_chk( zword_t, zword_t ); +void z_load( zword_t ); +void z_pull( zword_t ); +void z_push( zword_t ); + +#endif /* !defined(__ZTYPES_INCLUDED) */