commit c4e3fb793915f93d384d4597bfb496f66119c7ff Author: Scott Duensing Date: Sat Sep 8 19:51:49 2018 -0500 Initial commit. 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 0000000..331d8da Binary files /dev/null and b/8x8thin.sta differ 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 0000000..474c9d9 Binary files /dev/null and b/gamedata.z5 differ diff --git a/ifengine.pro b/ifengine.pro new file mode 100644 index 0000000..d5bad2b --- /dev/null +++ b/ifengine.pro @@ -0,0 +1,75 @@ +TEMPLATE = app +CONFIG += console +CONFIG -= \ + app_bundle \ + qt + +DESTDIR = $$OUT_PWD/build + +JOEY = /home/scott/joey + +JOEY_INCLUDES = \ + $$JOEY/dist + +JOEY_HEADERS = \ + $$JOEY/dist/joey.h + +JOEY_LIBS = \ + -L$$JOEY/dist/linux/x64 \ + -Wl,-rpath,$$JOEY/dist/linux/x64 \ + -Wl,--enable-new-dtags \ + -l:joeylib.a \ + -Wl,--no-undefined \ + -ldl \ + -lsndio \ + -lpthread \ + -lrt \ + -lm + +JOEY_FLAGS = \ + -pthread \ + -D_REENTRANT + +ZIP_FLAGS = \ + -DANSI_COLOR \ + -DUNIX \ + -DSCREEN_WIDTH=40 \ + -DSCREEN_HEIGHT=25 \ + +QMAKE_CFLAGS += \ + $$JOEY_FLAGS + +INCLUDEPATH += \ + $$JOEY_INCLUDES + +HEADERS += \ + $$JOEY_HEADERS \ + ansiterm.h \ + jzip.h \ + ztypes.h + +SOURCES += \ + main.c \ + ansiterm.c \ + control.c \ + extern.c \ + fileio.c \ + input.c \ + interpre.c \ + math.c \ + memory.c \ + object.c \ + operand.c \ + osdepend.c \ + property.c \ + quetzal.c \ + screen.c \ + text.c \ + variable.c \ + joeyio.c + +LIBS += \ + $$JOEY_LIBS \ + +DISTFILES += \ + build-IIgs.sh diff --git a/input.c b/input.c new file mode 100644 index 0000000..2d494e9 --- /dev/null +++ b/input.c @@ -0,0 +1,606 @@ + +/* $Id: input.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * -------------------------------------------------------------------- + * see doc/License.txt for License Information + * -------------------------------------------------------------------- + * + * File name: $Id: input.c,v 1.3 2000/07/05 15:20:34 jholder Exp $ + * + * Description: + * + * Modification history: + * $Log: input.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 + * + * + * -------------------------------------------------------------------- + */ + +/* + * input.c + * + * Input routines + * + */ + +#include "ztypes.h" + +/* Statically defined word separator list */ + +static const char *separators = " \t\n\f.,?"; +static zword_t dictionary_offset = 0; +static ZINT16 dictionary_size = 0; +static unsigned int entry_size = 0; + +static void tokenise_line( zword_t, zword_t, zword_t, zword_t ); +static const char *next_token( const char *, const char *, const char **, int *, const char * ); +static zword_t find_word( int, const char *, long ); + +/* + * z_read_char + * + * Read one character with optional timeout + * + * argv[0] = input device (must be 1) + * argv[1] = timeout value in tenths of a second (optional) + * argv[2] = timeout action routine (optional) + * + */ + +void z_read_char( int argc, zword_t * argv ) +{ + int c; + zword_t arg_list[2]; + + /* Supply default parameters */ + + if ( argc < 3 ) + argv[2] = 0; + if ( argc < 2 ) + argv[1] = 0; + + /* Flush any buffered output before read */ + + flush_buffer( FALSE ); + + /* Reset line count */ + + lines_written = 0; + + /* If more than one characters was asked for then fail the call */ + + if ( argv[0] != 1 ) + + c = 0; + + else + { + + if ( ( c = playback_key( ) ) == -1 ) + { + + /* Setup the timeout routine argument list */ + + arg_list[0] = argv[2]; + arg_list[1] = 0; /* as per spec 1.0 */ + /* was: arg_list[1] = argv[1]/10; */ + + /* Read a character 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 a character again */ + + do + { + flush_buffer( FALSE ); + c = input_character( ( int ) argv[1] ); + } + while ( c == -1 && z_call( 1, arg_list, ASYNC ) == 0 ); + + /* Fail call if input timed out */ + + if ( c == -1 ) + c = 0; + else + record_key( c ); + } + } + + store_operand( (zword_t)c ); + +} /* z_read_char */ + +/* + * z_sread_aread + * + * Read a line of input with optional timeout. + * + * argv[0] = character buffer address + * argv[1] = token buffer address + * argv[2] = timeout value in seconds (optional) + * argv[3] = timeout action routine (optional) + * + */ + +void z_sread_aread( int argc, zword_t * argv ) +{ + int i, in_size, out_size, terminator; + char *cbuf, *buffer; + + /* Supply default parameters */ + + if ( argc < 4 ) + argv[3] = 0; + if ( argc < 3 ) + argv[2] = 0; + if ( argc < 2 ) + argv[1] = 0; + + /* Refresh status line */ + + if ( h_type < V4 ) + z_show_status( ); + + /* Flush any buffered output before read */ + + flush_buffer( TRUE ); + + /* Reset line count */ + + lines_written = 0; + + /* Initialise character pointer and initial read size */ + + cbuf = ( char * ) &datap[argv[0]]; + in_size = ( h_type > 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) */