From 7f28c94eda576e3f972fc05468188986f2e45885 Mon Sep 17 00:00:00 2001 From: Torne Wuff Date: Sun, 17 Jan 2010 22:15:13 +0000 Subject: [PATCH] New plugin: frotz, a Z-machine interpreter, for playing interactive fiction. The interpreter more or less passes all the tests in the z-machine test suite. It should build for every target except Archos (for which it is disabled). git-svn-id: svn://svn.rockbox.org/rockbox/trunk@24267 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/CATEGORIES | 1 + apps/plugins/SUBDIRS | 4 + apps/plugins/frotz/SOURCES | 22 + apps/plugins/frotz/STATUS | 45 + apps/plugins/frotz/buffer.c | 152 +++ apps/plugins/frotz/dumb_frotz.h | 19 + apps/plugins/frotz/dumb_init.c | 86 ++ apps/plugins/frotz/dumb_output.c | 256 +++++ apps/plugins/frotz/err.c | 154 +++ apps/plugins/frotz/fastmem.c | 1013 +++++++++++++++++ apps/plugins/frotz/files.c | 563 ++++++++++ apps/plugins/frotz/frotz.c | 314 ++++++ apps/plugins/frotz/frotz.h | 583 ++++++++++ apps/plugins/frotz/frotz.make | 21 + apps/plugins/frotz/frotzplugin.h | 56 + apps/plugins/frotz/hotkey.c | 221 ++++ apps/plugins/frotz/input.c | 301 ++++++ apps/plugins/frotz/main.c | 205 ++++ apps/plugins/frotz/math.c | 261 +++++ apps/plugins/frotz/object.c | 1003 +++++++++++++++++ apps/plugins/frotz/process.c | 798 ++++++++++++++ apps/plugins/frotz/quetzal.c | 541 ++++++++++ apps/plugins/frotz/random.c | 82 ++ apps/plugins/frotz/redirect.c | 172 +++ apps/plugins/frotz/screen.c | 1743 ++++++++++++++++++++++++++++++ apps/plugins/frotz/setup.h | 67 ++ apps/plugins/frotz/sound.c | 204 ++++ apps/plugins/frotz/stream.c | 365 +++++++ apps/plugins/frotz/table.c | 193 ++++ apps/plugins/frotz/text.c | 1109 +++++++++++++++++++ apps/plugins/frotz/variable.c | 304 ++++++ apps/plugins/viewers.config | 8 + manual/plugins/frotz.tex | 67 ++ manual/plugins/main.tex | 3 + 34 files changed, 10936 insertions(+) create mode 100644 apps/plugins/frotz/SOURCES create mode 100644 apps/plugins/frotz/STATUS create mode 100644 apps/plugins/frotz/buffer.c create mode 100644 apps/plugins/frotz/dumb_frotz.h create mode 100644 apps/plugins/frotz/dumb_init.c create mode 100644 apps/plugins/frotz/dumb_output.c create mode 100644 apps/plugins/frotz/err.c create mode 100644 apps/plugins/frotz/fastmem.c create mode 100644 apps/plugins/frotz/files.c create mode 100644 apps/plugins/frotz/frotz.c create mode 100644 apps/plugins/frotz/frotz.h create mode 100644 apps/plugins/frotz/frotz.make create mode 100644 apps/plugins/frotz/frotzplugin.h create mode 100644 apps/plugins/frotz/hotkey.c create mode 100644 apps/plugins/frotz/input.c create mode 100644 apps/plugins/frotz/main.c create mode 100644 apps/plugins/frotz/math.c create mode 100644 apps/plugins/frotz/object.c create mode 100644 apps/plugins/frotz/process.c create mode 100644 apps/plugins/frotz/quetzal.c create mode 100644 apps/plugins/frotz/random.c create mode 100644 apps/plugins/frotz/redirect.c create mode 100644 apps/plugins/frotz/screen.c create mode 100644 apps/plugins/frotz/setup.h create mode 100644 apps/plugins/frotz/sound.c create mode 100644 apps/plugins/frotz/stream.c create mode 100644 apps/plugins/frotz/table.c create mode 100644 apps/plugins/frotz/text.c create mode 100644 apps/plugins/frotz/variable.c create mode 100644 manual/plugins/frotz.tex diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index a41514f93d..cf6e2663fa 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -28,6 +28,7 @@ fire,demos fireworks,demos firmware_flash,apps flipit,games +frotz,viewers goban,games greyscale,demos helloworld,demos diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS index 0993e8221c..5cb9356317 100644 --- a/apps/plugins/SUBDIRS +++ b/apps/plugins/SUBDIRS @@ -25,6 +25,10 @@ jpeg sudoku reversi goban +/* setjmp/longjmp are not implemented on sh */ +#if (CONFIG_CPU != SH7034) +frotz +#endif #ifdef HAVE_LCD_COLOR png #endif diff --git a/apps/plugins/frotz/SOURCES b/apps/plugins/frotz/SOURCES new file mode 100644 index 0000000000..6a73e0c602 --- /dev/null +++ b/apps/plugins/frotz/SOURCES @@ -0,0 +1,22 @@ +frotz.c +buffer.c +err.c +fastmem.c +files.c +hotkey.c +input.c +main.c +math.c +object.c +process.c +quetzal.c +random.c +redirect.c +screen.c +sound.c +stream.c +table.c +text.c +variable.c +dumb_init.c +dumb_output.c diff --git a/apps/plugins/frotz/STATUS b/apps/plugins/frotz/STATUS new file mode 100644 index 0000000000..3aa0f673ee --- /dev/null +++ b/apps/plugins/frotz/STATUS @@ -0,0 +1,45 @@ +frotz is quite portable and is divided into 'common' and os-specific files. +The common files are included here with minimal modifications. For the +os-specific files I have started with dumbfrotz, the port intended for a plain +C stdio system - it has its own screen buffering which is needed for rockbox. + +Things that work +---------------- + +Games, mostly! If the game is too large to fit in the plugin buffer it will +stop playback and steal the audio buffer. + +Saving and restoring (/path/to/story.sav, no filename selection). + +Transcripts, command records and replays (likewise). + +Undo, up to the limit of available memory (the rest of the plugin buffer if +the game fit there, or the rest of the audio buffer if not). + +Timed input, though it resets the timer when you enter the menu and only +counts until you enter the keyboard. + +Input line preloading, though the actual displayed line after editing looks +wrong. + +Things that don't work because I've not implemented it +------------------------------------------------------ + +Reading buttons that don't appear on the rockbox keyboard. + +Audible beeps (just a splash for now). + +Setting the random seed. + +Things which don't work in the original frotz anyway +---------------------------------------------------- + +Mouse and menus. + +Pictures. + +Non-beep sound samples. + +Unicode. + +Colours. diff --git a/apps/plugins/frotz/buffer.c b/apps/plugins/frotz/buffer.c new file mode 100644 index 0000000000..298ac69c20 --- /dev/null +++ b/apps/plugins/frotz/buffer.c @@ -0,0 +1,152 @@ +/* buffer.c - Text buffering and word wrapping + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +extern void stream_char (zchar); +extern void stream_word (const zchar *); +extern void stream_new_line (void); + +static zchar buffer[TEXT_BUFFER_SIZE]; +static int bufpos = 0; + +static zchar prev_c = 0; + +/* + * flush_buffer + * + * Copy the contents of the text buffer to the output streams. + * + */ + +void flush_buffer (void) +{ + static bool locked = FALSE; + + /* Make sure we stop when flush_buffer is called from flush_buffer. + Note that this is difficult to avoid as we might print a newline + during flush_buffer, which might cause a newline interrupt, that + might execute any arbitrary opcode, which might flush the buffer. */ + + if (locked || bufpos == 0) + return; + + /* Send the buffer to the output streams */ + + buffer[bufpos] = 0; + + + locked = TRUE; + + stream_word (buffer); + +#ifdef SPEECH_OUTPUT + os_speech_output(buffer); +#endif + + locked = FALSE; + + /* Reset the buffer */ + + bufpos = 0; + prev_c = 0; + +}/* flush_buffer */ + +/* + * print_char + * + * High level output function. + * + */ + +void print_char (zchar c) +{ + static bool flag = FALSE; + + if (message || ostream_memory || enable_buffering) { + + if (!flag) { + + /* Characters 0 and ZC_RETURN are special cases */ + + if (c == ZC_RETURN) + { new_line (); return; } + if (c == 0) + return; + + /* Flush the buffer before a whitespace or after a hyphen */ + + if (c == ' ' || c == ZC_INDENT || c == ZC_GAP || (prev_c == '-' && c != '-')) + + + flush_buffer (); + + /* Set the flag if this is part one of a style or font change */ + + if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) + flag = TRUE; + + /* Remember the current character code */ + + prev_c = c; + + } else flag = FALSE; + + /* Insert the character into the buffer */ + + buffer[bufpos++] = c; + + if (bufpos == TEXT_BUFFER_SIZE) + runtime_error (ERR_TEXT_BUF_OVF); + + } else stream_char (c); + +}/* print_char */ + +/* + * new_line + * + * High level newline function. + * + */ + +void new_line (void) +{ + + flush_buffer (); stream_new_line (); + +}/* new_line */ + + +/* + * init_buffer + * + * Initialize buffer variables. + * + */ + +void init_buffer(void) +{ + memset(buffer, 0, sizeof (zchar) * TEXT_BUFFER_SIZE); + bufpos = 0; + prev_c = 0; +} + diff --git a/apps/plugins/frotz/dumb_frotz.h b/apps/plugins/frotz/dumb_frotz.h new file mode 100644 index 0000000000..0b8415e4ae --- /dev/null +++ b/apps/plugins/frotz/dumb_frotz.h @@ -0,0 +1,19 @@ +/* dumb-frotz.h + * $Id: dumb-frotz.h,v 1.1.1.1 2002/03/26 22:38:34 feedle Exp $ + * Frotz os functions for a standard C library and a dumb terminal. + * Now you can finally play Zork Zero on your Teletype. + * + * Copyright 1997, 1998 Alembic Petrofsky . + * Any use permitted provided this notice stays intact. + * + * Changes for Rockbox copyright 2009 Torne Wuff + */ +#include "frotz.h" + +/* from ../common/setup.h */ +extern f_setup_t f_setup; + +/* dumb-output.c */ +void dumb_init_output(void); +void dumb_show_screen(bool show_cursor); +void dumb_dump_screen(void); diff --git a/apps/plugins/frotz/dumb_init.c b/apps/plugins/frotz/dumb_init.c new file mode 100644 index 0000000000..ea08447c0c --- /dev/null +++ b/apps/plugins/frotz/dumb_init.c @@ -0,0 +1,86 @@ +/* dumb-init.c + * $Id: dumb-init.c,v 1.1.1.1 2002/03/26 22:38:34 feedle Exp $ + * + * Copyright 1997,1998 Alva Petrofsky . + * Any use permitted provided this notice stays intact. + * + * Changes for Rockbox copyright 2009 Torne Wuff + */ + +#include "dumb_frotz.h" +#include "lib/pluginlib_exit.h" + +static int user_screen_width = LCD_WIDTH / SYSFONT_WIDTH; +static int user_screen_height = LCD_HEIGHT / SYSFONT_HEIGHT; +static int user_interpreter_number = -1; +static int user_random_seed = -1; +static int user_tandy_bit = 0; + + +void os_init_screen(void) +{ + if (h_version == V3 && user_tandy_bit) + h_config |= CONFIG_TANDY; + + if (h_version >= V5 && f_setup.undo_slots == 0) + h_flags &= ~UNDO_FLAG; + + h_screen_rows = user_screen_height; + h_screen_cols = user_screen_width; + + if (user_interpreter_number > 0) + h_interpreter_number = user_interpreter_number; + else { + /* Use ms-dos for v6 (because that's what most people have the + * graphics files for), but don't use it for v5 (or Beyond Zork + * will try to use funky characters). */ + h_interpreter_number = h_version == 6 ? INTERP_MSDOS : INTERP_DEC_20; + } + h_interpreter_version = 'F'; + + if (h_version >= V4) + h_config |= CONFIG_TIMEDINPUT; + + if (h_version >= V5) + h_flags &= ~(MOUSE_FLAG | MENU_FLAG); + + dumb_init_output(); + + h_flags &= ~GRAPHICS_FLAG; +} + +int os_random_seed (void) +{ + if (user_random_seed == -1) + /* Use the rockbox tick as seed value */ + return ((int)rb->current_tick) & 0x7fff; + else return user_random_seed; +} + +void os_restart_game (int stage) { (void)stage; } + +void os_fatal (const char *s) +{ + rb->splash(HZ*10, s); + exit(1); +} + +void os_init_setup(void) +{ + f_setup.attribute_assignment = 0; + f_setup.attribute_testing = 0; + f_setup.context_lines = 0; + f_setup.object_locating = 0; + f_setup.object_movement = 0; + f_setup.left_margin = 0; + f_setup.right_margin = 0; + f_setup.ignore_errors = 0; + f_setup.piracy = 0; + f_setup.undo_slots = MAX_UNDO_SLOTS; + f_setup.expand_abbreviations = 0; + f_setup.script_cols = 80; + f_setup.save_quetzal = 1; + f_setup.sound = 1; + f_setup.err_report_mode = ERR_DEFAULT_REPORT_MODE; + +} diff --git a/apps/plugins/frotz/dumb_output.c b/apps/plugins/frotz/dumb_output.c new file mode 100644 index 0000000000..eb61419195 --- /dev/null +++ b/apps/plugins/frotz/dumb_output.c @@ -0,0 +1,256 @@ +/* dumb-output.c + * $Id: dumb-output.c,v 1.2 2002/03/26 22:52:31 feedle Exp $ + * + * Copyright 1997,1998 Alfresco Petrofsky . + * Any use permitted provided this notice stays intact. + * + * Changes for Rockbox copyright 2009 Torne Wuff + * + * Rockbox is not really a dumb terminal (it supports printing text wherever + * you like) but it doesn't implement a terminal type buffer, so this is + * close enough to be a good starting point. Keeping a copy of the graphical + * framebuffer would be too expensive, text+attributes is much smaller. + */ + +#include "dumb_frotz.h" + +/* The in-memory state of the screen. */ +/* Each cell contains a style in the upper byte and a char in the lower. */ +typedef unsigned short cell; +static cell screen_data[(LCD_WIDTH/SYSFONT_WIDTH) * (LCD_HEIGHT/SYSFONT_HEIGHT)]; + +static cell make_cell(int style, char c) {return (style << 8) | (0xff & c);} +static char cell_char(cell c) {return c & 0xff;} +static int cell_style(cell c) {return c >> 8;} + +static int current_style = 0; +static int cursor_row = 0, cursor_col = 0; + +static cell *dumb_row(int r) {return screen_data + r * h_screen_cols;} + +int os_char_width (zchar z) +{ + (void)z; + return 1; +} + +int os_string_width (const zchar *s) +{ + int width = 0; + zchar c; + + while ((c = *s++) != 0) + if (c == ZC_NEW_STYLE || c == ZC_NEW_FONT) + s++; + else + width += os_char_width(c); + + return width; +} + +void os_set_cursor(int row, int col) +{ + cursor_row = row - 1; cursor_col = col - 1; + if (cursor_row >= h_screen_rows) + cursor_row = h_screen_rows - 1; +} + +/* Set a cell */ +static void dumb_set_cell(int row, int col, cell c) +{ + dumb_row(row)[col] = c; +} + +void os_set_text_style(int x) +{ + current_style = x & REVERSE_STYLE; +} + +static void dumb_display_cell(int row, int col) +{ + cell cel = dumb_row(row)[col]; + char c = cell_char(cel); + if (!c) + c = ' '; + char style = cell_style(cel); + char s[5]; + char *end = rb->utf8encode(c, s); + *end = 0; + if (style & REVERSE_STYLE) + rb->lcd_set_drawmode(DRMODE_INVERSEVID); + rb->lcd_putsxy(col * SYSFONT_WIDTH, row * SYSFONT_HEIGHT, s); + rb->lcd_set_drawmode(DRMODE_SOLID); +} + +/* put a character in the cell at the cursor and advance the cursor. */ +static void dumb_display_char(char c) +{ + dumb_set_cell(cursor_row, cursor_col, make_cell(current_style, c)); + if (++cursor_col == h_screen_cols) { + if (cursor_row == h_screen_rows - 1) + cursor_col--; + else { + cursor_row++; + cursor_col = 0; + } + } +} + +void os_display_char (zchar c) +{ + if (c >= ZC_LATIN1_MIN /*&& c <= ZC_LATIN1_MAX*/) { + dumb_display_char(c); + } else if (c >= 32 && c <= 126) { + dumb_display_char(c); + } else if (c == ZC_GAP) { + dumb_display_char(' '); dumb_display_char(' '); + } else if (c == ZC_INDENT) { + dumb_display_char(' '); dumb_display_char(' '); dumb_display_char(' '); + } + return; +} + + +/* Haxor your boxor? */ +void os_display_string (const zchar *s) +{ + zchar c; + + while ((c = *s++) != 0) + if (c == ZC_NEW_FONT) + s++; + else if (c == ZC_NEW_STYLE) + os_set_text_style(*s++); + else { + os_display_char (c); + } +} + +void os_erase_area (int top, int left, int bottom, int right) +{ + int row; + top--; left--; bottom--; right--; + if (left == 0 && right == h_screen_cols - 1) + rb->memset(dumb_row(top), 0, (bottom - top + 1) * h_screen_cols * sizeof(cell)); + else + for (row = top; row <= bottom; row++) + rb->memset(dumb_row(row) + left, 0, (right - left + 1) * sizeof(cell)); +} + +void os_scroll_area (int top, int left, int bottom, int right, int units) +{ + int row; + top--; left--; bottom--; right--; + if (units > 0) { + for (row = top; row <= bottom - units; row++) + rb->memcpy(dumb_row(row) + left, + dumb_row(row + units) + left, + (right - left + 1) * sizeof(cell)); + os_erase_area(bottom - units + 2, left + 1, bottom + 1, right + 1); + } else if (units < 0) { + for (row = bottom; row >= top - units; row--) + rb->memcpy(dumb_row(row) + left, + dumb_row(row + units) + left, + (right - left + 1) * sizeof(cell)); + os_erase_area(top + 1, left + 1, top - units, right + 1); + } +} + +int os_font_data(int font, int *height, int *width) +{ + if (font == TEXT_FONT) { + *height = 1; *width = 1; return 1; + } + return 0; +} + +void os_set_colour (int x, int y) { (void)x; (void)y; } +void os_set_font (int x) { (void)x; } + +/* Unconditionally show whole screen. */ +void dumb_dump_screen(void) +{ + int r, c; + rb->lcd_clear_display(); + for (r = 0; r < h_screen_height; r++) + for (c = 0; c < h_screen_width; c++) + dumb_display_cell(r, c); + rb->lcd_update(); +} + +/* Show the current screen contents. */ +void dumb_show_screen(bool show_cursor) +{ + (void)show_cursor; + dumb_dump_screen(); +} + +void os_more_prompt(void) +{ + int old_row = cursor_row; + int old_col = cursor_col; + int old_style = current_style; + + current_style = REVERSE_STYLE; + os_display_string("[MORE]"); + wait_for_key(); + + cursor_row = old_row; + cursor_col = old_col; + current_style = old_style; + os_erase_area(cursor_row+1, cursor_col+1, cursor_row+1, cursor_col+7); +} + +void os_reset_screen(void) +{ + current_style = REVERSE_STYLE; + os_display_string("[Press key to exit]"); + wait_for_key(); +} + + +/* To make the common code happy */ + +void os_prepare_sample (int a) { (void)a; } +void os_finish_with_sample (int a) { (void)a; } +void os_start_sample (int a, int b, int c, zword d) +{ + (void)a; (void)b; (void)c; (void)d; +} +void os_stop_sample (int a) { (void)a; } + +void dumb_init_output(void) +{ + if (h_version == V3) { + h_config |= CONFIG_SPLITSCREEN; + h_flags &= ~OLD_SOUND_FLAG; + } + + if (h_version >= V5) { + h_flags &= ~SOUND_FLAG; + } + + h_screen_height = h_screen_rows; + h_screen_width = h_screen_cols; + + h_font_width = 1; h_font_height = 1; + + os_erase_area(1, 1, h_screen_rows, h_screen_cols); +} + +bool os_picture_data(int num, int *height, int *width) +{ + (void)num; + *height = 0; + *width = 0; + return FALSE; +} + +void os_draw_picture (int num, int row, int col) +{ + (void)num; + (void)row; + (void)col; +} + +int os_peek_colour (void) {return BLACK_COLOUR; } diff --git a/apps/plugins/frotz/err.c b/apps/plugins/frotz/err.c new file mode 100644 index 0000000000..61ca78ce3b --- /dev/null +++ b/apps/plugins/frotz/err.c @@ -0,0 +1,154 @@ +/* err.c - Runtime error reporting functions + * Written by Jim Dunleavy + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +/* 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. */ + +/* int err_report_mode = ERR_DEFAULT_REPORT_MODE; */ + +static int error_count[ERR_NUM_ERRORS]; + +static char *err_messages[] = { + "Text buffer overflow", + "Store out of dynamic memory", + "Division by zero", + "Illegal object", + "Illegal attribute", + "No such property", + "Stack overflow", + "Call to illegal address", + "Call to non-routine", + "Stack underflow", + "Illegal opcode", + "Bad stack frame", + "Jump to illegal address", + "Can't save while in interrupt", + "Nesting stream #3 too deep", + "Illegal window", + "Illegal window property", + "Print at illegal address", + "@jin called with object 0", + "@get_child called with object 0", + "@get_parent called with object 0", + "@get_sibling called with object 0", + "@get_prop_addr called with object 0", + "@get_prop called with object 0", + "@put_prop called with object 0", + "@clear_attr called with object 0", + "@set_attr called with object 0", + "@test_attr called with object 0", + "@move_object called moving object 0", + "@move_object called moving into object 0", + "@remove_object called with object 0", + "@get_next_prop called with object 0" +}; + +static void print_long (unsigned long value, int base); + +/* + * init_err + * + * Initialise error reporting. + * + */ + +void init_err (void) +{ + int i; + + /* Initialize the counters. */ + + for (i = 0; i < ERR_NUM_ERRORS; i++) + error_count[i] = 0; +} + +/* + * runtime_error + * + * An error has occurred. Ignore it, pass it to os_fatal or report + * it according to err_report_mode. + * + * errnum : Numeric code for error (1 to ERR_NUM_ERRORS) + * + */ + +void runtime_error (int errnum) +{ + int wasfirst; + + if (errnum <= 0 || errnum > ERR_NUM_ERRORS) + return; + + if (f_setup.err_report_mode == ERR_REPORT_FATAL + || (!f_setup.ignore_errors && errnum <= ERR_MAX_FATAL)) { + flush_buffer (); + os_fatal (err_messages[errnum - 1]); + return; + } + + wasfirst = (error_count[errnum - 1] == 0); + error_count[errnum - 1]++; + + if ((f_setup.err_report_mode == ERR_REPORT_ALWAYS) + || (f_setup.err_report_mode == ERR_REPORT_ONCE && wasfirst)) { + long pc; + + GET_PC (pc); + print_string ("Warning: "); + print_string (err_messages[errnum - 1]); + print_string (" (PC = "); + print_long (pc, 16); + print_char (')'); + + if (f_setup.err_report_mode == ERR_REPORT_ONCE) { + print_string (" (will ignore further occurrences)"); + } else { + print_string (" (occurence "); + print_long (error_count[errnum - 1], 10); + print_char (')'); + } + new_line (); + } + +} /* report_error */ + +/* + * print_long + * + * Print an unsigned 32bit number in decimal or hex. + * + */ + +static void print_long (unsigned long value, int base) +{ + unsigned long i; + char c; + + for (i = (base == 10 ? 1000000000 : 0x10000000); i != 0; i /= base) + if (value >= i || i == 1) { + c = (value / i) % base; + print_char (c + (c <= 9 ? '0' : 'a' - 10)); + } + +}/* print_long */ diff --git a/apps/plugins/frotz/fastmem.c b/apps/plugins/frotz/fastmem.c new file mode 100644 index 0000000000..cdb4bce602 --- /dev/null +++ b/apps/plugins/frotz/fastmem.c @@ -0,0 +1,1013 @@ +/* fastmem.c - Memory related functions (fast version without virtual memory) + * Copyright (c) 1995-1997 Stefan Jokisch + * + * Changes for Rockbox copyright 2009 Torne Wuff + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* + * New undo mechanism added by Jim Dunleavy + */ + +#include "frotz.h" + +#define far + +extern void seed_random (int); +extern void restart_screen (void); +extern void refresh_text_style (void); +extern void call (zword, int, zword *, int); +extern void split_window (zword); +extern void script_open (void); +extern void script_close (void); + +extern zword save_quetzal (int, int); +extern zword restore_quetzal (int, int); + +extern void erase_window (zword); + +extern void (*op0_opcodes[]) (void); +extern void (*op1_opcodes[]) (void); +extern void (*op2_opcodes[]) (void); +extern void (*var_opcodes[]) (void); + +char save_name[MAX_PATH]; +char auxilary_name[MAX_PATH]; + +zbyte far *zmp = NULL; +zbyte far *pcp = NULL; + +int story_fp = -1; + +/* + * Data for the undo mechanism. + * This undo mechanism is based on the scheme used in Evin Robertson's + * Nitfol interpreter. + * Undo blocks are stored as differences between states. + */ + +typedef struct undo_struct undo_t; +struct undo_struct { + undo_t *next; + undo_t *prev; + long pc; + long diff_size; + zword frame_count; + zword stack_size; + zword frame_offset; + /* undo diff and stack data follow */ +}; + +static undo_t *first_undo = NULL, *last_undo = NULL, *curr_undo = NULL; +static zbyte *prev_zmp, *undo_diff; + +static int undo_count = 0; + +static void *arena_start = NULL, *arena_end = NULL, *arena_next = NULL; + +/* + * get_header_extension + * + * Read a value from the header extension (former mouse table). + * + */ + +zword get_header_extension (int entry) +{ + zword addr; + zword val; + + if (h_extension_table == 0 || entry > hx_table_size) + return 0; + + addr = h_extension_table + 2 * entry; + LOW_WORD (addr, val) + + return val; + +}/* get_header_extension */ + +/* + * set_header_extension + * + * Set an entry in the header extension (former mouse table). + * + */ + +void set_header_extension (int entry, zword val) +{ + zword addr; + + if (h_extension_table == 0 || entry > hx_table_size) + return; + + addr = h_extension_table + 2 * entry; + SET_WORD (addr, val) + +}/* set_header_extension */ + +/* + * restart_header + * + * Set all header fields which hold information about the interpreter. + * + */ + +void restart_header (void) +{ + zword screen_x_size; + zword screen_y_size; + zbyte font_x_size; + zbyte font_y_size; + + int i; + + SET_BYTE (H_CONFIG, h_config) + SET_WORD (H_FLAGS, h_flags) + + if (h_version >= V4) { + SET_BYTE (H_INTERPRETER_NUMBER, h_interpreter_number) + SET_BYTE (H_INTERPRETER_VERSION, h_interpreter_version) + SET_BYTE (H_SCREEN_ROWS, h_screen_rows) + SET_BYTE (H_SCREEN_COLS, h_screen_cols) + } + + /* It's less trouble to use font size 1x1 for V5 games, especially + because of a bug in the unreleased German version of "Zork 1" */ + + if (h_version != V6) { + screen_x_size = (zword) h_screen_cols; + screen_y_size = (zword) h_screen_rows; + font_x_size = 1; + font_y_size = 1; + } else { + screen_x_size = h_screen_width; + screen_y_size = h_screen_height; + font_x_size = h_font_width; + font_y_size = h_font_height; + } + + if (h_version >= V5) { + SET_WORD (H_SCREEN_WIDTH, screen_x_size) + SET_WORD (H_SCREEN_HEIGHT, screen_y_size) + SET_BYTE (H_FONT_HEIGHT, font_y_size) + SET_BYTE (H_FONT_WIDTH, font_x_size) + SET_BYTE (H_DEFAULT_BACKGROUND, h_default_background) + SET_BYTE (H_DEFAULT_FOREGROUND, h_default_foreground) + } + + if (h_version == V6) + for (i = 0; i < 8; i++) + storeb ((zword) (H_USER_NAME + i), h_user_name[i]); + + SET_BYTE (H_STANDARD_HIGH, h_standard_high) + SET_BYTE (H_STANDARD_LOW, h_standard_low) + +}/* restart_header */ + +/* + * init_memory + * + * Allocate memory and load the story file. + * + */ + +void init_memory (void) +{ + long size; + zword addr; + unsigned n; + int i, j; + zbyte *audiobuf; + size_t buf_size; + + static struct { + enum story story_id; + zword release; + zbyte serial[6]; + } records[] = { + { SHERLOCK, 21, "871214" }, + { SHERLOCK, 26, "880127" }, + { BEYOND_ZORK, 47, "870915" }, + { BEYOND_ZORK, 49, "870917" }, + { BEYOND_ZORK, 51, "870923" }, + { BEYOND_ZORK, 57, "871221" }, + { ZORK_ZERO, 296, "881019" }, + { ZORK_ZERO, 366, "890323" }, + { ZORK_ZERO, 383, "890602" }, + { ZORK_ZERO, 393, "890714" }, + { SHOGUN, 292, "890314" }, + { SHOGUN, 295, "890321" }, + { SHOGUN, 311, "890510" }, + { SHOGUN, 322, "890706" }, + { ARTHUR, 54, "890606" }, + { ARTHUR, 63, "890622" }, + { ARTHUR, 74, "890714" }, + { JOURNEY, 26, "890316" }, + { JOURNEY, 30, "890322" }, + { JOURNEY, 77, "890616" }, + { JOURNEY, 83, "890706" }, + { LURKING_HORROR, 203, "870506" }, + { LURKING_HORROR, 219, "870912" }, + { LURKING_HORROR, 221, "870918" }, + { UNKNOWN, 0, "------" } + }; + + /* Open story file */ + + if ((story_fp = rb->open(story_name, O_RDONLY)) < 0) + os_fatal ("Cannot open story file"); + + /* Allocate memory for story header */ + + zmp = rb->plugin_get_buffer(&buf_size); + + /* Load header into memory */ + + if (fread (zmp, 1, 64, story_fp) != 64) + os_fatal ("Story file read error"); + + /* Copy header fields to global variables */ + + LOW_BYTE (H_VERSION, h_version) + + if (h_version < V1 || h_version > V8) + os_fatal ("Unknown Z-code version"); + + LOW_BYTE (H_CONFIG, h_config) + + if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED)) + os_fatal ("Byte swapped story file"); + + LOW_WORD (H_RELEASE, h_release) + LOW_WORD (H_RESIDENT_SIZE, h_resident_size) + LOW_WORD (H_START_PC, h_start_pc) + LOW_WORD (H_DICTIONARY, h_dictionary) + LOW_WORD (H_OBJECTS, h_objects) + LOW_WORD (H_GLOBALS, h_globals) + LOW_WORD (H_DYNAMIC_SIZE, h_dynamic_size) + LOW_WORD (H_FLAGS, h_flags) + + for (i = 0, addr = H_SERIAL; i < 6; i++, addr++) + LOW_BYTE (addr, h_serial[i]) + + /* Auto-detect buggy story files that need special fixes */ + + story_id = UNKNOWN; + + for (i = 0; records[i].story_id != UNKNOWN; i++) { + + if (h_release == records[i].release) { + + for (j = 0; j < 6; j++) + if (h_serial[j] != records[i].serial[j]) + goto no_match; + + story_id = records[i].story_id; + + } + + no_match: ; /* null statement */ + + } + + LOW_WORD (H_ABBREVIATIONS, h_abbreviations) + LOW_WORD (H_FILE_SIZE, h_file_size) + + /* Calculate story file size in bytes */ + + if (h_file_size != 0) { + + story_size = (long) 2 * h_file_size; + + if (h_version >= V4) + story_size *= 2; + if (h_version >= V6) + story_size *= 2; + + } else { /* some old games lack the file size entry */ + + fseek (story_fp, 0, SEEK_END); + story_size = ftell (story_fp); + fseek (story_fp, 64, SEEK_SET); + + } + + LOW_WORD (H_CHECKSUM, h_checksum) + LOW_WORD (H_ALPHABET, h_alphabet) + LOW_WORD (H_FUNCTIONS_OFFSET, h_functions_offset) + LOW_WORD (H_STRINGS_OFFSET, h_strings_offset) + LOW_WORD (H_TERMINATING_KEYS, h_terminating_keys) + LOW_WORD (H_EXTENSION_TABLE, h_extension_table) + + /* Zork Zero Macintosh doesn't have the graphics flag set */ + + if (story_id == ZORK_ZERO && h_release == 296) + h_flags |= GRAPHICS_FLAG; + + /* Adjust opcode tables */ + + if (h_version <= V4) { + op0_opcodes[0x09] = z_pop; + op1_opcodes[0x0f] = z_not; + } else { + op0_opcodes[0x09] = z_catch; + op1_opcodes[0x0f] = z_call_n; + } + + /* Allocate memory for story data */ + + if ((size_t)story_size > buf_size) + { + audiobuf = rb->plugin_get_audio_buffer(&buf_size); + if ((size_t)story_size > buf_size) + os_fatal ("Out of memory"); + rb->memcpy(audiobuf, zmp, 64); + zmp = audiobuf; + } + + /* Assign left over memory as the arena for undo alloc */ + arena_start = arena_next = (void*)((int)(zmp + story_size + 3) & ~3); + arena_end = zmp + buf_size; + + /* Load story file in chunks of 32KB */ + + n = 0x8000; + + for (size = 64; size < story_size; size += n) { + + if (story_size - size < 0x8000) + n = (unsigned) (story_size - size); + + SET_PC (size) + + if (fread (pcp, 1, n, story_fp) != (signed)n) + os_fatal ("Story file read error"); + + } + + /* Read header extension table */ + + hx_table_size = get_header_extension (HX_TABLE_SIZE); + hx_unicode_table = get_header_extension (HX_UNICODE_TABLE); + +}/* init_memory */ + +/* + * init_undo + * + * Allocate memory for multiple undo. It is important not to occupy + * all the memory available, since the IO interface may need memory + * during the game, e.g. for loading sounds or pictures. + * + */ + +void init_undo (void) +{ + /* Allocate h_dynamic_size bytes for previous dynamic zmp state + + 1.5 h_dynamic_size for Quetzal diff + 2. */ + int size = (h_dynamic_size * 5) / 2 + 2; + if ((arena_end - arena_start) >= size) { + prev_zmp = arena_start; + undo_diff = arena_start + h_dynamic_size; + arena_start = (void*)((int)(arena_start + size + 3) & ~3); + arena_next = arena_start; + memcpy (prev_zmp, zmp, h_dynamic_size); + } else + f_setup.undo_slots = 0; + +}/* init_undo */ + +/* + * free_undo + * + * Free count undo blocks from the beginning of the undo list. + * + */ + +static void free_undo (int count) +{ + undo_t *p; + + if (count > undo_count) + count = undo_count; + while (count--) { + p = first_undo; + if (curr_undo == first_undo) + curr_undo = curr_undo->next; + first_undo = first_undo->next; + undo_count--; + } + if (first_undo) + first_undo->prev = NULL; + else + last_undo = NULL; +}/* free_undo */ + +/* + * reset_memory + * + * Close the story file and deallocate memory. + * + */ + +void reset_memory (void) +{ + if (story_fp != -1) + fclose (story_fp); + story_fp = -1; + + free_undo (undo_count); + undo_count = 0; + + arena_start = NULL; + arena_end = NULL; + arena_next = NULL; + zmp = NULL; +}/* reset_memory */ + +/* + * storeb + * + * Write a byte value to the dynamic Z-machine memory. + * + */ + +void storeb (zword addr, zbyte value) +{ + + if (addr >= h_dynamic_size) + runtime_error (ERR_STORE_RANGE); + + if (addr == H_FLAGS + 1) { /* flags register is modified */ + + h_flags &= ~(SCRIPTING_FLAG | FIXED_FONT_FLAG); + h_flags |= value & (SCRIPTING_FLAG | FIXED_FONT_FLAG); + + if (value & SCRIPTING_FLAG) { + if (!ostream_script) + script_open (); + } else { + if (ostream_script) + script_close (); + } + + refresh_text_style (); + + } + + SET_BYTE (addr, value) + +}/* storeb */ + +/* + * storew + * + * Write a word value to the dynamic Z-machine memory. + * + */ + +void storew (zword addr, zword value) +{ + + storeb ((zword) (addr + 0), hi (value)); + storeb ((zword) (addr + 1), lo (value)); + +}/* storew */ + +/* + * z_restart, re-load dynamic area, clear the stack and set the PC. + * + * no zargs used + * + */ + +void z_restart (void) +{ + static bool first_restart = TRUE; + + flush_buffer (); + + os_restart_game (RESTART_BEGIN); + + seed_random (0); + + if (!first_restart) { + + fseek (story_fp, 0, SEEK_SET); + + if (fread (zmp, 1, h_dynamic_size, story_fp) != h_dynamic_size) + os_fatal ("Story file read error"); + + } else first_restart = FALSE; + + restart_header (); + restart_screen (); + + sp = fp = stack + STACK_SIZE; + frame_count = 0; + + if (h_version != V6) { + + long pc = (long) h_start_pc; + SET_PC (pc) + + } else call (h_start_pc, 0, NULL, 0); + + os_restart_game (RESTART_END); + +}/* z_restart */ + +/* + * get_default_name + * + * Read a default file name from the memory of the Z-machine and + * copy it to a string. + * + */ + +static void get_default_name (char *default_name, zword addr) +{ + + if (addr != 0) { + + zbyte len; + int i; + + LOW_BYTE (addr, len) + addr++; + + for (i = 0; i < len; i++) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + default_name[i] = c; + + } + + default_name[i] = 0; + + if (strchr (default_name, '.') == NULL) + strcpy (default_name + i, ".AUX"); + + } else strcpy (default_name, auxilary_name); + +}/* get_default_name */ + +/* + * z_restore, restore [a part of] a Z-machine state from disk + * + * zargs[0] = address of area to restore (optional) + * zargs[1] = number of bytes to restore + * zargs[2] = address of suggested file name + * + */ + +void z_restore (void) +{ + char new_name[MAX_PATH]; + char default_name[MAX_PATH]; + int gfp; + + zword success = 0; + + if (zargc != 0) { + + /* Get the file name */ + + get_default_name (default_name, (zargc >= 3) ? zargs[2] : 0); + + if (os_read_file_name (new_name, default_name, FILE_LOAD_AUX) == 0) + goto finished; + + strcpy (auxilary_name, default_name); + + /* Open auxilary file */ + + if ((gfp = rb->open (new_name, O_RDONLY)) < 0) + goto finished; + + /* Load auxilary file */ + + success = fread (zmp + zargs[0], 1, zargs[1], gfp); + + /* Close auxilary file */ + + fclose (gfp); + + } else { + + /* Get the file name */ + + if (os_read_file_name (new_name, save_name, FILE_RESTORE) == 0) + goto finished; + + strcpy (save_name, new_name); + + /* Open game file */ + + if ((gfp = rb->open (new_name, O_RDONLY)) < 0) + goto finished; + + success = restore_quetzal (gfp, story_fp); + + /* Close game file */ + + fclose (gfp); + + if ((short) success >= 0) { + + if ((short) success > 0) { + zbyte old_screen_rows; + zbyte old_screen_cols; + + /* In V3, reset the upper window. */ + if (h_version == V3) + split_window (0); + + LOW_BYTE (H_SCREEN_ROWS, old_screen_rows); + LOW_BYTE (H_SCREEN_COLS, old_screen_cols); + + /* Reload cached header fields. */ + restart_header (); + + /* + * Since QUETZAL files may be saved on many different machines, + * the screen sizes may vary a lot. Erasing the status window + * seems to cover up most of the resulting badness. + */ + if (h_version > V3 && h_version != V6 + && (h_screen_rows != old_screen_rows + || h_screen_cols != old_screen_cols)) + erase_window (1); + } + } else + os_fatal ("Error reading save file"); + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); + +}/* z_restore */ + +/* + * mem_diff + * + * Set diff to a Quetzal-like difference between a and b, + * copying a to b as we go. It is assumed that diff points to a + * buffer which is large enough to hold the diff. + * mem_size is the number of bytes to compare. + * Returns the number of bytes copied to diff. + * + */ + +static long mem_diff (zbyte *a, zbyte *b, zword mem_size, zbyte *diff) +{ + unsigned size = mem_size; + zbyte *p = diff; + unsigned j; + zbyte c; + + for (;;) { + for (j = 0; size > 0 && (c = *a++ ^ *b++) == 0; j++) + size--; + if (size == 0) break; + size--; + if (j > 0x8000) { + *p++ = 0; + *p++ = 0xff; + *p++ = 0xff; + j -= 0x8000; + } + if (j > 0) { + *p++ = 0; + j--; + if (j <= 0x7f) { + *p++ = j; + } else { + *p++ = (j & 0x7f) | 0x80; + *p++ = (j & 0x7f80) >> 7; + } + } + *p++ = c; + *(b - 1) ^= c; + } + return p - diff; +}/* mem_diff */ + +/* + * mem_undiff + * + * Applies a quetzal-like diff to dest + * + */ + +static void mem_undiff (zbyte *diff, long diff_length, zbyte *dest) +{ + zbyte c; + + while (diff_length) { + c = *diff++; + diff_length--; + if (c == 0) { + unsigned runlen; + + if (!diff_length) + return; /* Incomplete run */ + runlen = *diff++; + diff_length--; + if (runlen & 0x80) { + if (!diff_length) + return; /* Incomplete extended run */ + c = *diff++; + diff_length--; + runlen = (runlen & 0x7f) | (((unsigned) c) << 7); + } + + dest += runlen + 1; + } else { + *dest++ ^= c; + } + } +}/* mem_undiff */ + +/* + * restore_undo + * + * This function does the dirty work for z_restore_undo. + * + */ + +int restore_undo (void) +{ + + if (f_setup.undo_slots == 0) /* undo feature unavailable */ + + return -1; + + if (curr_undo == NULL) /* no saved game state */ + + return 0; + + /* undo possible */ + + memcpy (zmp, prev_zmp, h_dynamic_size); + SET_PC (curr_undo->pc) + sp = stack + STACK_SIZE - curr_undo->stack_size; + fp = stack + curr_undo->frame_offset; + frame_count = curr_undo->frame_count; + mem_undiff ((zbyte *) (curr_undo + 1), curr_undo->diff_size, prev_zmp); + memcpy (sp, (zbyte *)(curr_undo + 1) + curr_undo->diff_size, + curr_undo->stack_size * sizeof (*sp)); + + curr_undo = curr_undo->prev; + + restart_header (); + + return 2; + +}/* restore_undo */ + +/* + * z_restore_undo, restore a Z-machine state from memory. + * + * no zargs used + * + */ + +void z_restore_undo (void) +{ + + store ((zword) restore_undo ()); + +}/* z_restore_undo */ + +/* + * z_save, save [a part of] the Z-machine state to disk. + * + * zargs[0] = address of memory area to save (optional) + * zargs[1] = number of bytes to save + * zargs[2] = address of suggested file name + * + */ + +void z_save (void) +{ + char new_name[MAX_PATH]; + char default_name[MAX_PATH]; + int gfp; + + zword success = 0; + + if (zargc != 0) { + + /* Get the file name */ + + get_default_name (default_name, (zargc >= 3) ? zargs[2] : 0); + + if (os_read_file_name (new_name, default_name, FILE_SAVE_AUX) == 0) + goto finished; + + strcpy (auxilary_name, default_name); + + /* Open auxilary file */ + + if ((gfp = rb->open (new_name, O_WRONLY|O_CREAT|O_TRUNC)) < 0) + goto finished; + + /* Write auxilary file */ + + success = fwrite (zmp + zargs[0], zargs[1], 1, gfp); + + /* Close auxilary file */ + + fclose (gfp); + + } else { + + /* Get the file name */ + + if (os_read_file_name (new_name, save_name, FILE_SAVE) == 0) + goto finished; + + strcpy (save_name, new_name); + + /* Open game file */ + + if ((gfp = rb->open (new_name, O_WRONLY|O_CREAT|O_TRUNC)) < 0) + goto finished; + + success = save_quetzal (gfp, story_fp); + + /* Close game file and check for errors */ + + if (fclose (gfp) != 0 || ferror (story_fp)) { + print_string ("Error writing save file\n"); + goto finished; + } + + /* Success */ + + success = 1; + + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); + +}/* z_save */ + +/* + * save_undo + * + * This function does the dirty work for z_save_undo. + * + */ + +int save_undo (void) +{ + long diff_size; + zword stack_size; + int size; + undo_t *p; + + if (f_setup.undo_slots == 0) /* undo feature unavailable */ + return -1; + + /* save undo possible */ + + while (last_undo != curr_undo) { + p = last_undo; + last_undo = last_undo->prev; + arena_next = p; + undo_count--; + } + if (last_undo) + last_undo->next = NULL; + else + first_undo = NULL; + + if (undo_count == f_setup.undo_slots) + free_undo (1); + + diff_size = mem_diff (zmp, prev_zmp, h_dynamic_size, undo_diff); + stack_size = stack + STACK_SIZE - sp; + do { + size = sizeof (undo_t) + diff_size + stack_size * sizeof (*sp); + if (arena_next > (void*)first_undo) { + /* Free space is all at the end */ + if ((arena_end - arena_next) >= size) { + /* Trivial: enough room at the end */ + p = arena_next; + arena_next = (void*)((int)(arena_next + size + 3) & ~3); + } else { + /* Need to wrap */ + arena_next = arena_start; + p = NULL; + } + } else { + /* Free space is somewhere else */ + if (((void*)first_undo - arena_next) >= size) { + /* There is room before the "first" undo */ + p = arena_next; + arena_next = (void*)((int)(arena_next + size + 3) & ~3); + } else { + /* Not enough room, just need to free some */ + p = NULL; + } + } + + if (p == NULL) + free_undo (1); + } while (!p && undo_count); + if (p == NULL) + return -1; + GET_PC (p->pc) + p->frame_count = frame_count; + p->diff_size = diff_size; + p->stack_size = stack_size; + p->frame_offset = fp - stack; + memcpy (p + 1, undo_diff, diff_size); + memcpy ((zbyte *)(p + 1) + diff_size, sp, stack_size * sizeof (*sp)); + + if (!first_undo) { + p->prev = NULL; + first_undo = p; + } else { + last_undo->next = p; + p->prev = last_undo; + } + p->next = NULL; + curr_undo = last_undo = p; + undo_count++; + return 1; + +}/* save_undo */ + +/* + * z_save_undo, save the current Z-machine state for a future undo. + * + * no zargs used + * + */ + +void z_save_undo (void) +{ + + store ((zword) save_undo ()); + +}/* z_save_undo */ + +/* + * z_verify, check the story file integrity. + * + * no zargs used + * + */ + +void z_verify (void) +{ + zword checksum = 0; + long i; + + /* Sum all bytes in story file except header bytes */ + + fseek (story_fp, 64, SEEK_SET); + + for (i = 64; i < story_size; i++) + checksum += fgetc (story_fp); + + /* Branch if the checksums are equal */ + + branch (checksum == h_checksum); + +}/* z_verify */ diff --git a/apps/plugins/frotz/files.c b/apps/plugins/frotz/files.c new file mode 100644 index 0000000000..1baaa4073f --- /dev/null +++ b/apps/plugins/frotz/files.c @@ -0,0 +1,563 @@ +/* files.c - Transscription, recording and playback + * Copyright (c) 1995-1997 Stefan Jokisch + * + * Changes for Rockbox copyright 2009 Torne Wuff + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +extern void set_more_prompts (bool); + +extern bool is_terminator (zchar); + +extern bool read_yes_or_no (const char *); + +char script_name[MAX_PATH]; +char command_name[MAX_PATH]; + +#ifdef __MSDOS__ +extern char latin1_to_ibm[]; +#endif + +static int script_width = 0; + +int sfp = -1; +int rfp = -1; +int pfp = -1; + +/* + * script_open + * + * Open the transscript file. 'AMFV' makes this more complicated as it + * turns transscription on/off several times to exclude some text from + * the transscription file. This wasn't a problem for the original V4 + * interpreters which always sent transscription to the printer, but it + * means a problem to modern interpreters that offer to open a new file + * every time transscription is turned on. Our solution is to append to + * the old transscription file in V1 to V4, and to ask for a new file + * name in V5+. + * + */ + +void script_open (void) +{ + static bool script_valid = FALSE; + + char new_name[MAX_PATH]; + + h_flags &= ~SCRIPTING_FLAG; + + if (h_version >= V5 || !script_valid) { + + if (!os_read_file_name (new_name, script_name, FILE_SCRIPT)) + goto done; + + strcpy (script_name, new_name); + + } + + /* Opening in "at" mode doesn't work for script_erase_input... */ + + if ((sfp = rb->open (script_name, O_RDWR|O_CREAT)) != -1) { + + fseek (sfp, 0, SEEK_END); + + h_flags |= SCRIPTING_FLAG; + + script_valid = TRUE; + ostream_script = TRUE; + + script_width = 0; + + } else print_string ("Cannot open file\n"); + +done: + + SET_WORD (H_FLAGS, h_flags) + +}/* script_open */ + +/* + * script_close + * + * Stop transscription. + * + */ + +void script_close (void) +{ + + h_flags &= ~SCRIPTING_FLAG; + SET_WORD (H_FLAGS, h_flags) + + fclose (sfp); ostream_script = FALSE; + sfp = -1; + +}/* script_close */ + +/* + * script_new_line + * + * Write a newline to the transscript file. + * + */ + +void script_new_line (void) +{ + + if (fputc ('\n', sfp) == EOF) + script_close (); + + script_width = 0; + +}/* script_new_line */ + +/* + * script_char + * + * Write a single character to the transscript file. + * + */ + +void script_char (zchar c) +{ + + if (c == ZC_INDENT && script_width != 0) + c = ' '; + + if (c == ZC_INDENT) + { script_char (' '); script_char (' '); script_char (' '); return; } + if (c == ZC_GAP) + { script_char (' '); script_char (' '); return; } + +#ifdef __MSDOS__ + if (c >= ZC_LATIN1_MIN) + c = latin1_to_ibm[c - ZC_LATIN1_MIN]; +#endif + + fputc (c, sfp); script_width++; + +}/* script_char */ + +/* + * script_word + * + * Write a string to the transscript file. + * + */ + +void script_word (const zchar *s) +{ + int width; + int i; + + if (*s == ZC_INDENT && script_width != 0) + script_char (*s++); + + for (i = 0, width = 0; s[i] != 0; i++) + + if (s[i] == ZC_NEW_STYLE || s[i] == ZC_NEW_FONT) + i++; + else if (s[i] == ZC_GAP) + width += 3; + else if (s[i] == ZC_INDENT) + width += 2; + else + width += 1; + + if (f_setup.script_cols != 0 && script_width + width > f_setup.script_cols) { + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + s++; + + script_new_line (); + + } + + for (i = 0; s[i] != 0; i++) + + if (s[i] == ZC_NEW_FONT || s[i] == ZC_NEW_STYLE) + i++; + else + script_char (s[i]); + +}/* script_word */ + +/* + * script_write_input + * + * Send an input line to the transscript file. + * + */ + +void script_write_input (const zchar *buf, zchar key) +{ + int width; + int i; + + for (i = 0, width = 0; buf[i] != 0; i++) + width++; + + if (f_setup.script_cols != 0 && script_width + width > f_setup.script_cols) + script_new_line (); + + for (i = 0; buf[i] != 0; i++) + script_char (buf[i]); + + if (key == ZC_RETURN) + script_new_line (); + +}/* script_write_input */ + +/* + * script_erase_input + * + * Remove an input line from the transscript file. + * + */ + +void script_erase_input (const zchar *buf) +{ + int width; + int i; + + for (i = 0, width = 0; buf[i] != 0; i++) + width++; + + fseek (sfp, -width, SEEK_CUR); script_width -= width; + +}/* script_erase_input */ + +/* + * script_mssg_on + * + * Start sending a "debugging" message to the transscript file. + * + */ + +void script_mssg_on (void) +{ + + if (script_width != 0) + script_new_line (); + + script_char (ZC_INDENT); + +}/* script_mssg_on */ + +/* + * script_mssg_off + * + * Stop writing a "debugging" message. + * + */ + +void script_mssg_off (void) +{ + + script_new_line (); + +}/* script_mssg_off */ + +/* + * record_open + * + * Open a file to record the player's input. + * + */ + +void record_open (void) +{ + char new_name[MAX_PATH]; + + if (os_read_file_name (new_name, command_name, FILE_RECORD)) { + + strcpy (command_name, new_name); + + if ((rfp = rb->open (new_name, O_WRONLY|O_CREAT|O_TRUNC)) != -1) + ostream_record = TRUE; + else + print_string ("Cannot open file\n"); + + } + +}/* record_open */ + +/* + * record_close + * + * Stop recording the player's input. + * + */ + +void record_close (void) +{ + + fclose (rfp); ostream_record = FALSE; + rfp = -1; + +}/* record_close */ + +/* + * record_code + * + * Helper function for record_char. + * + */ + +static void record_code (int c, bool force_encoding) +{ + + if (force_encoding || c == '[' || c < 0x20 || c > 0x7e) { + + int i; + + fputc ('[', rfp); + + for (i = 10000; i != 0; i /= 10) + if (c >= i || i == 1) + fputc ('0' + (c / i) % 10, rfp); + + fputc (']', rfp); + + } else fputc (c, rfp); + +}/* record_code */ + +/* + * record_char + * + * Write a character to the command file. + * + */ + +static void record_char (zchar c) +{ + + if (c != ZC_RETURN) { + if (c < ZC_HKEY_MIN || c > ZC_HKEY_MAX) { + record_code (translate_to_zscii (c), FALSE); + if (c == ZC_SINGLE_CLICK || c == ZC_DOUBLE_CLICK) { + record_code (mouse_x, TRUE); + record_code (mouse_y, TRUE); + } + } else record_code (1000 + c - ZC_HKEY_MIN, TRUE); + } + +}/* record_char */ + +/* + * record_write_key + * + * Copy a keystroke to the command file. + * + */ + +void record_write_key (zchar key) +{ + + record_char (key); + + if (fputc ('\n', rfp) == EOF) + record_close (); + +}/* record_write_key */ + +/* + * record_write_input + * + * Copy a line of input to a command file. + * + */ + +void record_write_input (const zchar *buf, zchar key) +{ + zchar c; + + while ((c = *buf++) != 0) + record_char (c); + + record_char (key); + + if (fputc ('\n', rfp) == EOF) + record_close (); + +}/* record_write_input */ + +/* + * replay_open + * + * Open a file of commands for playback. + * + */ + +void replay_open (void) +{ + char new_name[MAX_PATH]; + + if (os_read_file_name (new_name, command_name, FILE_PLAYBACK)) { + + strcpy (command_name, new_name); + + if ((pfp = rb->open (new_name, O_RDONLY)) != -1) { + + set_more_prompts (read_yes_or_no ("Do you want MORE prompts")); + + istream_replay = TRUE; + + } else print_string ("Cannot open file\n"); + + } + +}/* replay_open */ + +/* + * replay_close + * + * Stop playback of commands. + * + */ + +void replay_close (void) +{ + + set_more_prompts (TRUE); + + fclose (pfp); istream_replay = FALSE; + pfp = -1; + +}/* replay_close */ + +/* + * replay_code + * + * Helper function for replay_key and replay_line. + * + */ + +static int replay_code (void) +{ + int c; + + if ((c = fgetc (pfp)) == '[') { + + int c2; + + c = 0; + + while ((c2 = fgetc (pfp)) != EOF && c2 >= '0' && c2 <= '9') + c = 10 * c + c2 - '0'; + + return (c2 == ']') ? c : EOF; + + } else return c; + +}/* replay_code */ + +/* + * replay_char + * + * Read a character from the command file. + * + */ + +static zchar replay_char (void) +{ + int c; + + if ((c = replay_code ()) != EOF) { + + if (c != '\n') { + + if (c < 1000) { + + c = translate_from_zscii (c); + + if (c == ZC_SINGLE_CLICK || c == ZC_DOUBLE_CLICK) { + mouse_x = replay_code (); + mouse_y = replay_code (); + } + + return c; + + } else return ZC_HKEY_MIN + c - 1000; + } + + ungetc ('\n', pfp); + + return ZC_RETURN; + + } else return ZC_BAD; + +}/* replay_char */ + +/* + * replay_read_key + * + * Read a keystroke from a command file. + * + */ + +zchar replay_read_key (void) +{ + zchar key; + + key = replay_char (); + + if (fgetc (pfp) != '\n') { + + replay_close (); + return ZC_BAD; + + } else return key; + +}/* replay_read_key */ + +/* + * replay_read_input + * + * Read a line of input from a command file. + * + */ + +zchar replay_read_input (zchar *buf) +{ + zchar c; + + for (;;) { + + c = replay_char (); + + if (c == ZC_BAD || is_terminator (c)) + break; + + *buf++ = c; + + } + + *buf = 0; + + if (fgetc (pfp) != '\n') { + + replay_close (); + return ZC_BAD; + + } else return c; + +}/* replay_read_input */ diff --git a/apps/plugins/frotz/frotz.c b/apps/plugins/frotz/frotz.c new file mode 100644 index 0000000000..96029b85cb --- /dev/null +++ b/apps/plugins/frotz/frotz.c @@ -0,0 +1,314 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2009 Torne Wuff + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "plugin.h" +#include "dumb_frotz.h" +#include "lib/pluginlib_exit.h" +#include "lib/pluginlib_actions.h" + +PLUGIN_HEADER + +extern int frotz_main(void); +extern bool hot_key_quit(void); + +f_setup_t f_setup; +extern char save_name[], auxilary_name[], script_name[], command_name[]; +extern int story_fp, sfp, rfp, pfp; + +static struct viewport vp[NB_SCREENS]; + +static void atexit_cleanup(void); + +enum plugin_status plugin_start(const void* parameter) +{ + int i; + char* ext; + + PLUGINLIB_EXIT_INIT_ATEXIT(atexit_cleanup); + + if (!parameter) + return PLUGIN_ERROR; + + rb->lcd_setfont(FONT_SYSFIXED); +#if LCD_DEPTH > 1 + rb->lcd_set_backdrop(NULL); +#endif + rb->lcd_clear_display(); + + FOR_NB_SCREENS(i) + rb->viewport_set_defaults(&vp[i], i); + + story_name = parameter; + strcpy(save_name, story_name); + ext = rb->strrchr(save_name, '.'); + if (ext) + *ext = '\0'; + strcpy(auxilary_name, save_name); + strcpy(script_name, save_name); + strcpy(command_name, save_name); + rb->strlcat(save_name, ".sav", MAX_PATH); + rb->strlcat(auxilary_name, ".aux", MAX_PATH); + rb->strlcat(script_name, ".scr", MAX_PATH); + rb->strlcat(command_name, ".rec", MAX_PATH); + + frotz_main(); + + return PLUGIN_OK; +} + +void atexit_cleanup() +{ + if (story_fp != -1) + fclose(story_fp); + if (sfp != -1) + fclose(sfp); + if (rfp != -1) + fclose(rfp); + if (pfp != -1) + fclose(pfp); +} + +MENUITEM_STRINGLIST(ingame_menu, "Frotz", NULL, "Resume", "Undo", "Restart", + "Toggle input recording", "Play back input", + "Debug options", "Exit"); + +zchar menu(void) +{ + switch (rb->do_menu(&ingame_menu, NULL, vp, true)) + { + case 0: + default: + dumb_dump_screen(); + return ZC_BAD; + case 1: + return ZC_HKEY_UNDO; + case 2: + return ZC_HKEY_RESTART; + case 3: + return ZC_HKEY_RECORD; + case 4: + return ZC_HKEY_PLAYBACK; + case 5: + return ZC_HKEY_DEBUG; + case 6: + return ZC_HKEY_QUIT; + } +} + +const struct button_mapping* plugin_contexts[]={generic_actions}; + +void wait_for_key() +{ + int action; + + dumb_show_screen(false); + + for (;;) + { + action = pluginlib_getaction(TIMEOUT_BLOCK, + plugin_contexts, 1); + switch (action) + { + case PLA_QUIT: + hot_key_quit(); + break; + + case PLA_FIRE: + return; + } + } +} + +zchar do_input(int timeout, bool show_cursor) +{ + int action; + long timeout_at; + zchar menu_ret; + + dumb_show_screen(show_cursor); + + /* Convert timeout (tenths of a second) to ticks */ + if (timeout > 0) + timeout = (timeout * HZ) / 10; + else + timeout = TIMEOUT_BLOCK; + + timeout_at = *rb->current_tick + timeout; + + for (;;) + { + action = pluginlib_getaction(timeout, + plugin_contexts, 1); + switch (action) + { + case PLA_QUIT: + return ZC_HKEY_QUIT; + + case PLA_MENU: + menu_ret = menu(); + if (menu_ret != ZC_BAD) + return menu_ret; + timeout_at = *rb->current_tick + timeout; + break; + + case PLA_FIRE: + return ZC_RETURN; + + case PLA_START: + return ZC_BAD; + + default: + if (timeout != TIMEOUT_BLOCK && + !TIME_BEFORE(*rb->current_tick, timeout_at)) + return ZC_TIME_OUT; + } + } +} + +zchar os_read_key(int timeout, bool show_cursor) +{ + int r; + char inputbuf[5]; + short key; + zchar zkey; + + for(;;) + { + zkey = do_input(timeout, show_cursor); + if (zkey != ZC_BAD) + return zkey; + + inputbuf[0] = '\0'; + r = rb->kbd_input(inputbuf, 5); + rb->lcd_setfont(FONT_SYSFIXED); + dumb_dump_screen(); + if (!r) + { + rb->utf8decode(inputbuf, &key); + if (key > 0 && key < 256) + return (zchar)key; + } + } +} + +zchar os_read_line(int max, zchar *buf, int timeout, int width, int continued) +{ + (void)continued; + int r; + char inputbuf[256]; + const char *in; + char *out; + short key; + zchar zkey; + + for(;;) + { + zkey = do_input(timeout, true); + if (zkey != ZC_BAD) + return zkey; + + if (max > width) + max = width; + strcpy(inputbuf, buf); + r = rb->kbd_input(inputbuf, 256); + rb->lcd_setfont(FONT_SYSFIXED); + dumb_dump_screen(); + if (!r) + { + in = inputbuf; + out = buf; + while (*in && max) + { + in = rb->utf8decode(in, &key); + if (key > 0 && key < 256) + { + *out++ = key; + max--; + } + } + *out = '\0'; + os_display_string(buf); + return ZC_RETURN; + } + } +} + +bool read_yes_or_no(const char *s) +{ + char message_line[50]; + const char *message_lines[] = {message_line}; + const struct text_message message = {message_lines, 1}; + + rb->strlcpy(message_line, s, 49); + rb->strcat(message_line, "?"); + + if (rb->gui_syncyesno_run(&message, NULL, NULL) == YESNO_YES) + return TRUE; + else + return FALSE; +} + +zchar os_read_mouse(void) +{ + return 0; +} + +int os_read_file_name(char *file_name, const char *default_name, int flag) +{ + (void)flag; + strcpy(file_name, default_name); + return TRUE; +} + +void os_beep(int volume) +{ + rb->splashf(HZ/2, "[%s-PITCHED BEEP]", (volume==1) ? "HIGH" : "LOW"); +} + +static unsigned char unget_buf; +static int unget_file; + +int ungetc(int c, int f) +{ + unget_file = f; + unget_buf = c; + return c; +} + +int fgetc(int f) +{ + unsigned char cb; + if (unget_file == f) + { + unget_file = -1; + return unget_buf; + } + if (rb->read(f, &cb, 1) != 1) + return EOF; + return cb; +} + +int fputc(int c, int f) +{ + unsigned char cb = c; + if (rb->write(f, &cb, 1) != 1) + return EOF; + return cb; +} diff --git a/apps/plugins/frotz/frotz.h b/apps/plugins/frotz/frotz.h new file mode 100644 index 0000000000..bc8869cd24 --- /dev/null +++ b/apps/plugins/frotz/frotz.h @@ -0,0 +1,583 @@ +/* + * frotz.h + * + * Global declarations and definitions + * + */ + +#include "frotzplugin.h" + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +typedef unsigned char zbyte; +typedef unsigned short zword; + +enum story { + BEYOND_ZORK, + SHERLOCK, + ZORK_ZERO, + SHOGUN, + ARTHUR, + JOURNEY, + LURKING_HORROR, + UNKNOWN +}; + +typedef unsigned char zchar; + +/*** Constants that may be set at compile time ***/ + +#ifndef MAX_UNDO_SLOTS +#define MAX_UNDO_SLOTS 500 +#endif +#ifndef TEXT_BUFFER_SIZE +#define TEXT_BUFFER_SIZE 200 +#endif +#ifndef INPUT_BUFFER_SIZE +#define INPUT_BUFFER_SIZE 200 +#endif +#ifndef STACK_SIZE +#define STACK_SIZE 1024 +#endif + +/*** Story file header format ***/ + +#define H_VERSION 0 +#define H_CONFIG 1 +#define H_RELEASE 2 +#define H_RESIDENT_SIZE 4 +#define H_START_PC 6 +#define H_DICTIONARY 8 +#define H_OBJECTS 10 +#define H_GLOBALS 12 +#define H_DYNAMIC_SIZE 14 +#define H_FLAGS 16 +#define H_SERIAL 18 +#define H_ABBREVIATIONS 24 +#define H_FILE_SIZE 26 +#define H_CHECKSUM 28 +#define H_INTERPRETER_NUMBER 30 +#define H_INTERPRETER_VERSION 31 +#define H_SCREEN_ROWS 32 +#define H_SCREEN_COLS 33 +#define H_SCREEN_WIDTH 34 +#define H_SCREEN_HEIGHT 36 +#define H_FONT_HEIGHT 38 /* this is the font width in V5 */ +#define H_FONT_WIDTH 39 /* this is the font height in V5 */ +#define H_FUNCTIONS_OFFSET 40 +#define H_STRINGS_OFFSET 42 +#define H_DEFAULT_BACKGROUND 44 +#define H_DEFAULT_FOREGROUND 45 +#define H_TERMINATING_KEYS 46 +#define H_LINE_WIDTH 48 +#define H_STANDARD_HIGH 50 +#define H_STANDARD_LOW 51 +#define H_ALPHABET 52 +#define H_EXTENSION_TABLE 54 +#define H_USER_NAME 56 + +#define HX_TABLE_SIZE 0 +#define HX_MOUSE_X 1 +#define HX_MOUSE_Y 2 +#define HX_UNICODE_TABLE 3 + +/*** Various Z-machine constants ***/ + +#define V1 1 +#define V2 2 +#define V3 3 +#define V4 4 +#define V5 5 +#define V6 6 +#define V7 7 +#define V8 8 + +#define CONFIG_BYTE_SWAPPED 0x01 /* Story file is byte swapped - V3 */ +#define CONFIG_TIME 0x02 /* Status line displays time - V3 */ +#define CONFIG_TWODISKS 0x04 /* Story file occupied two disks - V3 */ +#define CONFIG_TANDY 0x08 /* Tandy licensed game - V3 */ +#define CONFIG_NOSTATUSLINE 0x10 /* Interpr can't support status lines - V3 */ +#define CONFIG_SPLITSCREEN 0x20 /* Interpr supports split screen mode - V3 */ +#define CONFIG_PROPORTIONAL 0x40 /* Interpr uses proportional font - V3 */ + +#define CONFIG_COLOUR 0x01 /* Interpr supports colour - V5+ */ +#define CONFIG_PICTURES 0x02 /* Interpr supports pictures - V6 */ +#define CONFIG_BOLDFACE 0x04 /* Interpr supports boldface style - V4+ */ +#define CONFIG_EMPHASIS 0x08 /* Interpr supports emphasis style - V4+ */ +#define CONFIG_FIXED 0x10 /* Interpr supports fixed width style - V4+ */ +#define CONFIG_SOUND 0x20 /* Interpr supports sound - V6 */ + +#define CONFIG_TIMEDINPUT 0x80 /* Interpr supports timed input - V4+ */ + +#define SCRIPTING_FLAG 0x0001 /* Outputting to transscription file - V1+ */ +#define FIXED_FONT_FLAG 0x0002 /* Use fixed width font - V3+ */ +#define REFRESH_FLAG 0x0004 /* Refresh the screen - V6 */ +#define GRAPHICS_FLAG 0x0008 /* Game wants to use graphics - V5+ */ +#define OLD_SOUND_FLAG 0x0010 /* Game wants to use sound effects - V3 */ +#define UNDO_FLAG 0x0010 /* Game wants to use UNDO feature - V5+ */ +#define MOUSE_FLAG 0x0020 /* Game wants to use a mouse - V5+ */ +#define COLOUR_FLAG 0x0040 /* Game wants to use colours - V5+ */ +#define SOUND_FLAG 0x0080 /* Game wants to use sound effects - V5+ */ +#define MENU_FLAG 0x0100 /* Game wants to use menus - V6 */ + +#define INTERP_DEFAULT 0 +#define INTERP_DEC_20 1 +#define INTERP_APPLE_IIE 2 +#define INTERP_MACINTOSH 3 +#define INTERP_AMIGA 4 +#define INTERP_ATARI_ST 5 +#define INTERP_MSDOS 6 +#define INTERP_CBM_128 7 +#define INTERP_CBM_64 8 +#define INTERP_APPLE_IIC 9 +#define INTERP_APPLE_IIGS 10 +#define INTERP_TANDY 11 + +#define BLACK_COLOUR 2 +#define RED_COLOUR 3 +#define GREEN_COLOUR 4 +#define YELLOW_COLOUR 5 +#define BLUE_COLOUR 6 +#define MAGENTA_COLOUR 7 +#define CYAN_COLOUR 8 +#define WHITE_COLOUR 9 +#define GREY_COLOUR 10 /* INTERP_MSDOS only */ +#define LIGHTGREY_COLOUR 10 /* INTERP_AMIGA only */ +#define MEDIUMGREY_COLOUR 11 /* INTERP_AMIGA only */ +#define DARKGREY_COLOUR 12 /* INTERP_AMIGA only */ + +#define REVERSE_STYLE 1 +#define BOLDFACE_STYLE 2 +#define EMPHASIS_STYLE 4 +#define FIXED_WIDTH_STYLE 8 + +#define TEXT_FONT 1 +#define PICTURE_FONT 2 +#define GRAPHICS_FONT 3 +#define FIXED_WIDTH_FONT 4 + +#define BEEP_HIGH 1 +#define BEEP_LOW 2 + +/*** Constants for os_restart_game */ + +#define RESTART_BEGIN 0 +#define RESTART_WPROP_SET 1 +#define RESTART_END 2 + +/*** Character codes ***/ + +#define ZC_TIME_OUT 0x00 +#define ZC_NEW_STYLE 0x01 +#define ZC_NEW_FONT 0x02 +#define ZC_BACKSPACE 0x08 +#define ZC_INDENT 0x09 +#define ZC_GAP 0x0b +#define ZC_RETURN 0x0d +#define ZC_HKEY_MIN 0x0e +#define ZC_HKEY_RECORD 0x0e +#define ZC_HKEY_PLAYBACK 0x0f +#define ZC_HKEY_SEED 0x10 +#define ZC_HKEY_UNDO 0x11 +#define ZC_HKEY_RESTART 0x12 +#define ZC_HKEY_QUIT 0x13 +#define ZC_HKEY_DEBUG 0x14 +#define ZC_HKEY_HELP 0x15 +#define ZC_HKEY_MAX 0x15 +#define ZC_ESCAPE 0x1b +#define ZC_ASCII_MIN 0x20 +#define ZC_ASCII_MAX 0x7e +#define ZC_BAD 0x7f +#define ZC_ARROW_MIN 0x81 +#define ZC_ARROW_UP 0x81 +#define ZC_ARROW_DOWN 0x82 +#define ZC_ARROW_LEFT 0x83 +#define ZC_ARROW_RIGHT 0x84 +#define ZC_ARROW_MAX 0x84 +#define ZC_FKEY_MIN 0x85 +#define ZC_FKEY_MAX 0x90 +#define ZC_NUMPAD_MIN 0x91 +#define ZC_NUMPAD_MAX 0x9a +#define ZC_SINGLE_CLICK 0x9b +#define ZC_DOUBLE_CLICK 0x9c +#define ZC_MENU_CLICK 0x9d +#define ZC_LATIN1_MIN 0xa0 +#define ZC_LATIN1_MAX 0xff + +/*** File types ***/ + +#define FILE_RESTORE 0 +#define FILE_SAVE 1 +#define FILE_SCRIPT 2 +#define FILE_PLAYBACK 3 +#define FILE_RECORD 4 +#define FILE_LOAD_AUX 5 +#define FILE_SAVE_AUX 6 + +/*** Data access macros ***/ + +#define SET_BYTE(addr,v) { zmp[addr] = v; } +#define LOW_BYTE(addr,v) { v = zmp[addr]; } +#define CODE_BYTE(v) { v = *pcp++; } + +#if defined (AMIGA) + +extern zbyte *pcp; +extern zbyte *zmp; + +#define lo(v) ((zbyte *)&v)[1] +#define hi(v) ((zbyte *)&v)[0] + +#define SET_WORD(addr,v) { zmp[addr] = hi(v); zmp[addr+1] = lo(v); } +#define LOW_WORD(addr,v) { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; } +#define HIGH_WORD(addr,v) { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; } +#define CODE_WORD(v) { hi(v) = *pcp++; lo(v) = *pcp++; } +#define GET_PC(v) { v = pcp - zmp; } +#define SET_PC(v) { pcp = zmp + v; } + +#endif + +/* A bunch of x86 assembly code previously appeared here. */ + +#if !defined (AMIGA) && !defined (MSDOS_16BIT) + +extern zbyte *pcp; +extern zbyte *zmp; + +#define lo(v) (v & 0xff) +#define hi(v) (v >> 8) + +#define SET_WORD(addr,v) { zmp[addr] = hi(v); zmp[addr+1] = lo(v); } +#define LOW_WORD(addr,v) { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; } +#define HIGH_WORD(addr,v) { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; } +#define CODE_WORD(v) { v = ((zword) pcp[0] << 8) | pcp[1]; pcp += 2; } +#define GET_PC(v) { v = pcp - zmp; } +#define SET_PC(v) { pcp = zmp + v; } + +#endif + + +/*** Story file header data ***/ + +extern zbyte h_version; +extern zbyte h_config; +extern zword h_release; +extern zword h_resident_size; +extern zword h_start_pc; +extern zword h_dictionary; +extern zword h_objects; +extern zword h_globals; +extern zword h_dynamic_size; +extern zword h_flags; +extern zbyte h_serial[6]; +extern zword h_abbreviations; +extern zword h_file_size; +extern zword h_checksum; +extern zbyte h_interpreter_number; +extern zbyte h_interpreter_version; +extern zbyte h_screen_rows; +extern zbyte h_screen_cols; +extern zword h_screen_width; +extern zword h_screen_height; +extern zbyte h_font_height; +extern zbyte h_font_width; +extern zword h_functions_offset; +extern zword h_strings_offset; +extern zbyte h_default_background; +extern zbyte h_default_foreground; +extern zword h_terminating_keys; +extern zword h_line_width; +extern zbyte h_standard_high; +extern zbyte h_standard_low; +extern zword h_alphabet; +extern zword h_extension_table; +extern zbyte h_user_name[8]; + +extern zword hx_table_size; +extern zword hx_mouse_x; +extern zword hx_mouse_y; +extern zword hx_unicode_table; + +/*** Various data ***/ + +extern const char *story_name; + +extern enum story story_id; +extern long story_size; + +extern zword stack[STACK_SIZE]; +extern zword *sp; +extern zword *fp; +extern zword frame_count; + +extern zword zargs[8]; +extern int zargc; + +extern bool ostream_screen; +extern bool ostream_script; +extern bool ostream_memory; +extern bool ostream_record; +extern bool istream_replay; +extern bool message; + +extern int cwin; +extern int mwin; + +extern int mouse_x; +extern int mouse_y; + +extern bool enable_wrapping; +extern bool enable_scripting; +extern bool enable_scrolling; +extern bool enable_buffering; + + +extern char *option_zcode_path; /* dg */ + + +/*** Blorb stuff ***/ +/* +bb_err_t blorb_err; +bb_map_t *blorb_map; +*/ + +/*** Z-machine opcodes ***/ + +void z_add (void); +void z_and (void); +void z_art_shift (void); +void z_buffer_mode (void); +void z_call_n (void); +void z_call_s (void); +void z_catch (void); +void z_check_arg_count (void); +void z_check_unicode (void); +void z_clear_attr (void); +void z_copy_table (void); +void z_dec (void); +void z_dec_chk (void); +void z_div (void); +void z_draw_picture (void); +void z_encode_text (void); +void z_erase_line (void); +void z_erase_picture (void); +void z_erase_window (void); +void z_get_child (void); +void z_get_cursor (void); +void z_get_next_prop (void); +void z_get_parent (void); +void z_get_prop (void); +void z_get_prop_addr (void); +void z_get_prop_len (void); +void z_get_sibling (void); +void z_get_wind_prop (void); +void z_inc (void); +void z_inc_chk (void); +void z_input_stream (void); +void z_insert_obj (void); +void z_je (void); +void z_jg (void); +void z_jin (void); +void z_jl (void); +void z_jump (void); +void z_jz (void); +void z_load (void); +void z_loadb (void); +void z_loadw (void); +void z_log_shift (void); +void z_make_menu (void); +void z_mod (void); +void z_mouse_window (void); +void z_move_window (void); +void z_mul (void); +void z_new_line (void); +void z_nop (void); +void z_not (void); +void z_or (void); +void z_output_stream (void); +void z_picture_data (void); +void z_picture_table (void); +void z_piracy (void); +void z_pop (void); +void z_pop_stack (void); +void z_print (void); +void z_print_addr (void); +void z_print_char (void); +void z_print_form (void); +void z_print_num (void); +void z_print_obj (void); +void z_print_paddr (void); +void z_print_ret (void); +void z_print_table (void); +void z_print_unicode (void); +void z_pull (void); +void z_push (void); +void z_push_stack (void); +void z_put_prop (void); +void z_put_wind_prop (void); +void z_quit (void); +void z_random (void); +void z_read (void); +void z_read_char (void); +void z_read_mouse (void); +void z_remove_obj (void); +void z_restart (void); +void z_restore (void); +void z_restore_undo (void); +void z_ret (void); +void z_ret_popped (void); +void z_rfalse (void); +void z_rtrue (void); +void z_save (void); +void z_save_undo (void); +void z_scan_table (void); +void z_scroll_window (void); +void z_set_attr (void); +void z_set_font (void); +void z_set_colour (void); +void z_set_cursor (void); +void z_set_margins (void); +void z_set_window (void); +void z_set_text_style (void); +void z_show_status (void); +void z_sound_effect (void); +void z_split_window (void); +void z_store (void); +void z_storeb (void); +void z_storew (void); +void z_sub (void); +void z_test (void); +void z_test_attr (void); +void z_throw (void); +void z_tokenise (void); +void z_verify (void); +void z_window_size (void); +void z_window_style (void); + +/* Definitions for error handling functions and error codes. */ + +/* extern int err_report_mode; */ + +void init_err (void); +void runtime_error (int); + +/* Error codes */ +#define ERR_TEXT_BUF_OVF 1 /* Text buffer overflow */ +#define ERR_STORE_RANGE 2 /* Store out of dynamic memory */ +#define ERR_DIV_ZERO 3 /* Division by zero */ +#define ERR_ILL_OBJ 4 /* Illegal object */ +#define ERR_ILL_ATTR 5 /* Illegal attribute */ +#define ERR_NO_PROP 6 /* No such property */ +#define ERR_STK_OVF 7 /* Stack overflow */ +#define ERR_ILL_CALL_ADDR 8 /* Call to illegal address */ +#define ERR_CALL_NON_RTN 9 /* Call to non-routine */ +#define ERR_STK_UNDF 10 /* Stack underflow */ +#define ERR_ILL_OPCODE 11 /* Illegal opcode */ +#define ERR_BAD_FRAME 12 /* Bad stack frame */ +#define ERR_ILL_JUMP_ADDR 13 /* Jump to illegal address */ +#define ERR_SAVE_IN_INTER 14 /* Can't save while in interrupt */ +#define ERR_STR3_NESTING 15 /* Nesting stream #3 too deep */ +#define ERR_ILL_WIN 16 /* Illegal window */ +#define ERR_ILL_WIN_PROP 17 /* Illegal window property */ +#define ERR_ILL_PRINT_ADDR 18 /* Print at illegal address */ +#define ERR_MAX_FATAL 18 + +/* Less serious errors */ +#define ERR_JIN_0 19 /* @jin called with object 0 */ +#define ERR_GET_CHILD_0 20 /* @get_child called with object 0 */ +#define ERR_GET_PARENT_0 21 /* @get_parent called with object 0 */ +#define ERR_GET_SIBLING_0 22 /* @get_sibling called with object 0 */ +#define ERR_GET_PROP_ADDR_0 23 /* @get_prop_addr called with object 0 */ +#define ERR_GET_PROP_0 24 /* @get_prop called with object 0 */ +#define ERR_PUT_PROP_0 25 /* @put_prop called with object 0 */ +#define ERR_CLEAR_ATTR_0 26 /* @clear_attr called with object 0 */ +#define ERR_SET_ATTR_0 27 /* @set_attr called with object 0 */ +#define ERR_TEST_ATTR_0 28 /* @test_attr called with object 0 */ +#define ERR_MOVE_OBJECT_0 29 /* @move_object called moving object 0 */ +#define ERR_MOVE_OBJECT_TO_0 30 /* @move_object called moving into object 0 */ +#define ERR_REMOVE_OBJECT_0 31 /* @remove_object called with object 0 */ +#define ERR_GET_NEXT_PROP_0 32 /* @get_next_prop called with object 0 */ +#define ERR_NUM_ERRORS (32) + +/* 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 + ERR_DEFAULT_REPORT_MODE. In any case, the player can + specify a report mode on the command line by typing "-Z 0" + through "-Z 3". */ + +#define ERR_REPORT_NEVER (0) +#define ERR_REPORT_ONCE (1) +#define ERR_REPORT_ALWAYS (2) +#define ERR_REPORT_FATAL (3) + +#define ERR_DEFAULT_REPORT_MODE ERR_REPORT_ONCE + + +/*** Various global functions ***/ + +zchar translate_from_zscii (zbyte); +zbyte translate_to_zscii (zchar); + +void flush_buffer (void); +void new_line (void); +void print_char (zchar); +void print_num (zword); +void print_object (zword); +void print_string (const char *); + +void stream_mssg_on (void); +void stream_mssg_off (void); + +void ret (zword); +void store (zword); +void branch (bool); + +void storeb (zword, zbyte); +void storew (zword, zword); + +/*** Interface functions ***/ + +void os_beep (int); +int os_char_width (zchar); +void os_display_char (zchar); +void os_display_string (const zchar *); +void os_draw_picture (int, int, int); +void os_erase_area (int, int, int, int); +void os_fatal (const char *) __attribute__((noreturn)); +void os_finish_with_sample (int); +int os_font_data (int, int *, int *); +void os_init_screen (void); +void os_more_prompt (void); +int os_peek_colour (void); +bool os_picture_data (int, int *, int *); +void os_prepare_sample (int); +void os_process_arguments (int, char *[]); +int os_random_seed (void); +int os_read_file_name (char *, const char *, int); +zchar os_read_key (int, bool); +zchar os_read_line (int, zchar *, int, int, int); +zchar os_read_mouse (void); +void os_reset_screen (void); +void os_restart_game (int); +void os_scroll_area (int, int, int, int, int); +void os_set_colour (int, int); +void os_set_cursor (int, int); +void os_set_font (int); +void os_set_text_style (int); +void os_start_sample (int, int, int, zword); +void os_stop_sample (int); +int os_string_width (const zchar *); +void os_init_setup (void); +int os_speech_output(const zchar *); + +#include "setup.h" diff --git a/apps/plugins/frotz/frotz.make b/apps/plugins/frotz/frotz.make new file mode 100644 index 0000000000..0bf4370cca --- /dev/null +++ b/apps/plugins/frotz/frotz.make @@ -0,0 +1,21 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# + +FROTZSRCDIR := $(APPSDIR)/plugins/frotz +FROTZBUILDDIR := $(BUILDDIR)/apps/plugins/frotz + +ROCKS += $(FROTZBUILDDIR)/frotz.rock + +FROTZ_SRC := $(call preprocess, $(FROTZSRCDIR)/SOURCES) +FROTZ_OBJ := $(call c2obj, $(FROTZ_SRC)) + +# add source files to OTHER_SRC to get automatic dependencies +OTHER_SRC += $(FROTZ_SRC) + +$(FROTZBUILDDIR)/frotz.rock: $(FROTZ_OBJ) diff --git a/apps/plugins/frotz/frotzplugin.h b/apps/plugins/frotz/frotzplugin.h new file mode 100644 index 0000000000..8caddb470d --- /dev/null +++ b/apps/plugins/frotz/frotzplugin.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2009 Torne Wuff + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef _FROTZPLUGIN_H_ +#define _FROTZPLUGIN_H_ + +#include "plugin.h" + +/* + * pretend stdio.h is implemented. references to FILE * still have to be + * changed to int, and references to NULL into -1, but there are less of those + */ +#define fread(ptr, size, nmemb, stream) rb->read(stream, ptr, size*nmemb) +#define fwrite(ptr, size, nmemb, stream) rb->write(stream, ptr, size*nmemb) +#define fclose(stream) rb->close(stream) +#define fseek(stream, offset, whence) rb->lseek(stream, offset, whence) +#define ftell(stream) rb->lseek(stream, 0, SEEK_CUR) +#define ferror(stream) 0 + +/* + * we need functions for character io + */ +extern int ungetc(int c, int f); +extern int fgetc(int f); +extern int fputc(int c, int f); + +/* + * this is used instead of os_read_key for more prompts and the like + * since the menu can't be used there. + */ +extern void wait_for_key(void); + +/* + * wrappers + */ +#define strchr(s, c) rb->strchr(s, c) +#define strcpy(dest, src) rb->strcpy(dest, src) + +#endif /* _FROTZPLUGIN_H_ */ diff --git a/apps/plugins/frotz/hotkey.c b/apps/plugins/frotz/hotkey.c new file mode 100644 index 0000000000..d214f1f322 --- /dev/null +++ b/apps/plugins/frotz/hotkey.c @@ -0,0 +1,221 @@ +/* hotkey.c - Hot key functions + * Copyright (c) 1995-1997 Stefan Jokisch + * + * Changes for Rockbox copyright 2009 Torne Wuff + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" +#include "lib/pluginlib_exit.h" + +extern int restore_undo (void); + +extern int read_number (void); + +extern bool read_yes_or_no (const char *); + +extern void replay_open (void); +extern void replay_close (void); +extern void record_open (void); +extern void record_close (void); + +extern void seed_random (int); + +/* + * hot_key_debugging + * + * ...allows user to toggle cheating options on/off. + * + */ + +static bool hot_key_debugging (void) +{ + + f_setup.attribute_assignment = read_yes_or_no ("Watch attribute assignment"); + f_setup.attribute_testing = read_yes_or_no ("Watch attribute testing"); + + f_setup.object_movement = read_yes_or_no ("Watch object movement"); + f_setup.object_locating = read_yes_or_no ("Watch object locating"); + + return FALSE; + +}/* hot_key_debugging */ + +/* + * hot_key_playback + * + * ...allows user to turn playback on. + * + */ + +static bool hot_key_playback (void) +{ + + rb->splash(HZ, "Playback on"); + + if (!istream_replay) + replay_open (); + + return FALSE; + +}/* hot_key_playback */ + +/* + * hot_key_recording + * + * ...allows user to turn recording on/off. + * + */ + +static bool hot_key_recording (void) +{ + + if (istream_replay) { + rb->splash(HZ, "Playback off"); + replay_close (); + } else if (ostream_record) { + rb->splash(HZ, "Recording off"); + record_close (); + } else { + rb->splash(HZ, "Recording on"); + record_open (); + } + + return FALSE; + +}/* hot_key_recording */ + +/* + * hot_key_seed + * + * ...allows user to seed the random number seed. + * + */ + +static bool hot_key_seed (void) +{ + + print_string ("Enter seed value (or return to randomize): "); + seed_random (read_number ()); + + return FALSE; + +}/* hot_key_seed */ + +/* + * hot_key_undo + * + * ...allows user to undo the previous turn. + * + */ + +static bool hot_key_undo (void) +{ + + if (restore_undo ()) { + + print_string ("undo\n"); + + if (h_version >= V5) { /* for V5+ games we must */ + store (2); /* store 2 (for success) */ + return TRUE; /* and abort the input */ + } + + if (h_version <= V3) { /* for V3- games we must */ + z_show_status (); /* draw the status line */ + return FALSE; /* and continue input */ + } + + } else rb->splash(HZ, "No undo information available."); + + return FALSE; + +}/* hot_key_undo */ + +/* + * hot_key_restart + * + * ...allows user to start a new game. + * + */ + +static bool hot_key_restart (void) +{ + + if (read_yes_or_no ("Do you wish to restart")) { + + z_restart (); + return TRUE; + + } else return FALSE; + +}/* hot_key_restart */ + +/* + * hot_key_quit + * + * ...allows user to exit the game. + * + */ + +bool hot_key_quit (void) +{ + + if (read_yes_or_no ("Do you wish to quit")) { + + exit(0); + + } else return FALSE; + +}/* hot_key_quit */ + +/* + * handle_hot_key + * + * Perform the action associated with a so-called hot key. Return + * true to abort the current input action. + * + */ + +bool handle_hot_key (zchar key) +{ + + if (cwin == 0) { + + bool aborting; + + aborting = FALSE; + + switch (key) { + case ZC_HKEY_RECORD: aborting = hot_key_recording (); break; + case ZC_HKEY_PLAYBACK: aborting = hot_key_playback (); break; + case ZC_HKEY_SEED: aborting = hot_key_seed (); break; + case ZC_HKEY_UNDO: aborting = hot_key_undo (); break; + case ZC_HKEY_RESTART: aborting = hot_key_restart (); break; + case ZC_HKEY_QUIT: aborting = hot_key_quit (); break; + case ZC_HKEY_DEBUG: aborting = hot_key_debugging (); break; + } + + if (aborting) + return TRUE; + + } + + return FALSE; + +}/* handle_hot_key */ diff --git a/apps/plugins/frotz/input.c b/apps/plugins/frotz/input.c new file mode 100644 index 0000000000..296bffc529 --- /dev/null +++ b/apps/plugins/frotz/input.c @@ -0,0 +1,301 @@ +/* input.c - High level input functions + * Copyright (c) 1995-1997 Stefan Jokisch + * + * Changes for Rockbox copyright 2009 Torne Wuff + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +extern int save_undo (void); + +extern zchar stream_read_key (zword, zword, bool); +extern zchar stream_read_input (int, zchar *, zword, zword, bool, bool); + +extern void tokenise_line (zword, zword, zword, bool); + +/* + * is_terminator + * + * Check if the given key is an input terminator. + * + */ + +bool is_terminator (zchar key) +{ + + if (key == ZC_TIME_OUT) + return TRUE; + if (key == ZC_RETURN) + return TRUE; + if (key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) + return TRUE; + + if (h_terminating_keys != 0) + + if (key >= ZC_ARROW_MIN && key <= ZC_MENU_CLICK) { + + zword addr = h_terminating_keys; + zbyte c; + + do { + LOW_BYTE (addr, c) + if (c == 255 || key == translate_from_zscii (c)) + return TRUE; + addr++; + } while (c != 0); + + } + + return FALSE; + +}/* is_terminator */ + +/* + * z_make_menu, add or remove a menu and branch if successful. + * + * zargs[0] = number of menu + * zargs[1] = table of menu entries or 0 to remove menu + * + */ + +void z_make_menu (void) +{ + + /* This opcode was only used for the Macintosh version of Journey. + It controls menus with numbers greater than 2 (menus 0, 1 and 2 + are system menus). Frotz doesn't implement menus yet. */ + + branch (FALSE); + +}/* z_make_menu */ + +extern bool read_yes_or_no (const char *s); + +/* + * read_string + * + * Read a string from the current input stream. + * + */ + +void read_string (int max, zchar *buffer) +{ + zchar key; + + buffer[0] = 0; + + do { + + key = stream_read_input (max, buffer, 0, 0, FALSE, FALSE); + + } while (key != ZC_RETURN); + +}/* read_string */ + +/* + * read_number + * + * Ask the user to type in a number and return it. + * + */ + +int read_number (void) +{ + zchar buffer[6]; + int value = 0; + int i; + + read_string (5, buffer); + + for (i = 0; buffer[i] != 0; i++) + if (buffer[i] >= '0' && buffer[i] <= '9') + value = 10 * value + buffer[i] - '0'; + + return value; + +}/* read_number */ + +/* + * z_read, read a line of input and (in V5+) store the terminating key. + * + * zargs[0] = address of text buffer + * zargs[1] = address of token buffer + * zargs[2] = timeout in tenths of a second (optional) + * zargs[3] = packed address of routine to be called on timeout + * + */ + +void z_read (void) +{ + zchar buffer[INPUT_BUFFER_SIZE]; + zword addr; + zchar key; + zbyte max, size; + zbyte c; + int i; + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + + /* Get maximum input size */ + + addr = zargs[0]; + + LOW_BYTE (addr, max) + + if (h_version <= V4) + max--; + + if (max >= INPUT_BUFFER_SIZE) + max = INPUT_BUFFER_SIZE - 1; + + /* Get initial input size */ + + if (h_version >= V5) { + addr++; + LOW_BYTE (addr, size) + } else size = 0; + + /* Copy initial input to local buffer */ + + for (i = 0; i < size; i++) { + addr++; + LOW_BYTE (addr, c) + buffer[i] = translate_from_zscii (c); + } + + buffer[i] = 0; + + /* Draw status line for V1 to V3 games */ + + if (h_version <= V3) + z_show_status (); + + /* Read input from current input stream */ + + key = stream_read_input ( + max, buffer, /* buffer and size */ + zargs[2], /* timeout value */ + zargs[3], /* timeout routine */ + TRUE, /* enable hot keys */ + h_version == V6); /* no script in V6 */ + + if (key == ZC_BAD) + return; + + /* Perform save_undo for V1 to V4 games */ + + if (h_version <= V4) + save_undo (); + + /* Copy local buffer back to dynamic memory */ + + for (i = 0; buffer[i] != 0; i++) { + + if (key == ZC_RETURN) { + + if (buffer[i] >= 'A' && buffer[i] <= 'Z') + buffer[i] += 'a' - 'A'; + if (buffer[i] >= 0xc0 && buffer[i] <= 0xde && buffer[i] != 0xd7) + buffer[i] += 0x20; + + } + + storeb ((zword) (zargs[0] + ((h_version <= V4) ? 1 : 2) + i), translate_to_zscii (buffer[i])); + + } + + /* Add null character (V1-V4) or write input length into 2nd byte */ + + if (h_version <= V4) + storeb ((zword) (zargs[0] + 1 + i), 0); + else + storeb ((zword) (zargs[0] + 1), i); + + /* Tokenise line if a token buffer is present */ + + if (key == ZC_RETURN && zargs[1] != 0) + tokenise_line (zargs[0], zargs[1], 0, FALSE); + + /* Store key */ + + if (h_version >= V5) + store (translate_to_zscii (key)); + +}/* z_read */ + +/* + * z_read_char, read and store a key. + * + * zargs[0] = input device (must be 1) + * zargs[1] = timeout in tenths of a second (optional) + * zargs[2] = packed address of routine to be called on timeout + * + */ + +void z_read_char (void) +{ + zchar key; + + /* Supply default arguments */ + + if (zargc < 2) + zargs[1] = 0; + + /* Read input from the current input stream */ + + key = stream_read_key ( + zargs[1], /* timeout value */ + zargs[2], /* timeout routine */ + TRUE); /* enable hot keys */ + + if (key == ZC_BAD) + return; + + /* Store key */ + + store (translate_to_zscii (key)); + +}/* z_read_char */ + +/* + * z_read_mouse, write the current mouse status into a table. + * + * zargs[0] = address of table + * + */ + +void z_read_mouse (void) +{ + zword btn; + + /* Read the mouse position and which buttons are down */ + + btn = os_read_mouse (); + hx_mouse_y = mouse_y; + hx_mouse_x = mouse_x; + + storew ((zword) (zargs[0] + 0), hx_mouse_y); + storew ((zword) (zargs[0] + 2), hx_mouse_x); + storew ((zword) (zargs[0] + 4), btn); /* mouse button bits */ + storew ((zword) (zargs[0] + 6), 0); /* menu selection */ + +}/* z_read_mouse */ diff --git a/apps/plugins/frotz/main.c b/apps/plugins/frotz/main.c new file mode 100644 index 0000000000..22c021bfd1 --- /dev/null +++ b/apps/plugins/frotz/main.c @@ -0,0 +1,205 @@ +/* main.c - Frotz V2.40 main function + * Copyright (c) 1995-1997 Stefan Jokisch + * + * Changes for Rockbox copyright 2009 Torne Wuff + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* + * This is an interpreter for Infocom V1 to V6 games. It also supports + * the recently defined V7 and V8 games. Please report bugs to + * + * s.jokisch@avu.de + * + */ + +#include "frotz.h" + +#ifndef MSDOS_16BIT +#define cdecl +#endif + +extern void interpret (void); +extern void init_memory (void); +extern void init_undo (void); +extern void reset_memory (void); +extern void init_buffer (void); +extern void init_sound (void); +extern void init_process (void); +extern void script_close (void); +extern void record_close (void); +extern void replay_close (void); + +/* Story file name, id number and size */ + +const char *story_name = 0; + +enum story story_id = UNKNOWN; +long story_size = 0; + +/* Story file header data */ + +zbyte h_version = 0; +zbyte h_config = 0; +zword h_release = 0; +zword h_resident_size = 0; +zword h_start_pc = 0; +zword h_dictionary = 0; +zword h_objects = 0; +zword h_globals = 0; +zword h_dynamic_size = 0; +zword h_flags = 0; +zbyte h_serial[6] = { 0, 0, 0, 0, 0, 0 }; +zword h_abbreviations = 0; +zword h_file_size = 0; +zword h_checksum = 0; +zbyte h_interpreter_number = 0; +zbyte h_interpreter_version = 0; +zbyte h_screen_rows = 0; +zbyte h_screen_cols = 0; +zword h_screen_width = 0; +zword h_screen_height = 0; +zbyte h_font_height = 1; +zbyte h_font_width = 1; +zword h_functions_offset = 0; +zword h_strings_offset = 0; +zbyte h_default_background = 0; +zbyte h_default_foreground = 0; +zword h_terminating_keys = 0; +zword h_line_width = 0; +zbyte h_standard_high = 1; +zbyte h_standard_low = 0; +zword h_alphabet = 0; +zword h_extension_table = 0; +zbyte h_user_name[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +zword hx_table_size = 0; +zword hx_mouse_x = 0; +zword hx_mouse_y = 0; +zword hx_unicode_table = 0; + +/* Stack data */ + +zword stack[STACK_SIZE]; +zword *sp = 0; +zword *fp = 0; +zword frame_count = 0; + +/* IO streams */ + +bool ostream_screen = TRUE; +bool ostream_script = FALSE; +bool ostream_memory = FALSE; +bool ostream_record = FALSE; +bool istream_replay = FALSE; +bool message = FALSE; + +/* Current window and mouse data */ + +int cwin = 0; +int mwin = 0; + +int mouse_y = 0; +int mouse_x = 0; + +/* Window attributes */ + +bool enable_wrapping = FALSE; +bool enable_scripting = FALSE; +bool enable_scrolling = FALSE; +bool enable_buffering = FALSE; + +/* User options */ + +/* +int option_attribute_assignment = 0; +int option_attribute_testing = 0; +int option_context_lines = 0; +int option_object_locating = 0; +int option_object_movement = 0; +int option_left_margin = 0; +int option_right_margin = 0; +int option_ignore_errors = 0; +int option_piracy = 0; +int option_undo_slots = MAX_UNDO_SLOTS; +int option_expand_abbreviations = 0; +int option_script_cols = 80; +int option_save_quetzal = 1; +*/ + +int option_sound = 1; +char *option_zcode_path; + + +/* + * z_piracy, branch if the story file is a legal copy. + * + * no zargs used + * + */ + +void z_piracy (void) +{ + + branch (!f_setup.piracy); + +}/* z_piracy */ + +/* + * main + * + * Prepare and run the game. + * + */ + +int cdecl frotz_main (void) +{ + + os_init_setup (); + + init_buffer (); + + init_err (); + + init_memory (); + + init_process (); + + init_sound (); + + os_init_screen (); + + init_undo (); + + z_restart (); + + interpret (); + + script_close (); + + record_close (); + + replay_close (); + + reset_memory (); + + os_reset_screen (); + + return 0; + +}/* main */ diff --git a/apps/plugins/frotz/math.c b/apps/plugins/frotz/math.c new file mode 100644 index 0000000000..5ff5163230 --- /dev/null +++ b/apps/plugins/frotz/math.c @@ -0,0 +1,261 @@ +/* math.c - Arithmetic, compare and logical opcodes + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +/* + * z_add, 16bit addition. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_add (void) +{ + + store ((zword) ((short) zargs[0] + (short) zargs[1])); + +}/* z_add */ + +/* + * z_and, bitwise AND operation. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_and (void) +{ + + store ((zword) (zargs[0] & zargs[1])); + +}/* z_and */ + +/* + * z_art_shift, arithmetic SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right + * + */ + +void z_art_shift (void) +{ + + if ((short) zargs[1] > 0) + store ((zword) ((short) zargs[0] << (short) zargs[1])); + else + store ((zword) ((short) zargs[0] >> - (short) zargs[1])); + +}/* z_art_shift */ + +/* + * z_div, signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_div (void) +{ + + if (zargs[1] == 0) + runtime_error (ERR_DIV_ZERO); + + store ((zword) ((short) zargs[0] / (short) zargs[1])); + +}/* z_div */ + +/* + * z_je, branch if the first value equals any of the following. + * + * zargs[0] = first value + * zargs[1] = second value (optional) + * ... + * zargs[3] = fourth value (optional) + * + */ + +void z_je (void) +{ + + branch ( + zargc > 1 && (zargs[0] == zargs[1] || ( + zargc > 2 && (zargs[0] == zargs[2] || ( + zargc > 3 && (zargs[0] == zargs[3])))))); + +}/* z_je */ + +/* + * z_jg, branch if the first value is greater than the second. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_jg (void) +{ + + branch ((short) zargs[0] > (short) zargs[1]); + +}/* z_jg */ + +/* + * z_jl, branch if the first value is less than the second. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_jl (void) +{ + + branch ((short) zargs[0] < (short) zargs[1]); + +}/* z_jl */ + +/* + * z_jz, branch if value is zero. + * + * zargs[0] = value + * + */ + +void z_jz (void) +{ + + branch ((short) zargs[0] == 0); + +}/* z_jz */ + +/* + * z_log_shift, logical SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right (negative) + * + */ + +void z_log_shift (void) +{ + + if ((short) zargs[1] > 0) + store ((zword) (zargs[0] << (short) zargs[1])); + else + store ((zword) (zargs[0] >> - (short) zargs[1])); + +}/* z_log_shift */ + +/* + * z_mod, remainder after signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_mod (void) +{ + + if (zargs[1] == 0) + runtime_error (ERR_DIV_ZERO); + + store ((zword) ((short) zargs[0] % (short) zargs[1])); + +}/* z_mod */ + +/* + * z_mul, 16bit multiplication. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_mul (void) +{ + + store ((zword) ((short) zargs[0] * (short) zargs[1])); + +}/* z_mul */ + +/* + * z_not, bitwise NOT operation. + * + * zargs[0] = value + * + */ + +void z_not (void) +{ + + store ((zword) ~zargs[0]); + +}/* z_not */ + +/* + * z_or, bitwise OR operation. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_or (void) +{ + + store ((zword) (zargs[0] | zargs[1])); + +}/* z_or */ + +/* + * z_sub, 16bit substraction. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_sub (void) +{ + + store ((zword) ((short) zargs[0] - (short) zargs[1])); + +}/* z_sub */ + +/* + * z_test, branch if all the flags of a bit mask are set in a value. + * + * zargs[0] = value to be examined + * zargs[1] = bit mask + * + */ + +void z_test (void) +{ + + branch ((zargs[0] & zargs[1]) == zargs[1]); + +}/* z_test */ diff --git a/apps/plugins/frotz/object.c b/apps/plugins/frotz/object.c new file mode 100644 index 0000000000..676b6c93c5 --- /dev/null +++ b/apps/plugins/frotz/object.c @@ -0,0 +1,1003 @@ +/* object.c - Object manipulation opcodes + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +#define MAX_OBJECT 2000 + +#define O1_PARENT 4 +#define O1_SIBLING 5 +#define O1_CHILD 6 +#define O1_PROPERTY_OFFSET 7 +#define O1_SIZE 9 + +#define O4_PARENT 6 +#define O4_SIBLING 8 +#define O4_CHILD 10 +#define O4_PROPERTY_OFFSET 12 +#define O4_SIZE 14 + +/* + * object_address + * + * Calculate the address of an object. + * + */ + +static zword object_address (zword obj) +{ +/* zchar obj_num[10]; */ + + /* Check object number */ + + if (obj > ((h_version <= V3) ? 255 : MAX_OBJECT)) { + print_string("@Attempt to address illegal object "); + print_num(obj); + print_string(". This is normally fatal."); + new_line(); + runtime_error (ERR_ILL_OBJ); + } + + /* Return object address */ + + if (h_version <= V3) + return h_objects + ((obj - 1) * O1_SIZE + 62); + else + return h_objects + ((obj - 1) * O4_SIZE + 126); + +}/* object_address */ + +/* + * object_name + * + * Return the address of the given object's name. + * + */ + +zword object_name (zword object) +{ + zword obj_addr; + zword name_addr; + + obj_addr = object_address (object); + + /* The object name address is found at the start of the properties */ + + if (h_version <= V3) + obj_addr += O1_PROPERTY_OFFSET; + else + obj_addr += O4_PROPERTY_OFFSET; + + LOW_WORD (obj_addr, name_addr) + + return name_addr; + +}/* object_name */ + +/* + * first_property + * + * Calculate the start address of the property list associated with + * an object. + * + */ + +static zword first_property (zword obj) +{ + zword prop_addr; + zbyte size; + + /* Fetch address of object name */ + + prop_addr = object_name (obj); + + /* Get length of object name */ + + LOW_BYTE (prop_addr, size) + + /* Add name length to pointer */ + + return prop_addr + 1 + 2 * size; + +}/* first_property */ + +/* + * next_property + * + * Calculate the address of the next property in a property list. + * + */ + +static zword next_property (zword prop_addr) +{ + zbyte value; + + /* Load the current property id */ + + LOW_BYTE (prop_addr, value) + prop_addr++; + + /* Calculate the length of this property */ + + if (h_version <= V3) + value >>= 5; + else if (!(value & 0x80)) + value >>= 6; + else { + + LOW_BYTE (prop_addr, value) + value &= 0x3f; + + if (value == 0) value = 64; /* demanded by Spec 1.0 */ + + } + + /* Add property length to current property pointer */ + + return prop_addr + value + 1; + +}/* next_property */ + +/* + * unlink_object + * + * Unlink an object from its parent and siblings. + * + */ + +static void unlink_object (zword object) +{ + zword obj_addr; + zword parent_addr; + zword sibling_addr; + + if (object == 0) { + runtime_error (ERR_REMOVE_OBJECT_0); + return; + } + + obj_addr = object_address (object); + + if (h_version <= V3) { + + zbyte parent; + zbyte younger_sibling; + zbyte older_sibling; + zbyte zero = 0; + + /* Get parent of object, and return if no parent */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + if (!parent) + return; + + /* Get (older) sibling of object and set both parent and sibling + pointers to 0 */ + + SET_BYTE (obj_addr, zero) + obj_addr += O1_SIBLING - O1_PARENT; + LOW_BYTE (obj_addr, older_sibling) + SET_BYTE (obj_addr, zero) + + /* Get first child of parent (the youngest sibling of the object) */ + + parent_addr = object_address (parent) + O1_CHILD; + LOW_BYTE (parent_addr, younger_sibling) + + /* Remove object from the list of siblings */ + + if (younger_sibling == object) + SET_BYTE (parent_addr, older_sibling) + else { + do { + sibling_addr = object_address (younger_sibling) + O1_SIBLING; + LOW_BYTE (sibling_addr, younger_sibling) + } while (younger_sibling != object); + SET_BYTE (sibling_addr, older_sibling) + } + + } else { + + zword parent; + zword younger_sibling; + zword older_sibling; + zword zero = 0; + + /* Get parent of object, and return if no parent */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + if (!parent) + return; + + /* Get (older) sibling of object and set both parent and sibling + pointers to 0 */ + + SET_WORD (obj_addr, zero) + obj_addr += O4_SIBLING - O4_PARENT; + LOW_WORD (obj_addr, older_sibling) + SET_WORD (obj_addr, zero) + + /* Get first child of parent (the youngest sibling of the object) */ + + parent_addr = object_address (parent) + O4_CHILD; + LOW_WORD (parent_addr, younger_sibling) + + /* Remove object from the list of siblings */ + + if (younger_sibling == object) + SET_WORD (parent_addr, older_sibling) + else { + do { + sibling_addr = object_address (younger_sibling) + O4_SIBLING; + LOW_WORD (sibling_addr, younger_sibling) + } while (younger_sibling != object); + SET_WORD (sibling_addr, older_sibling) + } + + } + +}/* unlink_object */ + +/* + * z_clear_attr, clear an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to be cleared + * + */ + +void z_clear_attr (void) +{ + zword obj_addr; + zbyte value; + + if (story_id == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error (ERR_ILL_ATTR); + + /* If we are monitoring attribute assignment display a short note */ + + if (f_setup.attribute_assignment) { + stream_mssg_on (); + print_string ("@clear_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_CLEAR_ATTR_0); + return; + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Clear attribute bit */ + + LOW_BYTE (obj_addr, value) + value &= ~(0x80 >> (zargs[1] & 7)); + SET_BYTE (obj_addr, value) + +}/* z_clear_attr */ + +/* + * z_jin, branch if the first object is inside the second. + * + * zargs[0] = first object + * zargs[1] = second object + * + */ + +void z_jin (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (f_setup.object_locating) { + stream_mssg_on (); + print_string ("@jin "); + print_object (zargs[0]); + print_string (" "); + print_object (zargs[1]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_JIN_0); + branch (0 == zargs[1]); + return; + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte parent; + + /* Get parent id from object */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + + /* Branch if the parent is obj2 */ + + branch (parent == zargs[1]); + + } else { + + zword parent; + + /* Get parent id from object */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + + /* Branch if the parent is obj2 */ + + branch (parent == zargs[1]); + + } + +}/* z_jin */ + +/* + * z_get_child, store the child of an object. + * + * zargs[0] = object + * + */ + +void z_get_child (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (f_setup.object_locating) { + stream_mssg_on (); + print_string ("@get_child "); + print_object (zargs[0]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_GET_CHILD_0); + store (0); + branch (FALSE); + return; + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte child; + + /* Get child id from object */ + + obj_addr += O1_CHILD; + LOW_BYTE (obj_addr, child) + + /* Store child id and branch */ + + store (child); + branch (child); + + } else { + + zword child; + + /* Get child id from object */ + + obj_addr += O4_CHILD; + LOW_WORD (obj_addr, child) + + /* Store child id and branch */ + + store (child); + branch (child); + + } + +}/* z_get_child */ + +/* + * z_get_next_prop, store the number of the first or next property. + * + * zargs[0] = object + * zargs[1] = address of current property (0 gets the first property) + * + */ + +void z_get_next_prop (void) +{ + zword prop_addr; + zbyte value; + zbyte mask; + + if (zargs[0] == 0) { + runtime_error (ERR_GET_NEXT_PROP_0); + store (0); + return; + } + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + if (zargs[1] != 0) { + + /* Scan down the property list */ + + do { + LOW_BYTE (prop_addr, value) + prop_addr = next_property (prop_addr); + } while ((value & mask) > zargs[1]); + + /* Exit if the property does not exist */ + + if ((value & mask) != zargs[1]) + runtime_error (ERR_NO_PROP); + + } + + /* Return the property id */ + + LOW_BYTE (prop_addr, value) + store ((zword) (value & mask)); + +}/* z_get_next_prop */ + +/* + * z_get_parent, store the parent of an object. + * + * zargs[0] = object + * + */ + +void z_get_parent (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (f_setup.object_locating) { + stream_mssg_on (); + print_string ("@get_parent "); + print_object (zargs[0]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_GET_PARENT_0); + store (0); + return; + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte parent; + + /* Get parent id from object */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + + /* Store parent */ + + store (parent); + + } else { + + zword parent; + + /* Get parent id from object */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + + /* Store parent */ + + store (parent); + + } + +}/* z_get_parent */ + +/* + * z_get_prop, store the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + * + */ + +void z_get_prop (void) +{ + zword prop_addr; + zword wprop_val; + zbyte bprop_val; + zbyte value; + zbyte mask; + + if (zargs[0] == 0) { + runtime_error (ERR_GET_PROP_0); + store (0); + return; + } + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + if ((value & mask) == zargs[1]) { /* property found */ + + /* Load property (byte or word sized) */ + + prop_addr++; + + if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) { + + LOW_BYTE (prop_addr, bprop_val) + wprop_val = bprop_val; + + } else LOW_WORD (prop_addr, wprop_val) + + } else { /* property not found */ + + /* Load default value */ + + prop_addr = h_objects + 2 * (zargs[1] - 1); + LOW_WORD (prop_addr, wprop_val) + + } + + /* Store the property value */ + + store (wprop_val); + +}/* z_get_prop */ + +/* + * z_get_prop_addr, store the address of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + * + */ + +void z_get_prop_addr (void) +{ + zword prop_addr; + zbyte value; + zbyte mask; + + if (zargs[0] == 0) { + runtime_error (ERR_GET_PROP_ADDR_0); + store (0); + return; + } + + if (story_id == BEYOND_ZORK) + if (zargs[0] > MAX_OBJECT) + { store (0); return; } + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + /* Calculate the property address or return zero */ + + if ((value & mask) == zargs[1]) { + + if (h_version >= V4 && (value & 0x80)) + prop_addr++; + store ((zword) (prop_addr + 1)); + + } else store (0); + +}/* z_get_prop_addr */ + +/* + * z_get_prop_len, store the length of an object property. + * + * zargs[0] = address of property to be examined + * + */ + +void z_get_prop_len (void) +{ + zword addr; + zbyte value; + + /* Back up the property pointer to the property id */ + + addr = zargs[0] - 1; + LOW_BYTE (addr, value) + + /* Calculate length of property */ + + if (h_version <= V3) + value = (value >> 5) + 1; + else if (!(value & 0x80)) + value = (value >> 6) + 1; + else { + + value &= 0x3f; + + if (value == 0) value = 64; /* demanded by Spec 1.0 */ + + } + + /* Store length of property */ + + store (value); + +}/* z_get_prop_len */ + +/* + * z_get_sibling, store the sibling of an object. + * + * zargs[0] = object + * + */ + +void z_get_sibling (void) +{ + zword obj_addr; + + if (zargs[0] == 0) { + runtime_error (ERR_GET_SIBLING_0); + store (0); + branch (FALSE); + return; + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte sibling; + + /* Get sibling id from object */ + + obj_addr += O1_SIBLING; + LOW_BYTE (obj_addr, sibling) + + /* Store sibling and branch */ + + store (sibling); + branch (sibling); + + } else { + + zword sibling; + + /* Get sibling id from object */ + + obj_addr += O4_SIBLING; + LOW_WORD (obj_addr, sibling) + + /* Store sibling and branch */ + + store (sibling); + branch (sibling); + + } + +}/* z_get_sibling */ + +/* + * z_insert_obj, make an object the first child of another object. + * + * zargs[0] = object to be moved + * zargs[1] = destination object + * + */ + +void z_insert_obj (void) +{ + zword obj1 = zargs[0]; + zword obj2 = zargs[1]; + zword obj1_addr; + zword obj2_addr; + + /* If we are monitoring object movements display a short note */ + + if (f_setup.object_movement) { + stream_mssg_on (); + print_string ("@move_obj "); + print_object (obj1); + print_string (" "); + print_object (obj2); + stream_mssg_off (); + } + + if (obj1 == 0) { + runtime_error (ERR_MOVE_OBJECT_0); + return; + } + + if (obj2 == 0) { + runtime_error (ERR_MOVE_OBJECT_TO_0); + return; + } + + /* Get addresses of both objects */ + + obj1_addr = object_address (obj1); + obj2_addr = object_address (obj2); + + /* Remove object 1 from current parent */ + + unlink_object (obj1); + + /* Make object 1 first child of object 2 */ + + if (h_version <= V3) { + + zbyte child; + + obj1_addr += O1_PARENT; + SET_BYTE (obj1_addr, obj2) + obj2_addr += O1_CHILD; + LOW_BYTE (obj2_addr, child) + SET_BYTE (obj2_addr, obj1) + obj1_addr += O1_SIBLING - O1_PARENT; + SET_BYTE (obj1_addr, child) + + } else { + + zword child; + + obj1_addr += O4_PARENT; + SET_WORD (obj1_addr, obj2) + obj2_addr += O4_CHILD; + LOW_WORD (obj2_addr, child) + SET_WORD (obj2_addr, obj1) + obj1_addr += O4_SIBLING - O4_PARENT; + SET_WORD (obj1_addr, child) + + } + +}/* z_insert_obj */ + +/* + * z_put_prop, set the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to set + * zargs[2] = value to set property to + * + */ + +void z_put_prop (void) +{ + zword prop_addr; + zword value; + zbyte mask; + + if (zargs[0] == 0) { + runtime_error (ERR_PUT_PROP_0); + return; + } + + /* Property id is in bottom five or six bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + /* Exit if the property does not exist */ + + if ((value & mask) != zargs[1]) + runtime_error (ERR_NO_PROP); + + /* Store the new property value (byte or word sized) */ + + prop_addr++; + + if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) { + zbyte v = zargs[2]; + SET_BYTE (prop_addr, v) + } else { + zword v = zargs[2]; + SET_WORD (prop_addr, v) + } + +}/* z_put_prop */ + +/* + * z_remove_obj, unlink an object from its parent and siblings. + * + * zargs[0] = object + * + */ + +void z_remove_obj (void) +{ + + /* If we are monitoring object movements display a short note */ + + if (f_setup.object_movement) { + stream_mssg_on (); + print_string ("@remove_obj "); + print_object (zargs[0]); + stream_mssg_off (); + } + + /* Call unlink_object to do the job */ + + unlink_object (zargs[0]); + +}/* z_remove_obj */ + +/* + * z_set_attr, set an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to set + * + */ + +void z_set_attr (void) +{ + zword obj_addr; + zbyte value; + + if (story_id == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error (ERR_ILL_ATTR); + + /* If we are monitoring attribute assignment display a short note */ + + if (f_setup.attribute_assignment) { + stream_mssg_on (); + print_string ("@set_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_SET_ATTR_0); + return; + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Load attribute byte */ + + LOW_BYTE (obj_addr, value) + + /* Set attribute bit */ + + value |= 0x80 >> (zargs[1] & 7); + + /* Store attribute byte */ + + SET_BYTE (obj_addr, value) + +}/* z_set_attr */ + +/* + * z_test_attr, branch if an object attribute is set. + * + * zargs[0] = object + * zargs[1] = number of attribute to test + * + */ + +void z_test_attr (void) +{ + zword obj_addr; + zbyte value; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error (ERR_ILL_ATTR); + + /* If we are monitoring attribute testing display a short note */ + + if (f_setup.attribute_testing) { + stream_mssg_on (); + print_string ("@test_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_TEST_ATTR_0); + branch (FALSE); + return; + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Load attribute byte */ + + LOW_BYTE (obj_addr, value) + + /* Test attribute */ + + branch (value & (0x80 >> (zargs[1] & 7))); + +}/* z_test_attr */ diff --git a/apps/plugins/frotz/process.c b/apps/plugins/frotz/process.c new file mode 100644 index 0000000000..99ad82ca85 --- /dev/null +++ b/apps/plugins/frotz/process.c @@ -0,0 +1,798 @@ +/* process.c - Interpreter loop and program control + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +#ifdef DJGPP +#include "djfrotz.h" +#endif + + +zword zargs[8]; +int zargc; + +static int finished = 0; + +static void __extended__ (void); +static void __illegal__ (void); + +void (*op0_opcodes[0x10]) (void) = { + z_rtrue, + z_rfalse, + z_print, + z_print_ret, + z_nop, + z_save, + z_restore, + z_restart, + z_ret_popped, + z_catch, + z_quit, + z_new_line, + z_show_status, + z_verify, + __extended__, + z_piracy +}; + +void (*op1_opcodes[0x10]) (void) = { + z_jz, + z_get_sibling, + z_get_child, + z_get_parent, + z_get_prop_len, + z_inc, + z_dec, + z_print_addr, + z_call_s, + z_remove_obj, + z_print_obj, + z_ret, + z_jump, + z_print_paddr, + z_load, + z_call_n +}; + +void (*var_opcodes[0x40]) (void) = { + __illegal__, + z_je, + z_jl, + z_jg, + z_dec_chk, + z_inc_chk, + z_jin, + z_test, + z_or, + z_and, + z_test_attr, + z_set_attr, + z_clear_attr, + z_store, + z_insert_obj, + z_loadw, + z_loadb, + z_get_prop, + z_get_prop_addr, + z_get_next_prop, + z_add, + z_sub, + z_mul, + z_div, + z_mod, + z_call_s, + z_call_n, + z_set_colour, + z_throw, + __illegal__, + __illegal__, + __illegal__, + z_call_s, + z_storew, + z_storeb, + z_put_prop, + z_read, + z_print_char, + z_print_num, + z_random, + z_push, + z_pull, + z_split_window, + z_set_window, + z_call_s, + z_erase_window, + z_erase_line, + z_set_cursor, + z_get_cursor, + z_set_text_style, + z_buffer_mode, + z_output_stream, + z_input_stream, + z_sound_effect, + z_read_char, + z_scan_table, + z_not, + z_call_n, + z_call_n, + z_tokenise, + z_encode_text, + z_copy_table, + z_print_table, + z_check_arg_count +}; + +void (*ext_opcodes[0x1d]) (void) = { + z_save, + z_restore, + z_log_shift, + z_art_shift, + z_set_font, + z_draw_picture, + z_picture_data, + z_erase_picture, + z_set_margins, + z_save_undo, + z_restore_undo, + z_print_unicode, + z_check_unicode, + __illegal__, + __illegal__, + __illegal__, + z_move_window, + z_window_size, + z_window_style, + z_get_wind_prop, + z_scroll_window, + z_pop_stack, + z_read_mouse, + z_mouse_window, + z_push_stack, + z_put_wind_prop, + z_print_form, + z_make_menu, + z_picture_table +}; + + +/* + * init_process + * + * Initialize process variables. + * + */ + +void init_process (void) +{ + finished = 0; +} /* init_process */ + + +/* + * load_operand + * + * Load an operand, either a variable or a constant. + * + */ + +static void load_operand (zbyte type) +{ + zword value; + + if (type & 2) { /* variable */ + + zbyte variable; + + CODE_BYTE (variable) + + if (variable == 0) + value = *sp++; + else if (variable < 16) + value = *(fp - variable); + else { + zword addr = h_globals + 2 * (variable - 16); + LOW_WORD (addr, value) + } + + } else if (type & 1) { /* small constant */ + + zbyte bvalue; + + CODE_BYTE (bvalue) + value = bvalue; + + } else CODE_WORD (value) /* large constant */ + + zargs[zargc++] = value; + +}/* load_operand */ + +/* + * load_all_operands + * + * Given the operand specifier byte, load all (up to four) operands + * for a VAR or EXT opcode. + * + */ + +static void load_all_operands (zbyte specifier) +{ + int i; + + for (i = 6; i >= 0; i -= 2) { + + zbyte type = (specifier >> i) & 0x03; + + if (type == 3) + break; + + load_operand (type); + + } + +}/* load_all_operands */ + +/* + * interpret + * + * Z-code interpreter main loop + * + */ + +void interpret (void) +{ + + do { + + zbyte opcode; + + CODE_BYTE (opcode) + + zargc = 0; + + if (opcode < 0x80) { /* 2OP opcodes */ + + load_operand ((zbyte) (opcode & 0x40) ? 2 : 1); + load_operand ((zbyte) (opcode & 0x20) ? 2 : 1); + + var_opcodes[opcode & 0x1f] (); + + } else if (opcode < 0xb0) { /* 1OP opcodes */ + + load_operand ((zbyte) (opcode >> 4)); + + op1_opcodes[opcode & 0x0f] (); + + } else if (opcode < 0xc0) { /* 0OP opcodes */ + + op0_opcodes[opcode - 0xb0] (); + + } else { /* VAR opcodes */ + + zbyte specifier1; + zbyte specifier2; + + if (opcode == 0xec || opcode == 0xfa) { /* opcodes 0xec */ + CODE_BYTE (specifier1) /* and 0xfa are */ + CODE_BYTE (specifier2) /* call opcodes */ + load_all_operands (specifier1); /* with up to 8 */ + load_all_operands (specifier2); /* arguments */ + } else { + CODE_BYTE (specifier1) + load_all_operands (specifier1); + } + + var_opcodes[opcode - 0xc0] (); + + } + +#if defined(DJGPP) && defined(SOUND_SUPPORT) + if (end_of_sound_flag) + end_of_sound (); +#endif + + } while (finished == 0); + + finished--; + +}/* interpret */ + +/* + * call + * + * Call a subroutine. Save PC and FP then load new PC and initialise + * new stack frame. Note that the caller may legally provide less or + * more arguments than the function actually has. The call type "ct" + * can be 0 (z_call_s), 1 (z_call_n) or 2 (direct call). + * + */ + +void call (zword routine, int argc, zword *args, int ct) +{ + long pc; + zword value; + zbyte count; + int i; + + if (sp - stack < 4) + runtime_error (ERR_STK_OVF); + + GET_PC (pc) + + *--sp = (zword) (pc >> 9); + *--sp = (zword) (pc & 0x1ff); + *--sp = (zword) (fp - stack - 1); + *--sp = (zword) (argc | (ct << (f_setup.save_quetzal ? 12 : 8))); + + fp = sp; + frame_count++; + + /* Calculate byte address of routine */ + + if (h_version <= V3) + pc = (long) routine << 1; + else if (h_version <= V5) + pc = (long) routine << 2; + else if (h_version <= V7) + pc = ((long) routine << 2) + ((long) h_functions_offset << 3); + else /* h_version == V8 */ + pc = (long) routine << 3; + + if (pc >= story_size) + runtime_error (ERR_ILL_CALL_ADDR); + + SET_PC (pc) + + /* Initialise local variables */ + + CODE_BYTE (count) + + if (count > 15) + runtime_error (ERR_CALL_NON_RTN); + if (sp - stack < count) + runtime_error (ERR_STK_OVF); + + if (f_setup.save_quetzal) + fp[0] |= (zword) count << 8; /* Save local var count for Quetzal. */ + + value = 0; + + for (i = 0; i < count; i++) { + + if (h_version <= V4) /* V1 to V4 games provide default */ + CODE_WORD (value) /* values for all local variables */ + + *--sp = (zword) ((argc-- > 0) ? args[i] : value); + + } + + /* Start main loop for direct calls */ + + if (ct == 2) + interpret (); + +}/* call */ + +/* + * ret + * + * Return from the current subroutine and restore the previous stack + * frame. The result may be stored (0), thrown away (1) or pushed on + * the stack (2). In the latter case a direct call has been finished + * and we must exit the interpreter loop. + * + */ + +void ret (zword value) +{ + long pc; + int ct; + + if (sp > fp) + runtime_error (ERR_STK_UNDF); + + sp = fp; + + ct = *sp++ >> (f_setup.save_quetzal ? 12 : 8); + frame_count--; + fp = stack + 1 + *sp++; + pc = *sp++; + pc = ((long) *sp++ << 9) | pc; + + SET_PC (pc) + + /* Handle resulting value */ + + if (ct == 0) + store (value); + if (ct == 2) + *--sp = value; + + /* Stop main loop for direct calls */ + + if (ct == 2) + finished++; + +}/* ret */ + +/* + * branch + * + * Take a jump after an instruction based on the flag, either true or + * false. The branch can be short or long; it is encoded in one or two + * bytes respectively. When bit 7 of the first byte is set, the jump + * takes place if the flag is true; otherwise it is taken if the flag + * is false. When bit 6 of the first byte is set, the branch is short; + * otherwise it is long. The offset occupies the bottom 6 bits of the + * first byte plus all the bits in the second byte for long branches. + * Uniquely, an offset of 0 means return false, and an offset of 1 is + * return true. + * + */ + +void branch (bool flag) +{ + long pc; + zword offset; + zbyte specifier; + zbyte off1; + zbyte off2; + + CODE_BYTE (specifier) + + off1 = specifier & 0x3f; + + if (!flag) + specifier ^= 0x80; + + if (!(specifier & 0x40)) { /* it's a long branch */ + + if (off1 & 0x20) /* propagate sign bit */ + off1 |= 0xc0; + + CODE_BYTE (off2) + + offset = (off1 << 8) | off2; + + } else offset = off1; /* it's a short branch */ + + if (specifier & 0x80) { + + if (offset > 1) { /* normal branch */ + + GET_PC (pc) + pc += (short) offset - 2; + SET_PC (pc) + + } else ret (offset); /* special case, return 0 or 1 */ + } + +}/* branch */ + +/* + * store + * + * Store an operand, either as a variable or pushed on the stack. + * + */ + +void store (zword value) +{ + zbyte variable; + + CODE_BYTE (variable) + + if (variable == 0) + *--sp = value; + else if (variable < 16) + *(fp - variable) = value; + else { + zword addr = h_globals + 2 * (variable - 16); + SET_WORD (addr, value) + } + +}/* store */ + +/* + * direct_call + * + * Call the interpreter loop directly. This is necessary when + * + * - a sound effect has been finished + * - a read instruction has timed out + * - a newline countdown has hit zero + * + * The interpreter returns the result value on the stack. + * + */ + +int direct_call (zword addr) +{ + zword saved_zargs[8]; + int saved_zargc; + int i; + + /* Calls to address 0 return false */ + + if (addr == 0) + return 0; + + /* Save operands and operand count */ + + for (i = 0; i < 8; i++) + saved_zargs[i] = zargs[i]; + + saved_zargc = zargc; + + /* Call routine directly */ + + call (addr, 0, 0, 2); + + /* Restore operands and operand count */ + + for (i = 0; i < 8; i++) + zargs[i] = saved_zargs[i]; + + zargc = saved_zargc; + + /* Resulting value lies on top of the stack */ + + return (short) *sp++; + +}/* direct_call */ + +/* + * __extended__ + * + * Load and execute an extended opcode. + * + */ + +static void __extended__ (void) +{ + zbyte opcode; + zbyte specifier; + + CODE_BYTE (opcode) + CODE_BYTE (specifier) + + load_all_operands (specifier); + + if (opcode < 0x1d) /* extended opcodes from 0x1d on */ + ext_opcodes[opcode] (); /* are reserved for future spec' */ + +}/* __extended__ */ + +/* + * __illegal__ + * + * Exit game because an unknown opcode has been hit. + * + */ + +static void __illegal__ (void) +{ + + runtime_error (ERR_ILL_OPCODE); + +}/* __illegal__ */ + +/* + * z_catch, store the current stack frame for later use with z_throw. + * + * no zargs used + * + */ + +void z_catch (void) +{ + + store (f_setup.save_quetzal ? frame_count : (zword) (fp - stack)); + +}/* z_catch */ + +/* + * z_throw, go back to the given stack frame and return the given value. + * + * zargs[0] = value to return + * zargs[1] = stack frame + * + */ + +void z_throw (void) +{ + + if (f_setup.save_quetzal) { + if (zargs[1] > frame_count) + runtime_error (ERR_BAD_FRAME); + + /* Unwind the stack a frame at a time. */ + for (; frame_count > zargs[1]; --frame_count) + fp = stack + 1 + fp[1]; + } else { + if (zargs[1] > STACK_SIZE) + runtime_error (ERR_BAD_FRAME); + + fp = stack + zargs[1]; + } + + ret (zargs[0]); + +}/* z_throw */ + +/* + * z_call_n, call a subroutine and discard its result. + * + * zargs[0] = packed address of subroutine + * zargs[1] = first argument (optional) + * ... + * zargs[7] = seventh argument (optional) + * + */ + +void z_call_n (void) +{ + + if (zargs[0] != 0) + call (zargs[0], zargc - 1, zargs + 1, 1); + +}/* z_call_n */ + +/* + * z_call_s, call a subroutine and store its result. + * + * zargs[0] = packed address of subroutine + * zargs[1] = first argument (optional) + * ... + * zargs[7] = seventh argument (optional) + * + */ + +void z_call_s (void) +{ + + if (zargs[0] != 0) + call (zargs[0], zargc - 1, zargs + 1, 0); + else + store (0); + +}/* z_call_s */ + +/* + * z_check_arg_count, branch if subroutine was called with >= n arg's. + * + * zargs[0] = number of arguments + * + */ + +void z_check_arg_count (void) +{ + + if (fp == stack + STACK_SIZE) + branch (zargs[0] == 0); + else + branch (zargs[0] <= (*fp & 0xff)); + +}/* z_check_arg_count */ + +/* + * z_jump, jump unconditionally to the given address. + * + * zargs[0] = PC relative address + * + */ + +void z_jump (void) +{ + long pc; + + GET_PC (pc) + + pc += (short) zargs[0] - 2; + + if (pc >= story_size) + runtime_error (ERR_ILL_JUMP_ADDR); + + SET_PC (pc) + +}/* z_jump */ + +/* + * z_nop, no operation. + * + * no zargs used + * + */ + +void z_nop (void) +{ + + /* Do nothing */ + +}/* z_nop */ + +/* + * z_quit, stop game and exit interpreter. + * + * no zargs used + * + */ + +void z_quit (void) +{ + + finished = 9999; + +}/* z_quit */ + +/* + * z_ret, return from a subroutine with the given value. + * + * zargs[0] = value to return + * + */ + +void z_ret (void) +{ + + ret (zargs[0]); + +}/* z_ret */ + +/* + * z_ret_popped, return from a subroutine with a value popped off the stack. + * + * no zargs used + * + */ + +void z_ret_popped (void) +{ + + ret (*sp++); + +}/* z_ret_popped */ + +/* + * z_rfalse, return from a subroutine with false (0). + * + * no zargs used + * + */ + +void z_rfalse (void) +{ + + ret (0); + +}/* z_rfalse */ + +/* + * z_rtrue, return from a subroutine with true (1). + * + * no zargs used + * + */ + +void z_rtrue (void) +{ + + ret (1); + +}/* z_rtrue */ diff --git a/apps/plugins/frotz/quetzal.c b/apps/plugins/frotz/quetzal.c new file mode 100644 index 0000000000..a75ade856e --- /dev/null +++ b/apps/plugins/frotz/quetzal.c @@ -0,0 +1,541 @@ +/* quetzal.c - Saving and restoring of Quetzal files. + * Written by Martin Frost + * + * Changes for Rockbox copyright 2009 Torne Wuff + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +#define far + +#define get_c fgetc +#define put_c fputc + +typedef unsigned long zlong; + +/* + * This is used only by save_quetzal. It probably should be allocated + * dynamically rather than statically. + */ + +static zword frames[STACK_SIZE/4+1]; + +/* + * ID types. + */ + +#define makeid(a,b,c,d) ((zlong) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d))) + +#define ID_FORM makeid ('F','O','R','M') +#define ID_IFZS makeid ('I','F','Z','S') +#define ID_IFhd makeid ('I','F','h','d') +#define ID_UMem makeid ('U','M','e','m') +#define ID_CMem makeid ('C','M','e','m') +#define ID_Stks makeid ('S','t','k','s') +#define ID_ANNO makeid ('A','N','N','O') + +/* + * Various parsing states within restoration. + */ + +#define GOT_HEADER 0x01 +#define GOT_STACK 0x02 +#define GOT_MEMORY 0x04 +#define GOT_NONE 0x00 +#define GOT_ALL 0x07 +#define GOT_ERROR 0x80 + +/* + * Macros used to write the files. + */ + +#define write_byte(fp,b) (put_c (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, (l) >> 24) && write_bytx (fp, (l) >> 16) && \ + write_bytx (fp, (l) >> 8) && write_bytx (fp, (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))) + +/* Read one word from file; return TRUE if OK. */ +static bool read_word (int f, zword *result) +{ + int a, b; + + if ((a = get_c (f)) == EOF) return FALSE; + if ((b = get_c (f)) == EOF) return FALSE; + + *result = ((zword) a << 8) | (zword) b; + return TRUE; +} + +/* Read one long from file; return TRUE if OK. */ +static bool read_long (int f, zlong *result) +{ + int a, b, c, d; + + if ((a = get_c (f)) == EOF) return FALSE; + if ((b = get_c (f)) == EOF) return FALSE; + if ((c = get_c (f)) == EOF) return FALSE; + if ((d = get_c (f)) == EOF) return FALSE; + + *result = ((zlong) a << 24) | ((zlong) b << 16) | + ((zlong) c << 8) | (zlong) d; + return TRUE; +} + +/* + * Restore a saved game using Quetzal format. Return 2 if OK, 0 if an error + * occurred before any damage was done, -1 on a fatal error. + */ + +zword restore_quetzal (int svf, int stf) +{ + zlong ifzslen, currlen, tmpl; + zlong pc; + zword i, tmpw; + zword fatal = 0; /* Set to -1 when errors must be fatal. */ + zbyte skip, progress = GOT_NONE; + int x, y; + + /* Check it's really an `IFZS' file. */ + if (!read_long (svf, &tmpl) + || !read_long (svf, &ifzslen) + || !read_long (svf, &currlen)) return 0; + if (tmpl != ID_FORM || currlen != ID_IFZS) + { + print_string ("This is not a saved game file!\n"); + return 0; + } + if ((ifzslen & 1) || ifzslen<4) /* Sanity checks. */ return 0; + ifzslen -= 4; + + /* Read each chunk and process it. */ + while (ifzslen > 0) + { + /* Read chunk header. */ + if (ifzslen < 8) /* Couldn't contain a chunk. */ return 0; + if (!read_long (svf, &tmpl) + || !read_long (svf, &currlen)) return 0; + ifzslen -= 8; /* Reduce remaining by size of header. */ + + /* Handle chunk body. */ + if (ifzslen < currlen) /* Chunk goes past EOF?! */ return 0; + skip = currlen & 1; + ifzslen -= currlen + (zlong) skip; + + switch (tmpl) + { + /* `IFhd' header chunk; must be first in file. */ + case ID_IFhd: + if (progress & GOT_HEADER) + { + print_string ("Save file has two IFZS chunks!\n"); + return fatal; + } + progress |= GOT_HEADER; + if (currlen < 13 + || !read_word (svf, &tmpw)) return fatal; + if (tmpw != h_release) + progress = GOT_ERROR; + + for (i=H_SERIAL; i STACK_SIZE) + { + print_string ("Save-file has too much stack (and I can't cope).\n"); + return fatal; + } + currlen -= 8; + if ((signed)currlen < tmpw*2) return fatal; + for (i=0; i 0; + currlen -= 8, ++frame_count) + { + if (currlen < 8) return fatal; + if (sp - stack < 4) /* No space for frame. */ + { + print_string ("Save-file has too much stack (and I can't cope).\n"); + return fatal; + } + + /* Read PC, procedure flag and formal param count. */ + if (!read_long (svf, &tmpl)) return fatal; + y = (int) (tmpl & 0x0F); /* Number of formals. */ + tmpw = y << 8; + + /* Read result variable. */ + if ((x = get_c (svf)) == EOF) return fatal; + + /* Check the procedure flag... */ + if (tmpl & 0x10) + { + tmpw |= 0x1000; /* It's a procedure. */ + tmpl >>= 8; /* Shift to get PC value. */ + } + else + { + /* Functions have type 0, so no need to or anything. */ + tmpl >>= 8; /* Shift to get PC value. */ + --tmpl; /* Point at result byte. */ + /* Sanity check on result variable... */ + if (zmp[tmpl] != (zbyte) x) + { + print_string ("Save-file has wrong variable number on stack (possibly wrong game version?)\n"); + return fatal; + } + } + *--sp = (zword) (tmpl >> 9); /* High part of PC */ + *--sp = (zword) (tmpl & 0x1FF); /* Low part of PC */ + *--sp = (zword) (fp - stack - 1); /* FP */ + + /* Read and process argument mask. */ + if ((x = get_c (svf)) == EOF) return fatal; + ++x; /* Should now be a power of 2 */ + for (i=0; i<8; ++i) + if (x & (1< 0; --currlen) + { + if ((x = get_c (svf)) == EOF) return fatal; + if (x == 0) /* Start run. */ + { + /* Check for bogus run. */ + if (currlen < 2) + { + print_string ("File contains bogus `CMem' chunk.\n"); + for (; currlen > 0; --currlen) + (void) get_c (svf); /* Skip rest. */ + currlen = 1; + i = 0xFFFF; + break; /* Keep going; may be a `UMem' too. */ + } + /* Copy story file to memory during the run. */ + --currlen; + if ((x = get_c (svf)) == EOF) return fatal; + for (; x >= 0 && i h_dynamic_size) + { + print_string ("warning: `CMem' chunk too long!\n"); + for (; currlen > 1; --currlen) + (void) get_c (svf); /* Skip rest. */ + break; /* Keep going; there may be a `UMem' too. */ + } + } + /* If chunk is short, assume a run. */ + for (; i 0) + { + for (; j > 0x100; j -= 0x100) + { + if (!write_run (svf, 0xFF)) return 0; + cmemlen += 2; + } + if (!write_run (svf, j-1)) return 0; + cmemlen += 2; + j = 0; + } + /* Any runs are now written. Write this (nonzero) byte. */ + if (!write_byte (svf, (zbyte) c)) return 0; + ++cmemlen; + } + } + /* + * Reached end of dynamic memory. We ignore any unwritten run there may be + * at this point. + */ + if (cmemlen & 1) /* Chunk length must be even. */ + if (!write_byte (svf, 0)) return 0; + + /* Write `Stks' chunk. You are not expected to understand this. ;) */ + if ((stkspos = ftell (svf)) < 0) return 0; + if (!write_chnk (svf, ID_Stks, 0)) return 0; + + /* + * We construct a list of frame indices, most recent first, in `frames'. + * These indices are the offsets into the `stack' array of the word before + * the first word pushed in each frame. + */ + frames[0] = sp - stack; /* The frame we'd get by doing a call now. */ + for (i = fp - stack + 4, n=0; i < STACK_SIZE+4; i = stack[i-3] + 5) + frames[++n] = i; + + /* + * All versions other than V6 can use evaluation stack outside a function + * context. We write a faked stack frame (most fields zero) to cater for + * this. + */ + if (h_version != V6) + { + for (i=0; i<6; ++i) + if (!write_byte (svf, 0)) return 0; + nstk = STACK_SIZE - frames[n]; + if (!write_word (svf, nstk)) return 0; + for (j=STACK_SIZE-1; j >= frames[n]; --j) + if (!write_word (svf, stack[j])) return 0; + stkslen = 8 + 2*nstk; + } + + /* Write out the rest of the stack frames. */ + for (i=n; i>0; --i) + { + p = stack + frames[i] - 4; /* Points to call frame. */ + nvars = (p[0] & 0x0F00) >> 8; + nargs = p[0] & 0x00FF; + nstk = frames[i] - frames[i-1] - nvars - 4; + pc = ((zlong) p[3] << 9) | p[2]; + + switch (p[0] & 0xF000) /* Check type of call. */ + { + case 0x0000: /* Function. */ + var = zmp[pc]; + pc = ((pc + 1) << 8) | nvars; + break; + case 0x1000: /* Procedure. */ + var = 0; + pc = (pc << 8) | 0x10 | nvars; /* Set procedure flag. */ + break; + /* case 0x2000: */ + default: + runtime_error (ERR_SAVE_IN_INTER); + return 0; + } + if (nargs != 0) + nargs = (1 << nargs) - 1; /* Make args into bitmap. */ + + /* Write the main part of the frame... */ + if (!write_long (svf, pc) + || !write_byte (svf, var) + || !write_byte (svf, nargs) + || !write_word (svf, nstk)) return 0; + + /* Write the variables and eval stack. */ + for (j=0, ++p; j> 16) & 0x7fff; + } + + store ((zword) (result % zargs[0] + 1)); + + } + +}/* z_random */ diff --git a/apps/plugins/frotz/redirect.c b/apps/plugins/frotz/redirect.c new file mode 100644 index 0000000000..d81776dbcd --- /dev/null +++ b/apps/plugins/frotz/redirect.c @@ -0,0 +1,172 @@ +/* redirect.c - Output redirection to Z-machine memory + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +#define MAX_NESTING 16 + +extern zword get_max_width (zword); + +static int depth = -1; + +static struct { + zword xsize; + zword table; + zword width; + zword total; +} redirect[MAX_NESTING]; + +/* + * memory_open + * + * Begin output redirection to the memory of the Z-machine. + * + */ + +void memory_open (zword table, zword xsize, bool buffering) +{ + + if (++depth < MAX_NESTING) { + + if (!buffering) + xsize = 0xffff; + if (buffering && (short) xsize <= 0) + xsize = get_max_width ((zword) (- (short) xsize)); + + storew (table, 0); + + redirect[depth].table = table; + redirect[depth].width = 0; + redirect[depth].total = 0; + redirect[depth].xsize = xsize; + + ostream_memory = TRUE; + + } else runtime_error (ERR_STR3_NESTING); + +}/* memory_open */ + +/* + * memory_new_line + * + * Redirect a newline to the memory of the Z-machine. + * + */ + +void memory_new_line (void) +{ + zword size; + zword addr; + + redirect[depth].total += redirect[depth].width; + redirect[depth].width = 0; + + addr = redirect[depth].table; + + LOW_WORD (addr, size) + addr += 2; + + if (redirect[depth].xsize != 0xffff) { + + redirect[depth].table = addr + size; + size = 0; + + } else storeb ((zword) (addr + (size++)), 13); + + storew (redirect[depth].table, size); + +}/* memory_new_line */ + +/* + * memory_word + * + * Redirect a string of characters to the memory of the Z-machine. + * + */ + +void memory_word (const zchar *s) +{ + zword size; + zword addr; + zchar c; + + if (h_version == V6) { + + int width = os_string_width (s); + + if (redirect[depth].xsize != 0xffff) + + if (redirect[depth].width + width > redirect[depth].xsize) { + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + width = os_string_width (++s); + + memory_new_line (); + + } + + redirect[depth].width += width; + + } + + addr = redirect[depth].table; + + LOW_WORD (addr, size) + addr += 2; + + while ((c = *s++) != 0) + storeb ((zword) (addr + (size++)), translate_to_zscii (c)); + + storew (redirect[depth].table, size); + +}/* memory_word */ + +/* + * memory_close + * + * End of output redirection. + * + */ + +void memory_close (void) +{ + + if (depth >= 0) { + + if (redirect[depth].xsize != 0xffff) + memory_new_line (); + + if (h_version == V6) { + + h_line_width = (redirect[depth].xsize != 0xffff) ? + redirect[depth].total : redirect[depth].width; + + SET_WORD (H_LINE_WIDTH, h_line_width) + + } + + if (depth == 0) + ostream_memory = FALSE; + + depth--; + + } + +}/* memory_close */ diff --git a/apps/plugins/frotz/screen.c b/apps/plugins/frotz/screen.c new file mode 100644 index 0000000000..713ec2921c --- /dev/null +++ b/apps/plugins/frotz/screen.c @@ -0,0 +1,1743 @@ +/* screen.c - Generic screen manipulation + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +extern void set_header_extension (int, zword); + +extern int direct_call (zword); + +static struct { + enum story story_id; + int pic; + int pic1; + int pic2; +} mapper[] = { + { ZORK_ZERO, 5, 497, 498 }, + { ZORK_ZERO, 6, 501, 502 }, + { ZORK_ZERO, 7, 499, 500 }, + { ZORK_ZERO, 8, 503, 504 }, + { ARTHUR, 54, 170, 171 }, + { SHOGUN, 50, 61, 62 }, + { UNKNOWN, 0, 0, 0 } +}; + +static int font_height = 1; +static int font_width = 1; + +static bool input_redraw = FALSE; +static bool more_prompts = TRUE; +static bool discarding = FALSE; +static bool cursor = TRUE; + +static int input_window = 0; + +static struct { + zword y_pos; + zword x_pos; + zword y_size; + zword x_size; + zword y_cursor; + zword x_cursor; + zword left; + zword right; + zword nl_routine; + zword nl_countdown; + zword style; + zword colour; + zword font; + zword font_size; + zword attribute; + zword line_count; +} wp[8], *cwp; + + +/* + * winarg0 + * + * Return the window number in zargs[0]. In V6 only, -3 refers to the + * current window. + * + */ + +static zword winarg0 (void) +{ + + if (h_version == V6 && (short) zargs[0] == -3) + return cwin; + + if (zargs[0] >= ((h_version == V6) ? 8 : 2)) + runtime_error (ERR_ILL_WIN); + + return zargs[0]; + +}/* winarg0 */ + +/* + * winarg2 + * + * Return the (optional) window number in zargs[2]. -3 refers to the + * current window. This optional window number was only used by some + * V6 opcodes: set_cursor, set_margins, set_colour. + * + */ + +static zword winarg2 (void) +{ + + if (zargc < 3 || (short) zargs[2] == -3) + return cwin; + + if (zargs[2] >= 8) + runtime_error (ERR_ILL_WIN); + + return zargs[2]; + +}/* winarg2 */ + +/* + * update_cursor + * + * Move the hardware cursor to make it match the window properties. + * + */ + +static void update_cursor (void) +{ + + os_set_cursor ( + cwp->y_pos + cwp->y_cursor - 1, + cwp->x_pos + cwp->x_cursor - 1); + +}/* update_cursor */ + +/* + * reset_cursor + * + * Reset the cursor of a given window to its initial position. + * + */ + +static void reset_cursor (zword win) +{ + int lines = 0; + + if (h_version <= V4 && win == 0) + lines = wp[0].y_size / hi (wp[0].font_size) - 1; + + wp[win].y_cursor = hi (wp[0].font_size) * lines + 1; + wp[win].x_cursor = wp[win].left + 1; + + if (win == cwin) + update_cursor (); + +}/* reset_cursor */ + +/* + * set_more_prompts + * + * Turn more prompts on/off. + * + */ + +void set_more_prompts (bool flag) +{ + + if (flag && !more_prompts) + cwp->line_count = 0; + + more_prompts = flag; + +}/* set_more_prompts */ + +/* + * units_left + * + * Return the #screen units from the cursor to the end of the line. + * + */ + +static int units_left (void) +{ + + return cwp->x_size - cwp->right - cwp->x_cursor + 1; + +}/* units_left */ + +/* + * get_max_width + * + * Return maximum width of a line in the given window. This is used in + * connection with the extended output stream #3 call in V6. + * + */ + +zword get_max_width (zword win) +{ + + if (h_version == V6) { + + if (win >= 8) + runtime_error (ERR_ILL_WIN); + + return wp[win].x_size - wp[win].left - wp[win].right; + + } else return 0xffff; + +}/* get_max_width */ + +/* + * countdown + * + * Decrement the newline counter. Call the newline interrupt when the + * counter hits zero. This is a helper function for screen_new_line. + * + */ + +static void countdown (void) +{ + + if (cwp->nl_countdown != 0) + if (--cwp->nl_countdown == 0) + direct_call (cwp->nl_routine); + +}/* countdown */ + +/* + * screen_new_line + * + * Print a newline to the screen. + * + */ + +void screen_new_line (void) +{ + + if (discarding) return; + + /* Handle newline interrupts at the start (for most cases) */ + + if (h_interpreter_number != INTERP_MSDOS || story_id != ZORK_ZERO || h_release != 393) + countdown (); + + /* Check whether the last input line gets destroyed */ + + if (input_window == cwin) + input_redraw = TRUE; + + /* If the cursor has not reached the bottom line, then move it to + the next line; otherwise scroll the window or reset the cursor + to the top left. */ + + cwp->x_cursor = cwp->left + 1; + + if (cwp->y_cursor + 2 * font_height - 1 > cwp->y_size) + + if (enable_scrolling) { + + zword y = cwp->y_pos; + zword x = cwp->x_pos; + + os_scroll_area (y, + x, + y + cwp->y_size - 1, + x + cwp->x_size - 1, + font_height); + + } else cwp->y_cursor = 1; + + else cwp->y_cursor += font_height; + + update_cursor (); + + /* See if we need to print a more prompt (unless the game has set + the line counter to -999 in order to suppress more prompts). */ + + if (enable_scrolling && (short) cwp->line_count != -999) { + + zword above = (cwp->y_cursor - 1) / font_height; + zword below = (cwp->y_size - cwp->y_cursor + 1) / font_height; + + cwp->line_count++; + + if ((short) cwp->line_count >= (short) above + below - 1) { + + if (more_prompts) + os_more_prompt (); + + cwp->line_count = f_setup.context_lines; + + } + + } + + /* Handle newline interrupts at the end for Zork Zero under DOS */ + + if (h_interpreter_number == INTERP_MSDOS && story_id == ZORK_ZERO && h_release == 393) + countdown (); + +}/* screen_new_line */ + +/* + * screen_char + * + * Display a single character on the screen. + * + */ + +void screen_char (zchar c) +{ + int width; + + if (discarding) return; + + if (c == ZC_INDENT && cwp->x_cursor != cwp->left + 1) + c = ' '; + + if (units_left () < (width = os_char_width (c))) { + + if (!enable_wrapping) + { cwp->x_cursor = cwp->x_size - cwp->right; return; } + + screen_new_line (); + + } + + os_display_char (c); cwp->x_cursor += width; + +}/* screen_char */ + +/* + * screen_word + * + * Display a string of characters on the screen. If the word doesn't fit + * then use wrapping or clipping depending on the current setting of the + * enable_wrapping flag. + * + */ + +void screen_word (const zchar *s) +{ + int width; + + if (discarding) return; + + if (*s == ZC_INDENT && cwp->x_cursor != cwp->left + 1) + screen_char (*s++); + + if (units_left () < (width = os_string_width (s))) { + + if (!enable_wrapping) { + + zchar c; + + while ((c = *s++) != 0) + + if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) { + + int arg = (int) *s++; + + if (c == ZC_NEW_FONT) + os_set_font (arg); + if (c == ZC_NEW_STYLE) + os_set_text_style (arg); + + } else screen_char (c); + + return; + + } + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + width = os_string_width (++s); + +#ifdef AMIGA + if (cwin == 0) Justifiable (); +#endif + + screen_new_line (); + + } + + os_display_string (s); cwp->x_cursor += width; + +}/* screen_word */ + +/* + * screen_write_input + * + * Display an input line on the screen. This is required during playback. + * + */ + +void screen_write_input (const zchar *buf, zchar key) +{ + int width; + + if (units_left () < (width = os_string_width (buf))) + screen_new_line (); + + os_display_string (buf); cwp->x_cursor += width; + + if (key == ZC_RETURN) + screen_new_line (); + +}/* screen_write_input */ + +/* + * screen_erase_input + * + * Remove an input line that has already been printed from the screen + * as if it was deleted by the player. This could be necessary during + * playback. + * + */ + +void screen_erase_input (const zchar *buf) +{ + + if (buf[0] != 0) { + + int width = os_string_width (buf); + + zword y; + zword x; + + cwp->x_cursor -= width; + + y = cwp->y_pos + cwp->y_cursor - 1; + x = cwp->x_pos + cwp->x_cursor - 1; + + os_erase_area (y, x, y + font_height - 1, x + width - 1); + os_set_cursor (y, x); + + } + +}/* screen_erase_input */ + +/* + * console_read_input + * + * Read an input line from the keyboard and return the terminating key. + * + */ + +zchar console_read_input (int max, zchar *buf, zword timeout, bool continued) +{ + zchar key; + int i; + + /* Make sure there is some space for input */ + + if (cwin == 0 && units_left () + os_string_width (buf) < 10 * font_width) + screen_new_line (); + + /* Make sure the input line is visible */ + + if (continued && input_redraw) + screen_write_input (buf, -1); + + input_window = cwin; + input_redraw = FALSE; + + /* Get input line from IO interface */ + + cwp->x_cursor -= os_string_width (buf); + key = os_read_line (max, buf, timeout, units_left (), continued); + cwp->x_cursor += os_string_width (buf); + + if (key != ZC_TIME_OUT) + for (i = 0; i < 8; i++) + wp[i].line_count = 0; + + /* Add a newline if the input was terminated normally */ + + if (key == ZC_RETURN) + screen_new_line (); + + return key; + +}/* console_read_input */ + +/* + * console_read_key + * + * Read a single keystroke and return it. + * + */ + +zchar console_read_key (zword timeout) +{ + zchar key; + int i; + + key = os_read_key (timeout, cursor); + + if (key != ZC_TIME_OUT) + for (i = 0; i < 8; i++) + wp[i].line_count = 0; + + return key; + +}/* console_read_key */ + +/* + * update_attributes + * + * Set the three enable_*** variables to make them match the attributes + * of the current window. + * + */ + +static void update_attributes (void) +{ + zword attr = cwp->attribute; + + enable_wrapping = attr & 1; + enable_scrolling = attr & 2; + enable_scripting = attr & 4; + enable_buffering = attr & 8; + + /* Some story files forget to select wrapping for printing hints */ + + if (story_id == ZORK_ZERO && h_release == 366) + if (cwin == 0) + enable_wrapping = TRUE; + if (story_id == SHOGUN && h_release <= 295) + if (cwin == 0) + enable_wrapping = TRUE; + +}/* update_attributes */ + +/* + * refresh_text_style + * + * Set the right text style. This can be necessary when the fixed font + * flag is changed, or when a new window is selected, or when the game + * uses the set_text_style opcode. + * + */ + +void refresh_text_style (void) +{ + zword style; + + if (h_version != V6) { + + style = wp[0].style; + + if (cwin != 0 || h_flags & FIXED_FONT_FLAG) + style |= FIXED_WIDTH_STYLE; + + } else style = cwp->style; + + if (!ostream_memory && ostream_screen && enable_buffering) { + + print_char (ZC_NEW_STYLE); + print_char (style); + + } else os_set_text_style (style); + +}/* refresh_text_style */ + +/* + * set_window + * + * Set the current window. In V6 every window has its own set of window + * properties such as colours, text style, cursor position and size. + * + */ + +static void set_window (zword win) +{ + + flush_buffer (); + + cwin = win; cwp = wp + win; + + update_attributes (); + + if (h_version == V6) { + + os_set_colour (lo (cwp->colour), hi (cwp->colour)); + + if (os_font_data (cwp->font, &font_height, &font_width)) + os_set_font (cwp->font); + + os_set_text_style (cwp->style); + + } else refresh_text_style (); + + if (h_version != V6 && win != 0) { + wp[win].y_cursor = 1; + wp[win].x_cursor = 1; + } + + update_cursor (); + +}/* set_window */ + +/* + * erase_window + * + * Erase a window to background colour. + * + */ + +void erase_window (zword win) +{ + zword y = wp[win].y_pos; + zword x = wp[win].x_pos; + + if (h_version == V6 && win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (wp[win].colour), hi (wp[win].colour)); + + os_erase_area (y, + x, + y + wp[win].y_size - 1, + x + wp[win].x_size - 1); + + if (h_version == V6 && win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (cwp->colour), hi (cwp->colour)); + + reset_cursor (win); + + wp[win].line_count = 0; + +}/* erase_window */ + +/* + * split_window + * + * Divide the screen into upper (1) and lower (0) windows. In V3 the upper + * window appears below the status line. + * + */ + +void split_window (zword height) +{ + zword stat_height = 0; + + flush_buffer (); + + /* Calculate height of status line and upper window */ + + if (h_version != V6) + height *= hi (wp[1].font_size); + + if (h_version <= V3) + stat_height = hi (wp[7].font_size); + + /* Cursor of upper window mustn't be swallowed by the lower window */ + + wp[1].y_cursor += wp[1].y_pos - 1 - stat_height; + + wp[1].y_pos = 1 + stat_height; + wp[1].y_size = height; + + if ((short) wp[1].y_cursor > (short) wp[1].y_size) + reset_cursor (1); + + /* Cursor of lower window mustn't be swallowed by the upper window */ + + wp[0].y_cursor += wp[0].y_pos - 1 - stat_height - height; + + wp[0].y_pos = 1 + stat_height + height; + wp[0].y_size = h_screen_height - stat_height - height; + + if ((short) wp[0].y_cursor < 1) + reset_cursor (0); + + /* Erase the upper window in V3 only */ + + if (h_version == V3 && height != 0) + erase_window (1); + +}/* split_window */ + +/* + * erase_screen + * + * Erase the entire screen to background colour. + * + */ + +static void erase_screen (zword win) +{ + int i; + + os_erase_area (1, 1, h_screen_height, h_screen_width); + + if ((short) win == -1) { + split_window (0); + set_window (0); + reset_cursor (0); + } + + for (i = 0; i < 8; i++) + wp[i].line_count = 0; + +}/* erase_screen */ + +/* #ifdef AMIGA */ + +/* + * resize_screen + * + * Try to adapt the window properties to a new screen size. + * + */ + +void resize_screen (void) +{ + + if (h_version != V6) { + + wp[0].x_size = h_screen_width; + wp[1].x_size = h_screen_width; + wp[7].x_size = h_screen_width; + + wp[0].y_size = h_screen_height - wp[1].y_size - wp[7].y_size; + + } + +}/* resize_screen */ + +/* #endif */ + +/* + * restart_screen + * + * Prepare the screen for a new game. + * + */ + +void restart_screen (void) +{ + + /* Use default settings */ + + os_set_colour (h_default_foreground, h_default_background); + + if (os_font_data (TEXT_FONT, &font_height, &font_width)) + os_set_font (TEXT_FONT); + + os_set_text_style (0); + + cursor = TRUE; + + /* Initialise window properties */ + + mwin = 1; + + for (cwp = wp; cwp < wp + 8; cwp++) { + cwp->y_pos = 1; + cwp->x_pos = 1; + cwp->y_size = 0; + cwp->x_size = 0; + cwp->y_cursor = 1; + cwp->x_cursor = 1; + cwp->left = 0; + cwp->right = 0; + cwp->nl_routine = 0; + cwp->nl_countdown = 0; + cwp->style = 0; + cwp->colour = (h_default_background << 8) | h_default_foreground; + cwp->font = TEXT_FONT; + cwp->font_size = (font_height << 8) | font_width; + cwp->attribute = 8; + } + + /* Prepare lower/upper windows and status line */ + + wp[0].attribute = 15; + + wp[0].left = f_setup.left_margin; + wp[0].right = f_setup.right_margin; + + wp[0].x_size = h_screen_width; + wp[1].x_size = h_screen_width; + + if (h_version <= V3) + wp[7].x_size = h_screen_width; + + os_restart_game (RESTART_WPROP_SET); + + /* Clear the screen, unsplit it and select window 0 */ + + erase_screen ((zword) (-1)); + +}/* restart_screen */ + +/* + * validate_click + * + * Return false if the last mouse click occured outside the current + * mouse window; otherwise write the mouse arrow coordinates to the + * memory of the header extension table and return true. + * + */ + +bool validate_click (void) +{ + + if (mwin >= 0) { + + if (mouse_y < wp[mwin].y_pos || mouse_y >= wp[mwin].y_pos + wp[mwin].y_size) + return FALSE; + if (mouse_x < wp[mwin].x_pos || mouse_x >= wp[mwin].x_pos + wp[mwin].x_size) + return FALSE; + + hx_mouse_y = mouse_y - wp[mwin].y_pos + 1; + hx_mouse_x = mouse_x - wp[mwin].x_pos + 1; + + } else { + + if (mouse_y < 1 || mouse_y > h_screen_height) + return FALSE; + if (mouse_x < 1 || mouse_x > h_screen_width) + return FALSE; + + hx_mouse_y = mouse_y; + hx_mouse_x = mouse_x; + + } + + if (h_version != V6) { + hx_mouse_y = (hx_mouse_y - 1) / h_font_height + 1; + hx_mouse_x = (hx_mouse_x - 1) / h_font_width + 1; + } + + set_header_extension (HX_MOUSE_Y, hx_mouse_y); + set_header_extension (HX_MOUSE_X, hx_mouse_x); + + return TRUE; + +}/* validate_click */ + +/* + * screen_mssg_on + * + * Start printing a so-called debugging message. The contents of the + * message are passed to the message stream, a Frotz specific output + * stream with maximum priority. + * + */ + +void screen_mssg_on (void) +{ + + if (cwin == 0) { /* messages in window 0 only */ + + os_set_text_style (0); + + if (cwp->x_cursor != cwp->left + 1) + screen_new_line (); + + screen_char (ZC_INDENT); + + } else discarding = TRUE; /* discard messages in other windows */ + +}/* screen_mssg_on */ + +/* + * screen_mssg_off + * + * Stop printing a "debugging" message. + * + */ + +void screen_mssg_off (void) +{ + + if (cwin == 0) { /* messages in window 0 only */ + + screen_new_line (); + + refresh_text_style (); + + } else discarding = FALSE; /* message has been discarded */ + +}/* screen_mssg_off */ + +/* + * z_buffer_mode, turn text buffering on/off. + * + * zargs[0] = new text buffering flag (0 or 1) + * + */ + +void z_buffer_mode (void) +{ + + /* Infocom's V6 games rarely use the buffer_mode opcode. If they do + then only to print text immediately, without any delay. This was + used to give the player some sign of life while the game was + spending much time on parsing a complicated input line. (To turn + off word wrapping, V6 games use the window_style opcode instead.) + Today we can afford to ignore buffer_mode in V6. */ + + if (h_version != V6) { + + flush_buffer (); + + wp[0].attribute &= ~8; + + if (zargs[0] != 0) + wp[0].attribute |= 8; + + update_attributes (); + + } + +}/* z_buffer_mode */ + +/* + * z_draw_picture, draw a picture. + * + * zargs[0] = number of picture to draw + * zargs[1] = y-coordinate of top left corner + * zargs[2] = x-coordinate of top left corner + * + */ + +void z_draw_picture (void) +{ + zword pic = zargs[0]; + + zword y = zargs[1]; + zword x = zargs[2]; + + int i; + + flush_buffer (); + + if (y == 0) /* use cursor line if y-coordinate is 0 */ + y = cwp->y_cursor; + if (x == 0) /* use cursor column if x-coordinate is 0 */ + x = cwp->x_cursor; + + y += cwp->y_pos - 1; + x += cwp->x_pos - 1; + + /* The following is necessary to make Amiga and Macintosh story + files work with MCGA graphics files. Some screen-filling + pictures of the original Amiga release like the borders of + Zork Zero were split into several MCGA pictures (left, right + and top borders). We pretend this has not happened. */ + + for (i = 0; mapper[i].story_id != UNKNOWN; i++) + + if (story_id == mapper[i].story_id && pic == mapper[i].pic) { + + int height1, width1; + int height2, width2; + + int delta = 0; + + os_picture_data (pic, &height1, &width1); + os_picture_data (mapper[i].pic2, &height2, &width2); + + if (story_id == ARTHUR && pic == 54) + delta = h_screen_width / 160; + + os_draw_picture (mapper[i].pic1, y + height1, x + delta); + os_draw_picture (mapper[i].pic2, y + height1, x + width1 - width2 - delta); + + } + + os_draw_picture (pic, y, x); + + if (story_id == SHOGUN) + + if (pic == 3) { + + int height, width; + + os_picture_data (59, &height, &width); + os_draw_picture (59, y, h_screen_width - width + 1); + + } + +}/* z_draw_picture */ + +/* + * z_erase_line, erase the line starting at the cursor position. + * + * zargs[0] = 1 + #units to erase (1 clears to the end of the line) + * + */ + +void z_erase_line (void) +{ + zword pixels = zargs[0]; + zword y, x; + + flush_buffer (); + + /* Clipping at the right margin of the current window */ + + if (--pixels == 0 || pixels > units_left ()) + pixels = units_left (); + + /* Erase from cursor position */ + + y = cwp->y_pos + cwp->y_cursor - 1; + x = cwp->x_pos + cwp->x_cursor - 1; + + os_erase_area (y, x, y + font_height - 1, x + pixels - 1); + +}/* z_erase_line */ + +/* + * z_erase_picture, erase a picture with background colour. + * + * zargs[0] = number of picture to erase + * zargs[1] = y-coordinate of top left corner (optional) + * zargs[2] = x-coordinate of top left corner (optional) + * + */ + +void z_erase_picture (void) +{ + int height, width; + + zword y = zargs[1]; + zword x = zargs[2]; + + flush_buffer (); + + if (y == 0) /* use cursor line if y-coordinate is 0 */ + y = cwp->y_cursor; + if (x == 0) /* use cursor column if x-coordinate is 0 */ + x = cwp->x_cursor; + + os_picture_data (zargs[0], &height, &width); + + y += cwp->y_pos - 1; + x += cwp->x_pos - 1; + + os_erase_area (y, x, y + height - 1, x + width - 1); + +}/* z_erase_picture */ + +/* + * z_erase_window, erase a window or the screen to background colour. + * + * zargs[0] = window (-3 current, -2 screen, -1 screen & unsplit) + * + */ + +void z_erase_window (void) +{ + + flush_buffer (); + + if ((short) zargs[0] == -1 || (short) zargs[0] == -2) + erase_screen (zargs[0]); + else + erase_window (winarg0 ()); + +}/* z_erase_window */ + +/* + * z_get_cursor, write the cursor coordinates into a table. + * + * zargs[0] = address to write information to + * + */ + +void z_get_cursor (void) +{ + zword y, x; + + flush_buffer (); + + y = cwp->y_cursor; + x = cwp->x_cursor; + + if (h_version != V6) { /* convert to grid positions */ + y = (y - 1) / h_font_height + 1; + x = (x - 1) / h_font_width + 1; + } + + storew ((zword) (zargs[0] + 0), y); + storew ((zword) (zargs[0] + 2), x); + +}/* z_get_cursor */ + +/* + * z_get_wind_prop, store the value of a window property. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = number of window property to be stored + * + */ + +void z_get_wind_prop (void) +{ + + flush_buffer (); + + if (zargs[1] >= 16) + runtime_error (ERR_ILL_WIN_PROP); + + store (((zword *) (wp + winarg0 ())) [zargs[1]]); + +}/* z_get_wind_prop */ + +/* + * z_mouse_window, select a window as mouse window. + * + * zargs[0] = window number (-3 is the current) or -1 for the screen + * + */ + +void z_mouse_window (void) +{ + + mwin = ((short) zargs[0] == -1) ? -1 : winarg0 (); + +}/* z_mouse_window */ + +/* + * z_move_window, place a window on the screen. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = y-coordinate + * zargs[2] = x-coordinate + * + */ + +void z_move_window (void) +{ + zword win = winarg0 (); + + flush_buffer (); + + wp[win].y_pos = zargs[1]; + wp[win].x_pos = zargs[2]; + + if (win == cwin) + update_cursor (); + +}/* z_move_window */ + +/* + * z_picture_data, get information on a picture or the graphics file. + * + * zargs[0] = number of picture or 0 for the graphics file + * zargs[1] = address to write information to + * + */ + +void z_picture_data (void) +{ + zword pic = zargs[0]; + zword table = zargs[1]; + + int height, width; + int i; + + bool avail = os_picture_data (pic, &height, &width); + + for (i = 0; mapper[i].story_id != UNKNOWN; i++) + + if (story_id == mapper[i].story_id) { + + if (pic == mapper[i].pic) { + + int height2, width2; + + avail &= os_picture_data (mapper[i].pic1, &height2, &width2); + avail &= os_picture_data (mapper[i].pic2, &height2, &width2); + + height += height2; + + } else if (pic == mapper[i].pic1 || pic == mapper[i].pic2) + + avail = FALSE; + } + + storew ((zword) (table + 0), (zword) (height)); + storew ((zword) (table + 2), (zword) (width)); + + branch (avail); + +}/* z_picture_data */ + +/* + * z_picture_table, prepare a group of pictures for faster display. + * + * zargs[0] = address of table holding the picture numbers + * + */ + +void z_picture_table (void) +{ + + /* This opcode is used by Shogun and Zork Zero when the player + encounters built-in games such as Peggleboz. Nowadays it is + not very helpful to hold the picture data in memory because + even a small disk cache avoids re-loading of data. */ + +}/* z_picture_table */ + +/* + * z_print_table, print ASCII text in a rectangular area. + * + * zargs[0] = address of text to be printed + * zargs[1] = width of rectangular area + * zargs[2] = height of rectangular area (optional) + * zargs[3] = number of char's to skip between lines (optional) + * + */ + +void z_print_table (void) +{ + zword addr = zargs[0]; + zword x; + int i, j; + + flush_buffer (); + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 1; + if (zargc < 4) + zargs[3] = 0; + + /* Write text in width x height rectangle */ + + x = cwp->x_cursor; + + for (i = 0; i < zargs[2]; i++) { + + if (i != 0) { + + flush_buffer (); + + cwp->y_cursor += font_height; + cwp->x_cursor = x; + + update_cursor (); + + } + + for (j = 0; j < zargs[1]; j++) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + print_char (c); + + } + + addr += zargs[3]; + + } + +}/* z_print_table */ + +/* + * z_put_wind_prop, set the value of a window property. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = number of window property to set + * zargs[2] = value to set window property to + * + */ + +void z_put_wind_prop (void) +{ + + flush_buffer (); + + if (zargs[1] >= 16) + runtime_error (ERR_ILL_WIN_PROP); + + ((zword *) (wp + winarg0 ())) [zargs[1]] = zargs[2]; + +}/* z_put_wind_prop */ + +/* + * z_scroll_window, scroll a window up or down. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = #screen units to scroll up (positive) or down (negative) + * + */ + +void z_scroll_window (void) +{ + zword win = winarg0 (); + zword y, x; + + flush_buffer (); + + /* Use the correct set of colours when scrolling the window */ + + if (win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (wp[win].colour), hi (wp[win].colour)); + + y = wp[win].y_pos; + x = wp[win].x_pos; + + os_scroll_area (y, + x, + y + wp[win].y_size - 1, + x + wp[win].x_size - 1, + (short) zargs[1]); + + if (win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (cwp->colour), hi (cwp->colour)); + +}/* z_scroll_window */ + +/* + * z_set_colour, set the foreground and background colours. + * + * zargs[0] = foreground colour + * zargs[1] = background colour + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_colour (void) +{ + zword win = (h_version == V6) ? winarg2 () : 0; + + zword fg = zargs[0]; + zword bg = zargs[1]; + + flush_buffer (); + + if ((short) fg == -1) /* colour -1 is the colour at the cursor */ + fg = os_peek_colour (); + if ((short) bg == -1) + bg = os_peek_colour (); + + if (fg == 0) /* colour 0 means keep current colour */ + fg = lo (wp[win].colour); + if (bg == 0) + bg = hi (wp[win].colour); + + if (fg == 1) /* colour 1 is the system default colour */ + fg = h_default_foreground; + if (bg == 1) + bg = h_default_background; + + if (h_version == V6 && h_interpreter_number == INTERP_AMIGA) + + /* Changing colours of window 0 affects the entire screen */ + + if (win == 0) { + + int i; + + for (i = 1; i < 8; i++) { + + zword bg2 = hi (wp[i].colour); + zword fg2 = lo (wp[i].colour); + + if (bg2 < 16) + bg2 = (bg2 == lo (wp[0].colour)) ? fg : bg; + if (fg2 < 16) + fg2 = (fg2 == lo (wp[0].colour)) ? fg : bg; + + wp[i].colour = (bg2 << 8) | fg2; + + } + + } + + wp[win].colour = (bg << 8) | fg; + + if (win == cwin || h_version != V6) + os_set_colour (fg, bg); + +}/* z_set_colour */ + +/* + * z_set_font, set the font for text output and store the previous font. + * + * zargs[0] = number of font or 0 to keep current font + * + */ + +void z_set_font (void) +{ + zword win = (h_version == V6) ? cwin : 0; + zword font = zargs[0]; + + if (font != 0) { + + if (os_font_data (font, &font_height, &font_width)) { + + store (wp[win].font); + + wp[win].font = font; + wp[win].font_size = (font_height << 8) | font_width; + + if (!ostream_memory && ostream_screen && enable_buffering) { + + print_char (ZC_NEW_FONT); + print_char (font); + + } else os_set_font (font); + + } else store (0); + + } else store (wp[win].font); + +}/* z_set_font */ + +/* + * z_set_cursor, set the cursor position or turn the cursor on/off. + * + * zargs[0] = y-coordinate or -2/-1 for cursor on/off + * zargs[1] = x-coordinate + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_cursor (void) +{ + zword win = (h_version == V6) ? winarg2 () : 1; + + zword y = zargs[0]; + zword x = zargs[1]; + + flush_buffer (); + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = -3; + + /* Handle cursor on/off */ + + if ((short) y < 0) { + + if ((short) y == -2) + cursor = TRUE; + if ((short) y == -1) + cursor = FALSE; + + return; + + } + + /* Convert grid positions to screen units if this is not V6 */ + + if (h_version != V6) { + + if (cwin == 0) + return; + + y = (y - 1) * h_font_height + 1; + x = (x - 1) * h_font_width + 1; + + } + + /* Protect the margins */ + + if (y == 0) /* use cursor line if y-coordinate is 0 */ + y = wp[win].y_cursor; + if (x == 0) /* use cursor column if x-coordinate is 0 */ + x = wp[win].x_cursor; + if (x <= wp[win].left || x > wp[win].x_size - wp[win].right) + x = wp[win].left + 1; + + /* Move the cursor */ + + wp[win].y_cursor = y; + wp[win].x_cursor = x; + + if (win == cwin) + update_cursor (); + +}/* z_set_cursor */ + +/* + * z_set_margins, set the left and right margins of a window. + * + * zargs[0] = left margin in pixels + * zargs[1] = right margin in pixels + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_margins (void) +{ + zword win = winarg2 (); + + flush_buffer (); + + wp[win].left = zargs[0]; + wp[win].right = zargs[1]; + + /* Protect the margins */ + + if (wp[win].x_cursor <= zargs[0] || wp[win].x_cursor > wp[win].x_size - zargs[1]) { + + wp[win].x_cursor = zargs[0] + 1; + + if (win == cwin) + update_cursor (); + + } + +}/* z_set_margins */ + +/* + * z_set_text_style, set the style for text output. + * + * zargs[0] = style flags to set or 0 to reset text style + * + */ + +void z_set_text_style (void) +{ + zword win = (h_version == V6) ? cwin : 0; + zword style = zargs[0]; + + wp[win].style |= style; + + if (style == 0) + wp[win].style = 0; + + refresh_text_style (); + +}/* z_set_text_style */ + +/* + * z_set_window, select the current window. + * + * zargs[0] = window to be selected (-3 is the current one) + * + */ + +void z_set_window (void) +{ + + set_window (winarg0 ()); + +}/* z_set_window */ + +/* + * pad_status_line + * + * Pad the status line with spaces up to the given position. + * + */ + +static void pad_status_line (int column) +{ + int spaces; + + flush_buffer (); + + spaces = units_left () / os_char_width (' ') - column; + + /* while (spaces--) */ + /* Justin Wesley's fix for narrow displays (Agenda PDA) */ + while (spaces-- > 0) + screen_char (' '); + +}/* pad_status_line */ + +/* + * z_show_status, display the status line for V1 to V3 games. + * + * no zargs used + * + */ + +void z_show_status (void) +{ + zword global0; + zword global1; + zword global2; + zword addr; + + bool brief = FALSE; + + /* One V5 game (Wishbringer Solid Gold) contains this opcode by + accident, so just return if the version number does not fit */ + + if (h_version >= V4) + return; + + /* Read all relevant global variables from the memory of the + Z-machine into local variables */ + + addr = h_globals; + LOW_WORD (addr, global0) + addr += 2; + LOW_WORD (addr, global1) + addr += 2; + LOW_WORD (addr, global2) + + /* Frotz uses window 7 for the status line. Don't forget to select + reverse and fixed width text style */ + + set_window (7); + + print_char (ZC_NEW_STYLE); + print_char (REVERSE_STYLE | FIXED_WIDTH_STYLE); + + /* If the screen width is below 55 characters then we have to use + the brief status line format */ + + if (h_screen_cols < 55) + brief = TRUE; + + /* Print the object description for the global variable 0 */ + + print_char (' '); + print_object (global0); + + /* A header flag tells us whether we have to display the current + time or the score/moves information */ + + if (h_config & CONFIG_TIME) { /* print hours and minutes */ + + zword hours = (global1 + 11) % 12 + 1; + + pad_status_line (brief ? 15 : 20); + + print_string ("Time: "); + + if (hours < 10) + print_char (' '); + print_num (hours); + + print_char (':'); + + if (global2 < 10) + print_char ('0'); + print_num (global2); + + print_char (' '); + + print_char ((global1 >= 12) ? 'p' : 'a'); + print_char ('m'); + + } else { /* print score and moves */ + + pad_status_line (brief ? 15 : 30); + + print_string (brief ? "S: " : "Score: "); + print_num (global1); + + pad_status_line (brief ? 8 : 14); + + print_string (brief ? "M: " : "Moves: "); + print_num (global2); + + } + + /* Pad the end of the status line with spaces */ + + pad_status_line (0); + + /* Return to the lower window */ + + set_window (0); + +}/* z_show_status */ + +/* + * z_split_window, split the screen into an upper (1) and lower (0) window. + * + * zargs[0] = height of upper window in screen units (V6) or #lines + * + */ + +void z_split_window (void) +{ + + split_window (zargs[0]); + +}/* z_split_window */ + +/* + * z_window_size, change the width and height of a window. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = new height in screen units + * zargs[2] = new width in screen units + * + */ + +void z_window_size (void) +{ + zword win = winarg0 (); + + flush_buffer (); + + wp[win].y_size = zargs[1]; + wp[win].x_size = zargs[2]; + + /* Keep the cursor within the window */ + + if (wp[win].y_cursor > zargs[1] || wp[win].x_cursor > zargs[2]) + reset_cursor (win); + +}/* z_window_size */ + +/* + * z_window_style, set / clear / toggle window attributes. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = window attribute flags + * zargs[2] = operation to perform (optional, defaults to 0) + * + */ + +void z_window_style (void) +{ + zword win = winarg0 (); + zword flags = zargs[1]; + + flush_buffer (); + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + + /* Set window style */ + + switch (zargs[2]) { + case 0: wp[win].attribute = flags; break; + case 1: wp[win].attribute |= flags; break; + case 2: wp[win].attribute &= ~flags; break; + case 3: wp[win].attribute ^= flags; break; + } + + if (cwin == win) + update_attributes (); + +}/* z_window_style */ diff --git a/apps/plugins/frotz/setup.h b/apps/plugins/frotz/setup.h new file mode 100644 index 0000000000..a9b9360122 --- /dev/null +++ b/apps/plugins/frotz/setup.h @@ -0,0 +1,67 @@ +/* + * Various status thingies for the interpreter and interface. + * + */ + +typedef struct frotz_setup_struct { + int attribute_assignment; /* done */ + int attribute_testing; /* done */ + int context_lines; /* done */ + int object_locating; /* done */ + int object_movement; /* done */ + int left_margin; /* done */ + int right_margin; /* done */ + int ignore_errors; /* done */ + int interpreter_number; /* Just dumb frotz now */ + int piracy; /* done */ + int undo_slots; /* done */ + int expand_abbreviations; /* done */ + int script_cols; /* done */ + int save_quetzal; /* done */ + int sound; /* done */ + int err_report_mode; /* done */ +} f_setup_t; + +extern f_setup_t f_setup; + + +typedef struct zcode_header_struct { + zbyte h_version; + zbyte h_config; + zword h_release; + zword h_resident_size; + zword h_start_pc; + zword h_dictionary; + zword h_objects; + zword h_globals; + zword h_dynamic_size; + zword h_flags; + zbyte h_serial[6]; + zword h_abbreviations; + zword h_file_size; + zword h_checksum; + zbyte h_interpreter_number; + zbyte h_interpreter_version; + zbyte h_screen_rows; + zbyte h_screen_cols; + zword h_screen_width; + zword h_screen_height; + zbyte h_font_height; + zbyte h_font_width; + zword h_functions_offset; + zword h_strings_offset; + zbyte h_default_background; + zbyte h_default_foreground; + zword h_terminating_keys; + zword h_line_width; + zbyte h_standard_high; + zbyte h_standard_low; + zword h_alphabet; + zword h_extension_table; + zbyte h_user_name[8]; + + zword hx_table_size; + zword hx_mouse_x; + zword hx_mouse_y; + zword hx_unicode_table; +} z_header_t; diff --git a/apps/plugins/frotz/sound.c b/apps/plugins/frotz/sound.c new file mode 100644 index 0000000000..ea0e3570e6 --- /dev/null +++ b/apps/plugins/frotz/sound.c @@ -0,0 +1,204 @@ +/* sound.c - Sound effect function + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +#ifdef DJGPP +#include "djfrotz.h" +#endif + +#define EFFECT_PREPARE 1 +#define EFFECT_PLAY 2 +#define EFFECT_STOP 3 +#define EFFECT_FINISH_WITH 4 + +extern int direct_call (zword); + +static zword routine = 0; + +static int next_sample = 0; +static int next_volume = 0; + +static bool locked = FALSE; +static bool playing = FALSE; + +/* + * init_sound + * + * Initialize sound variables. + * + */ + +void init_sound (void) +{ + locked = FALSE; + playing = FALSE; +} /* init_sound */ + + +/* + * start_sample + * + * Call the IO interface to play a sample. + * + */ + +static void start_sample (int number, int volume, int repeats, zword eos) +{ + + static zbyte lh_repeats[] = { + 0x00, 0x00, 0x00, 0x01, 0xff, + 0x00, 0x01, 0x01, 0x01, 0x01, + 0xff, 0x01, 0x01, 0xff, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (story_id == LURKING_HORROR) + repeats = lh_repeats[number]; + + os_start_sample (number, volume, repeats, eos); + + routine = eos; + playing = TRUE; + +}/* start_sample */ + +/* + * start_next_sample + * + * Play a sample that has been delayed until the previous sound effect has + * finished. This is necessary for two samples in The Lurking Horror that + * immediately follow other samples. + * + */ + +static void start_next_sample (void) +{ + + if (next_sample != 0) + start_sample (next_sample, next_volume, 0, 0); + + next_sample = 0; + next_volume = 0; + +}/* start_next_sample */ + +/* + * end_of_sound + * + * Call the Z-code routine which was given as the last parameter of + * a sound_effect call. This function may be called from a hardware + * interrupt (which requires extremely careful programming). + * + */ + +void end_of_sound (void) +{ + +#if defined(DJGPP) && defined(SOUND_SUPPORT) + end_of_sound_flag = 0; +#endif + + playing = FALSE; + + if (!locked) { + + if (story_id == LURKING_HORROR) + start_next_sample (); + + direct_call (routine); + + } + +}/* end_of_sound */ + +/* + * z_sound_effect, load / play / stop / discard a sound effect. + * + * zargs[0] = number of bleep (1 or 2) or sample + * zargs[1] = operation to perform (samples only) + * zargs[2] = repeats and volume (play sample only) + * zargs[3] = end-of-sound routine (play sample only, optional) + * + * Note: Volumes range from 1 to 8, volume 255 is the default volume. + * Repeats are stored in the high byte, 255 is infinite loop. + * + */ + +void z_sound_effect (void) +{ + zword number = zargs[0]; + zword effect = zargs[1]; + zword volume = zargs[2]; + + /* By default play sound 1 at volume 8 */ + if (zargc < 1) + number = 1; + if (zargc < 2) + effect = EFFECT_PLAY; + if (zargc < 3) + volume = 8; + + if (number >= 3 || number == 0) { + + locked = TRUE; + + if (story_id == LURKING_HORROR && (number == 9 || number == 16)) { + + if (effect == EFFECT_PLAY) { + + next_sample = number; + next_volume = volume; + + locked = FALSE; + + if (!playing) + start_next_sample (); + + } else locked = FALSE; + + return; + + } + + playing = FALSE; + + switch (effect) { + + case EFFECT_PREPARE: + os_prepare_sample (number); + break; + case EFFECT_PLAY: + start_sample (number, lo (volume), hi (volume), (zargc == 4) ? zargs[3] : 0); + break; + case EFFECT_STOP: + os_stop_sample (number); + break; + case EFFECT_FINISH_WITH: + os_finish_with_sample (number); + break; + + } + + locked = FALSE; + + } else os_beep (number); + +}/* z_sound_effect */ diff --git a/apps/plugins/frotz/stream.c b/apps/plugins/frotz/stream.c new file mode 100644 index 0000000000..4ccb44451c --- /dev/null +++ b/apps/plugins/frotz/stream.c @@ -0,0 +1,365 @@ +/* stream.c - IO stream implementation + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +extern bool handle_hot_key (zchar); + +extern bool validate_click (void); + +extern void replay_open (void); +extern void replay_close (void); +extern void memory_open (zword, zword, bool); +extern void memory_close (void); +extern void record_open (void); +extern void record_close (void); +extern void script_open (void); +extern void script_close (void); + +extern void memory_word (const zchar *); +extern void memory_new_line (void); +extern void record_write_key (zchar); +extern void record_write_input (const zchar *, zchar); +extern void script_char (zchar); +extern void script_word (const zchar *); +extern void script_new_line (void); +extern void script_write_input (const zchar *, zchar); +extern void script_erase_input (const zchar *); +extern void script_mssg_on (void); +extern void script_mssg_off (void); +extern void screen_char (zchar); +extern void screen_word (const zchar *); +extern void screen_new_line (void); +extern void screen_write_input (const zchar *, zchar); +extern void screen_erase_input (const zchar *); +extern void screen_mssg_on (void); +extern void screen_mssg_off (void); + +extern zchar replay_read_key (void); +extern zchar replay_read_input (zchar *); +extern zchar console_read_key (zword); +extern zchar console_read_input (int, zchar *, zword, bool); + +extern int direct_call (zword); + +/* + * stream_mssg_on + * + * Start printing a "debugging" message. + * + */ + +void stream_mssg_on (void) +{ + + flush_buffer (); + + if (ostream_screen) + screen_mssg_on (); + if (ostream_script && enable_scripting) + script_mssg_on (); + + message = TRUE; + +}/* stream_mssg_on */ + +/* + * stream_mssg_off + * + * Stop printing a "debugging" message. + * + */ + +void stream_mssg_off (void) +{ + + flush_buffer (); + + if (ostream_screen) + screen_mssg_off (); + if (ostream_script && enable_scripting) + script_mssg_off (); + + message = FALSE; + +}/* stream_mssg_off */ + +/* + * z_output_stream, open or close an output stream. + * + * zargs[0] = stream to open (positive) or close (negative) + * zargs[1] = address to redirect output to (stream 3 only) + * zargs[2] = width of redirected output (stream 3 only, optional) + * + */ + +void z_output_stream (void) +{ + + flush_buffer (); + + switch ((short) zargs[0]) { + + case 1: ostream_screen = TRUE; + break; + case -1: ostream_screen = FALSE; + break; + case 2: if (!ostream_script) script_open (); + break; + case -2: if (ostream_script) script_close (); + break; + case 3: memory_open (zargs[1], zargs[2], zargc >= 3); + break; + case -3: memory_close (); + break; + case 4: if (!ostream_record) record_open (); + break; + case -4: if (ostream_record) record_close (); + break; + + } + +}/* z_output_stream */ + +/* + * stream_char + * + * Send a single character to the output stream. + * + */ + +void stream_char (zchar c) +{ + + if (ostream_screen) + screen_char (c); + if (ostream_script && enable_scripting) + script_char (c); + +}/* stream_char */ + +/* + * stream_word + * + * Send a string of characters to the output streams. + * + */ + +void stream_word (const zchar *s) +{ + + if (ostream_memory && !message) + + memory_word (s); + + else { + + if (ostream_screen) + screen_word (s); + if (ostream_script && enable_scripting) + script_word (s); + + } + +}/* stream_word */ + +/* + * stream_new_line + * + * Send a newline to the output streams. + * + */ + +void stream_new_line (void) +{ + + if (ostream_memory && !message) + + memory_new_line (); + + else { + + if (ostream_screen) + screen_new_line (); + if (ostream_script && enable_scripting) + script_new_line (); + + } + +}/* stream_new_line */ + +/* + * z_input_stream, select an input stream. + * + * zargs[0] = input stream to be selected + * + */ + +void z_input_stream (void) +{ + + flush_buffer (); + + if (zargs[0] == 0 && istream_replay) + replay_close (); + if (zargs[0] == 1 && !istream_replay) + replay_open (); + +}/* z_input_stream */ + +/* + * stream_read_key + * + * Read a single keystroke from the current input stream. + * + */ + +zchar stream_read_key ( zword timeout, zword routine, + bool hot_keys ) +{ + zchar key = ZC_BAD; + + flush_buffer (); + + /* Read key from current input stream */ + +continue_input: + + do { + + if (istream_replay) + key = replay_read_key (); + else + key = console_read_key (timeout); + + } while (key == ZC_BAD); + + /* Verify mouse clicks */ + + if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK) + if (!validate_click ()) + goto continue_input; + + /* Copy key to the command file */ + + if (ostream_record && !istream_replay) + record_write_key (key); + + /* Handle timeouts */ + + if (key == ZC_TIME_OUT) + if (direct_call (routine) == 0) + goto continue_input; + + /* Handle hot keys */ + + if (hot_keys && key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) { + + if (h_version == V4 && key == ZC_HKEY_UNDO) + goto continue_input; + if (!handle_hot_key (key)) + goto continue_input; + + return ZC_BAD; + + } + + /* Return key */ + + return key; + +}/* stream_read_key */ + +/* + * stream_read_input + * + * Read a line of input from the current input stream. + * + */ + +zchar stream_read_input ( int max, zchar *buf, + zword timeout, zword routine, + bool hot_keys, + bool no_scripting ) +{ + zchar key = ZC_BAD; + + flush_buffer (); + + /* Remove initial input from the transscript file or from the screen */ + + if (ostream_script && enable_scripting && !no_scripting) + script_erase_input (buf); + if (istream_replay) + screen_erase_input (buf); + + /* Read input line from current input stream */ + +continue_input: + + do { + + if (istream_replay) + key = replay_read_input (buf); + else + key = console_read_input (max, buf, timeout, key != ZC_BAD); + + } while (key == ZC_BAD); + + /* Verify mouse clicks */ + + if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK) + if (!validate_click ()) + goto continue_input; + + /* Copy input line to the command file */ + + if (ostream_record && !istream_replay) + record_write_input (buf, key); + + /* Handle timeouts */ + + if (key == ZC_TIME_OUT) + if (direct_call (routine) == 0) + goto continue_input; + + /* Handle hot keys */ + + if (hot_keys && key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) { + + if (!handle_hot_key (key)) + goto continue_input; + + return ZC_BAD; + + } + + /* Copy input line to transscript file or to the screen */ + + if (ostream_script && enable_scripting && !no_scripting) + script_write_input (buf, key); + if (istream_replay) + screen_write_input (buf, key); + + /* Return terminating key */ + + return key; + +}/* stream_read_input */ diff --git a/apps/plugins/frotz/table.c b/apps/plugins/frotz/table.c new file mode 100644 index 0000000000..eb3a16366d --- /dev/null +++ b/apps/plugins/frotz/table.c @@ -0,0 +1,193 @@ +/* table.c - Table handling opcodes + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +/* + * z_copy_table, copy a table or fill it with zeroes. + * + * zargs[0] = address of table + * zargs[1] = destination address or 0 for fill + * zargs[2] = size of table + * + * Note: Copying is safe even when source and destination overlap; but + * if zargs[1] is negative the table _must_ be copied forwards. + * + */ + +void z_copy_table (void) +{ + zword addr; + zword size = zargs[2]; + zbyte value; + int i; + + if (zargs[1] == 0) /* zero table */ + + for (i = 0; i < size; i++) + storeb ((zword) (zargs[0] + i), 0); + + else if ((short) size < 0 || zargs[0] > zargs[1]) /* copy forwards */ + + for (i = 0; i < (((short) size < 0) ? - (short) size : size); i++) { + addr = zargs[0] + i; + LOW_BYTE (addr, value) + storeb ((zword) (zargs[1] + i), value); + } + + else /* copy backwards */ + + for (i = size - 1; i >= 0; i--) { + addr = zargs[0] + i; + LOW_BYTE (addr, value) + storeb ((zword) (zargs[1] + i), value); + } + +}/* z_copy_table */ + +/* + * z_loadb, store a value from a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + * + */ + +void z_loadb (void) +{ + zword addr = zargs[0] + zargs[1]; + zbyte value; + + LOW_BYTE (addr, value) + + store (value); + +}/* z_loadb */ + +/* + * z_loadw, store a value from a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + * + */ + +void z_loadw (void) +{ + zword addr = zargs[0] + 2 * zargs[1]; + zword value; + + LOW_WORD (addr, value) + + store (value); + +}/* z_loadw */ + +/* + * z_scan_table, find and store the address of a target within a table. + * + * zargs[0] = target value to be searched for + * zargs[1] = address of table + * zargs[2] = number of table entries to check value against + * zargs[3] = type of table (optional, defaults to 0x82) + * + * Note: The table is a word array if bit 7 of zargs[3] is set; otherwise + * it's a byte array. The lower bits hold the address step. + * + */ + +void z_scan_table (void) +{ + zword addr = zargs[1]; + int i; + + /* Supply default arguments */ + + if (zargc < 4) + zargs[3] = 0x82; + + /* Scan byte or word array */ + + for (i = 0; i < zargs[2]; i++) { + + if (zargs[3] & 0x80) { /* scan word array */ + + zword wvalue; + + LOW_WORD (addr, wvalue) + + if (wvalue == zargs[0]) + goto finished; + + } else { /* scan byte array */ + + zbyte bvalue; + + LOW_BYTE (addr, bvalue) + + if (bvalue == zargs[0]) + goto finished; + + } + + addr += zargs[3] & 0x7f; + + } + + addr = 0; + +finished: + + store (addr); + branch (addr); + +}/* z_scan_table */ + +/* + * z_storeb, write a byte into a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + * + */ + +void z_storeb (void) +{ + + storeb ((zword) (zargs[0] + zargs[1]), zargs[2]); + +}/* z_storeb */ + +/* + * z_storew, write a word into a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + * + */ + +void z_storew (void) +{ + + storew ((zword) (zargs[0] + 2 * zargs[1]), zargs[2]); + +}/* z_storew */ diff --git a/apps/plugins/frotz/text.c b/apps/plugins/frotz/text.c new file mode 100644 index 0000000000..8145cfea29 --- /dev/null +++ b/apps/plugins/frotz/text.c @@ -0,0 +1,1109 @@ +/* text.c - Text manipulation functions + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +enum string_type { + LOW_STRING, ABBREVIATION, HIGH_STRING, EMBEDDED_STRING, VOCABULARY +}; + +extern zword object_name (zword); + +static zchar decoded[10]; +static zword encoded[3]; + +/* + * According to Matteo De Luigi , + * 0xab and 0xbb were in each other's proper positions. + * Sat Apr 21, 2001 + */ +static zchar zscii_to_latin1[] = { + 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, 0x00, 0x00, 0xa1, 0xbf +}; + +/* + * translate_from_zscii + * + * Map a ZSCII character onto the ISO Latin-1 alphabet. + * + */ + +zchar translate_from_zscii (zbyte c) +{ + + if (c == 0xfc) + return ZC_MENU_CLICK; + if (c == 0xfd) + return ZC_DOUBLE_CLICK; + if (c == 0xfe) + return ZC_SINGLE_CLICK; + + if (c >= 0x9b && story_id != BEYOND_ZORK) { + + if (hx_unicode_table != 0) { /* game has its own Unicode table */ + + zbyte N; + + LOW_BYTE (hx_unicode_table, N) + + if (c - 0x9b < N) { + + zword addr = hx_unicode_table + 1 + 2 * (c - 0x9b); + zword unicode; + + LOW_WORD (addr, unicode) + + return (unicode < 0x100) ? (zchar) unicode : '?'; + + } else return '?'; + + } else /* game uses standard set */ + + if (c <= 0xdf) { + + if (c == 0xdc || c == 0xdd) /* Oe and oe ligatures */ + return '?'; /* are not ISO-Latin 1 */ + + return zscii_to_latin1[c - 0x9b]; + + } else return '?'; + } + + return c; + +}/* translate_from_zscii */ + +/* + * translate_to_zscii + * + * Map an ISO Latin-1 character onto the ZSCII alphabet. + * + */ + +zbyte translate_to_zscii (zchar c) +{ + int i; + + if (c == ZC_SINGLE_CLICK) + return 0xfe; + if (c == ZC_DOUBLE_CLICK) + return 0xfd; + if (c == ZC_MENU_CLICK) + return 0xfc; + + if (c >= ZC_LATIN1_MIN) { + + if (hx_unicode_table != 0) { /* game has its own Unicode table */ + + zbyte N; + int i; + + LOW_BYTE (hx_unicode_table, N) + + for (i = 0x9b; i < 0x9b + N; i++) { + + zword addr = hx_unicode_table + 1 + 2 * (i - 0x9b); + zword unicode; + + LOW_WORD (addr, unicode) + + if (c == unicode) + return (zbyte) i; + + } + + return '?'; + + } else { /* game uses standard set */ + + for (i = 0x9b; i <= 0xdf; i++) + if (c == zscii_to_latin1[i - 0x9b]) + return (zbyte) i; + + return '?'; + + } + } + + if (c == 0) /* Safety thing from David Kinder */ + c = '?'; /* regarding his Unicode patches */ + /* Sept 15, 2002 */ + + return c; + +}/* translate_to_zscii */ + +/* + * alphabet + * + * Return a character from one of the three character sets. + * + */ + +static zchar alphabet (int set, int index) +{ + + if (h_alphabet != 0) { /* game uses its own alphabet */ + + zbyte c; + + zword addr = h_alphabet + 26 * set + index; + LOW_BYTE (addr, c) + + return translate_from_zscii (c); + + } else /* game uses default alphabet */ + + if (set == 0) + return 'a' + index; + else if (set == 1) + return 'A' + index; + else if (h_version == V1) + return " 0123456789.,!?_#'\"/\\<-:()"[index]; + else + return " ^0123456789.,!?_#'\"/\\-:()"[index]; + +}/* alphabet */ + +/* + * load_string + * + * Copy a ZSCII string from the memory to the global "decoded" string. + * + */ + +static void load_string (zword addr, zword length) +{ + int resolution = (h_version <= V3) ? 2 : 3; + int i = 0; + + while (i < 3 * resolution) + + if (i < length) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + decoded[i++] = translate_from_zscii (c); + + } else decoded[i++] = 0; + +}/* load_string */ + +/* + * encode_text + * + * Encode the Unicode text in the global "decoded" string then write + * the result to the global "encoded" array. (This is used to look up + * words in the dictionary.) Up to V3 the vocabulary resolution is + * two, since V4 it is three words. Because each word contains three + * Z-characters, that makes six or nine Z-characters respectively. + * Longer words are chopped to the proper size, shorter words are are + * padded out with 5's. For word completion we pad with 0s and 31s, + * the minimum and maximum Z-characters. + * + */ + +static void encode_text (int padding) +{ + static zchar again[] = { 'a', 'g', 'a', 'i', 'n', 0 }; + static zchar examine[] = { 'e', 'x', 'a', 'm', 'i', 'n', 'e', 0 }; + static zchar wait[] = { 'w', 'a', 'i', 't', 0 }; + + zbyte zchars[12]; + const zchar *ptr = decoded; + zchar c; + int resolution = (h_version <= V3) ? 2 : 3; + int i = 0; + + /* Expand abbreviations that some old Infocom games lack */ + + if (f_setup.expand_abbreviations) + + if (padding == 0x05 && decoded[1] == 0) + + switch (decoded[0]) { + case 'g': ptr = again; break; + case 'x': ptr = examine; break; + case 'z': ptr = wait; break; + } + + /* Translate string to a sequence of Z-characters */ + + while (i < 3 * resolution) + + if ((c = *ptr++) != 0) { + + int index, set; + zbyte c2; + + /* Search character in the alphabet */ + + for (set = 0; set < 3; set++) + for (index = 0; index < 26; index++) + if (c == alphabet (set, index)) + goto letter_found; + + /* Character not found, store its ZSCII value */ + + c2 = translate_to_zscii (c); + + zchars[i++] = 5; + zchars[i++] = 6; + zchars[i++] = c2 >> 5; + zchars[i++] = c2 & 0x1f; + + continue; + + letter_found: + + /* Character found, store its index */ + + if (set != 0) + zchars[i++] = ((h_version <= V2) ? 1 : 3) + set; + + zchars[i++] = index + 6; + + } else zchars[i++] = padding; + + /* Three Z-characters make a 16bit word */ + + for (i = 0; i < resolution; i++) + + encoded[i] = + (zchars[3 * i + 0] << 10) | + (zchars[3 * i + 1] << 5) | + (zchars[3 * i + 2]); + + encoded[resolution - 1] |= 0x8000; + +}/* encode_text */ + +/* + * z_check_unicode, test if a unicode character can be read and printed. + * + * zargs[0] = Unicode + * + */ + +void z_check_unicode (void) +{ + zword c = zargs[0]; + + if (c >= 0x20 && c <= 0x7e) + store (3); + else if (c == 0xa0) + store (1); + else if (c >= 0xa1 && c <= 0xff) + store (3); + else + store (0); + +}/* z_check_unicode */ + +/* + * z_encode_text, encode a ZSCII string for use in a dictionary. + * + * zargs[0] = address of text buffer + * zargs[1] = length of ASCII string + * zargs[2] = offset of ASCII string within the text buffer + * zargs[3] = address to store encoded text in + * + * This is a V5+ opcode and therefore the dictionary resolution must be + * three 16bit words. + * + */ + +void z_encode_text (void) +{ + int i; + + load_string ((zword) (zargs[0] + zargs[2]), zargs[1]); + + encode_text (0x05); + + for (i = 0; i < 3; i++) + storew ((zword) (zargs[3] + 2 * i), encoded[i]); + +}/* z_encode_text */ + +/* + * decode_text + * + * Convert encoded text to Unicode. The encoded text consists of 16bit + * words. Every word holds 3 Z-characters (5 bits each) plus a spare + * bit to mark the last word. The Z-characters translate to ZSCII by + * looking at the current current character set. Some select another + * character set, others refer to abbreviations. + * + * There are several different string types: + * + * LOW_STRING - from the lower 64KB (byte address) + * ABBREVIATION - from the abbreviations table (word address) + * HIGH_STRING - from the end of the memory map (packed address) + * EMBEDDED_STRING - from the instruction stream (at PC) + * VOCABULARY - from the dictionary (byte address) + * + * The last type is only used for word completion. + * + */ + +#define outchar(c) if (st==VOCABULARY) *ptr++=c; else print_char(c) + +static void decode_text (enum string_type st, zword addr) +{ + zchar *ptr; + long byte_addr; + zchar c2; + zword code; + zbyte c, prev_c = 0; + int shift_state = 0; + int shift_lock = 0; + int status = 0; + + ptr = NULL; /* makes compilers shut up */ + byte_addr = 0; + + /* Calculate the byte address if necessary */ + + if (st == ABBREVIATION) + + byte_addr = (long) addr << 1; + + else if (st == HIGH_STRING) { + + if (h_version <= V3) + byte_addr = (long) addr << 1; + else if (h_version <= V5) + byte_addr = (long) addr << 2; + else if (h_version <= V7) + byte_addr = ((long) addr << 2) + ((long) h_strings_offset << 3); + else /* h_version == V8 */ + byte_addr = (long) addr << 3; + + if (byte_addr >= story_size) + runtime_error (ERR_ILL_PRINT_ADDR); + + } + + /* Loop until a 16bit word has the highest bit set */ + + if (st == VOCABULARY) + ptr = decoded; + + do { + + int i; + + /* Fetch the next 16bit word */ + + if (st == LOW_STRING || st == VOCABULARY) { + LOW_WORD (addr, code) + addr += 2; + } else if (st == HIGH_STRING || st == ABBREVIATION) { + HIGH_WORD (byte_addr, code) + byte_addr += 2; + } else + CODE_WORD (code) + + /* Read its three Z-characters */ + + for (i = 10; i >= 0; i -= 5) { + + zword abbr_addr; + zword ptr_addr; + + c = (code >> i) & 0x1f; + + switch (status) { + + case 0: /* normal operation */ + + if (shift_state == 2 && c == 6) + status = 2; + + else if (h_version == V1 && c == 1) + new_line (); + + else if (h_version >= V2 && shift_state == 2 && c == 7) + new_line (); + + else if (c >= 6) + outchar (alphabet (shift_state, c - 6)); + + else if (c == 0) + outchar (' '); + + else if (h_version >= V2 && c == 1) + status = 1; + + else if (h_version >= V3 && c <= 3) + status = 1; + + else { + + shift_state = (shift_lock + (c & 1) + 1) % 3; + + if (h_version <= V2 && c >= 4) + shift_lock = shift_state; + + break; + + } + + shift_state = shift_lock; + + break; + + case 1: /* abbreviation */ + + ptr_addr = h_abbreviations + 64 * (prev_c - 1) + 2 * c; + + LOW_WORD (ptr_addr, abbr_addr) + decode_text (ABBREVIATION, abbr_addr); + + status = 0; + break; + + case 2: /* ZSCII character - first part */ + + status = 3; + break; + + case 3: /* ZSCII character - second part */ + + c2 = translate_from_zscii ((prev_c << 5) | c); + outchar (c2); + + status = 0; + break; + + } + + prev_c = c; + + } + + } while (!(code & 0x8000)); + + if (st == VOCABULARY) + *ptr = 0; + +}/* decode_text */ + +#undef outchar + +/* + * z_new_line, print a new line. + * + * no zargs used + * + */ + +void z_new_line (void) +{ + + new_line (); + +}/* z_new_line */ + +/* + * z_print, print a string embedded in the instruction stream. + * + * no zargs used + * + */ + +void z_print (void) +{ + + decode_text (EMBEDDED_STRING, 0); + +}/* z_print */ + +/* + * z_print_addr, print a string from the lower 64KB. + * + * zargs[0] = address of string to print + * + */ + +void z_print_addr (void) +{ + + decode_text (LOW_STRING, zargs[0]); + +}/* z_print_addr */ + +/* + * z_print_char print a single ZSCII character. + * + * zargs[0] = ZSCII character to be printed + * + */ + +void z_print_char (void) +{ + + print_char (translate_from_zscii (zargs[0])); + +}/* z_print_char */ + +/* + * z_print_form, print a formatted table. + * + * zargs[0] = address of formatted table to be printed + * + */ + +void z_print_form (void) +{ + zword count; + zword addr = zargs[0]; + + bool first = TRUE; + + for (;;) { + + LOW_WORD (addr, count) + addr += 2; + + if (count == 0) + break; + + if (!first) + new_line (); + + while (count--) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + print_char (translate_from_zscii (c)); + + } + + first = FALSE; + + } + +}/* z_print_form */ + +/* + * print_num + * + * Print a signed 16bit number. + * + */ + +void print_num (zword value) +{ + int i; + + /* Print sign */ + + if ((short) value < 0) { + print_char ('-'); + value = - (short) value; + } + + /* Print absolute value */ + + for (i = 10000; i != 0; i /= 10) + if (value >= i || i == 1) + print_char ('0' + (value / i) % 10); + +}/* print_num */ + +/* + * z_print_num, print a signed number. + * + * zargs[0] = number to print + * + */ + +void z_print_num (void) +{ + + print_num (zargs[0]); + +}/* z_print_num */ + +/* + * print_object + * + * Print an object description. + * + */ + +void print_object (zword object) +{ + zword addr = object_name (object); + zword code = 0x94a5; + zbyte length; + + LOW_BYTE (addr, length) + addr++; + + if (length != 0) + LOW_WORD (addr, code) + + if (code == 0x94a5) { /* encoded text 0x94a5 == empty string */ + + print_string ("object#"); /* supply a generic name */ + print_num (object); /* for anonymous objects */ + + } else decode_text (LOW_STRING, addr); + +}/* print_object */ + +/* + * z_print_obj, print an object description. + * + * zargs[0] = number of object to be printed + * + */ + +void z_print_obj (void) +{ + + print_object (zargs[0]); + +}/* z_print_obj */ + +/* + * z_print_paddr, print the string at the given packed address. + * + * zargs[0] = packed address of string to be printed + * + */ + +void z_print_paddr (void) +{ + + decode_text (HIGH_STRING, zargs[0]); + +}/* z_print_paddr */ + +/* + * z_print_ret, print the string at PC, print newline then return true. + * + * no zargs used + * + */ + +void z_print_ret (void) +{ + + decode_text (EMBEDDED_STRING, 0); + new_line (); + ret (1); + +}/* z_print_ret */ + +/* + * print_string + * + * Print a string of ASCII characters. + * + */ + +void print_string (const char *s) +{ + char c; + + while ((c = *s++) != 0) + + if (c == '\n') + new_line (); + else + print_char (c); + +}/* print_string */ + +/* + * z_print_unicode + * + * zargs[0] = Unicode + * + */ + +void z_print_unicode (void) +{ + + print_char ((zargs[0] <= 0xff) ? zargs[0] : '?'); + +}/* z_print_unicode */ + +/* + * lookup_text + * + * Scan a dictionary searching for the given word. The first argument + * can be + * + * 0x00 - find the first word which is >= the given one + * 0x05 - find the word which exactly matches the given one + * 0x1f - find the last word which is <= the given one + * + * The return value is 0 if the search fails. + * + */ + +static zword lookup_text (int padding, zword dct) +{ + zword entry_addr; + zword entry_count; + zword entry; + zword addr; + zbyte entry_len; + zbyte sep_count; + int resolution = (h_version <= V3) ? 2 : 3; + int entry_number; + int lower, upper; + int i; + bool sorted; + + encode_text (padding); + + LOW_BYTE (dct, sep_count) /* skip word separators */ + dct += 1 + sep_count; + LOW_BYTE (dct, entry_len) /* get length of entries */ + dct += 1; + LOW_WORD (dct, entry_count) /* get number of entries */ + dct += 2; + + if ((short) entry_count < 0) { /* bad luck, entries aren't sorted */ + + entry_count = - (short) entry_count; + sorted = FALSE; + + } else sorted = TRUE; /* entries are sorted */ + + lower = 0; + upper = entry_count - 1; + + while (lower <= upper) { + + if (sorted) /* binary search */ + entry_number = (lower + upper) / 2; + else /* linear search */ + entry_number = lower; + + entry_addr = dct + entry_number * entry_len; + + /* Compare word to dictionary entry */ + + addr = entry_addr; + + for (i = 0; i < resolution; i++) { + LOW_WORD (addr, entry) + if (encoded[i] != entry) + goto continuing; + addr += 2; + } + + return entry_addr; /* exact match found, return now */ + + continuing: + + if (sorted) /* binary search */ + + if (encoded[i] > entry) + lower = entry_number + 1; + else + upper = entry_number - 1; + + else lower++; /* linear search */ + + } + + /* No exact match has been found */ + + if (padding == 0x05) + return 0; + + entry_number = (padding == 0x00) ? lower : upper; + + if (entry_number == -1 || entry_number == entry_count) + return 0; + + return dct + entry_number * entry_len; + +}/* lookup_text */ + +/* + * tokenise_text + * + * Translate a single word to a token and append it to the token + * buffer. Every token consists of the address of the dictionary + * entry, the length of the word and the offset of the word from + * the start of the text buffer. Unknown words cause empty slots + * if the flag is set (such that the text can be scanned several + * times with different dictionaries); otherwise they are zero. + * + */ + +static void tokenise_text (zword text, zword length, zword from, zword parse, zword dct, bool flag) +{ + zword addr; + zbyte token_max, token_count; + + LOW_BYTE (parse, token_max) + parse++; + LOW_BYTE (parse, token_count) + + if (token_count < token_max) { /* sufficient space left for token? */ + + storeb (parse++, token_count + 1); + + load_string ((zword) (text + from), length); + + addr = lookup_text (0x05, dct); + + if (addr != 0 || !flag) { + + parse += 4 * token_count; + + storew ((zword) (parse + 0), addr); + storeb ((zword) (parse + 2), length); + storeb ((zword) (parse + 3), from); + + } + + } + +}/* tokenise_text */ + +/* + * tokenise_line + * + * Split an input line into words and translate the words to tokens. + * + */ + +void tokenise_line (zword text, zword token, zword dct, bool flag) +{ + zword addr1; + zword addr2; + zbyte length; + zbyte c; + + length = 0; /* makes compilers shut up */ + + /* Use standard dictionary if the given dictionary is zero */ + + if (dct == 0) + dct = h_dictionary; + + /* Remove all tokens before inserting new ones */ + + storeb ((zword) (token + 1), 0); + + /* Move the first pointer across the text buffer searching for the + beginning of a word. If this succeeds, store the position in a + second pointer. Move the first pointer searching for the end of + the word. When it is found, "tokenise" the word. Continue until + the end of the buffer is reached. */ + + addr1 = text; + addr2 = 0; + + if (h_version >= V5) { + addr1++; + LOW_BYTE (addr1, length) + } + + do { + + zword sep_addr; + zbyte sep_count; + zbyte separator; + + /* Fetch next ZSCII character */ + + addr1++; + + if (h_version >= V5 && addr1 == text + 2 + length) + c = 0; + else + LOW_BYTE (addr1, c) + + /* Check for separator */ + + sep_addr = dct; + + LOW_BYTE (sep_addr, sep_count) + sep_addr++; + + do { + + LOW_BYTE (sep_addr, separator) + sep_addr++; + + } while (c != separator && --sep_count != 0); + + /* This could be the start or the end of a word */ + + if (sep_count == 0 && c != ' ' && c != 0) { + + if (addr2 == 0) + addr2 = addr1; + + } else if (addr2 != 0) { + + tokenise_text ( + text, + (zword) (addr1 - addr2), + (zword) (addr2 - text), + token, dct, flag ); + + addr2 = 0; + + } + + /* Translate separator (which is a word in its own right) */ + + if (sep_count != 0) + + tokenise_text ( + text, + (zword) (1), + (zword) (addr1 - text), + token, dct, flag ); + + } while (c != 0); + +}/* tokenise_line */ + +/* + * z_tokenise, make a lexical analysis of a ZSCII string. + * + * zargs[0] = address of string to analyze + * zargs[1] = address of token buffer + * zargs[2] = address of dictionary (optional) + * zargs[3] = set when unknown words cause empty slots (optional) + * + */ + +void z_tokenise (void) +{ + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + if (zargc < 4) + zargs[3] = 0; + + /* Call tokenise_line to do the real work */ + + tokenise_line (zargs[0], zargs[1], zargs[2], zargs[3] != 0); + +}/* z_tokenise */ + +/* + * completion + * + * Scan the vocabulary to complete the last word on the input line + * (similar to "tcsh" under Unix). The return value is + * + * 2 ==> completion is impossible + * 1 ==> completion is ambiguous + * 0 ==> completion is successful + * + * The function also returns a string in its second argument. In case + * of 2, the string is empty; in case of 1, the string is the longest + * extension of the last word on the input line that is common to all + * possible completions (for instance, if the last word on the input + * is "fo" and its only possible completions are "follow" and "folly" + * then the string is "ll"); in case of 0, the string is an extension + * to the last word that results in the only possible completion. + * + */ + +int completion (const zchar *buffer, zchar *result) +{ + zword minaddr; + zword maxaddr; + zchar *ptr; + zchar c; + int len; + int i; + + *result = 0; + + /* Copy last word to "decoded" string */ + + len = 0; + + while ((c = *buffer++) != 0) + + if (c != ' ') { + + if (len < 9) + decoded[len++] = c; + + } else len = 0; + + decoded[len] = 0; + + /* Search the dictionary for first and last possible extensions */ + + minaddr = lookup_text (0x00, h_dictionary); + maxaddr = lookup_text (0x1f, h_dictionary); + + if (minaddr == 0 || maxaddr == 0 || minaddr > maxaddr) + return 2; + + /* Copy first extension to "result" string */ + + decode_text (VOCABULARY, minaddr); + + ptr = result; + + for (i = len; (c = decoded[i]) != 0; i++) + *ptr++ = c; + *ptr = 0; + + /* Merge second extension with "result" string */ + + decode_text (VOCABULARY, maxaddr); + + for (i = len, ptr = result; (c = decoded[i]) != 0; i++, ptr++) + if (*ptr != c) break; + *ptr = 0; + + /* Search was ambiguous or successful */ + + return (minaddr == maxaddr) ? 0 : 1; + +}/* completion */ diff --git a/apps/plugins/frotz/variable.c b/apps/plugins/frotz/variable.c new file mode 100644 index 0000000000..3e5c6e0c09 --- /dev/null +++ b/apps/plugins/frotz/variable.c @@ -0,0 +1,304 @@ +/* variable.c - Variable and stack related opcodes + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Frotz is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +/* + * z_dec, decrement a variable. + * + * zargs[0] = variable to decrement + * + */ + +void z_dec (void) +{ + zword value; + + if (zargs[0] == 0) + (*sp)--; + else if (zargs[0] < 16) + (*(fp - zargs[0]))--; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value--; + SET_WORD (addr, value) + } + +}/* z_dec */ + +/* + * z_dec_chk, decrement a variable and branch if now less than value. + * + * zargs[0] = variable to decrement + * zargs[1] = value to check variable against + * + */ + +void z_dec_chk (void) +{ + zword value; + + if (zargs[0] == 0) + value = --(*sp); + else if (zargs[0] < 16) + value = --(*(fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value--; + SET_WORD (addr, value) + } + + branch ((short) value < (short) zargs[1]); + +}/* z_dec_chk */ + +/* + * z_inc, increment a variable. + * + * zargs[0] = variable to increment + * + */ + +void z_inc (void) +{ + zword value; + + if (zargs[0] == 0) + (*sp)++; + else if (zargs[0] < 16) + (*(fp - zargs[0]))++; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value++; + SET_WORD (addr, value) + } + +}/* z_inc */ + +/* + * z_inc_chk, increment a variable and branch if now greater than value. + * + * zargs[0] = variable to increment + * zargs[1] = value to check variable against + * + */ + +void z_inc_chk (void) +{ + zword value; + + if (zargs[0] == 0) + value = ++(*sp); + else if (zargs[0] < 16) + value = ++(*(fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value++; + SET_WORD (addr, value) + } + + branch ((short) value > (short) zargs[1]); + +}/* z_inc_chk */ + +/* + * z_load, store the value of a variable. + * + * zargs[0] = variable to store + * + */ + +void z_load (void) +{ + zword value; + + if (zargs[0] == 0) + value = *sp; + else if (zargs[0] < 16) + value = *(fp - zargs[0]); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + } + + store (value); + +}/* z_load */ + +/* + * z_pop, pop a value off the game stack and discard it. + * + * no zargs used + * + */ + +void z_pop (void) +{ + + sp++; + +}/* z_pop */ + +/* + * z_pop_stack, pop n values off the game or user stack and discard them. + * + * zargs[0] = number of values to discard + * zargs[1] = address of user stack (optional) + * + */ + +void z_pop_stack (void) +{ + + if (zargc == 2) { /* it's a user stack */ + + zword size; + zword addr = zargs[1]; + + LOW_WORD (addr, size) + + size += zargs[0]; + storew (addr, size); + + } else sp += zargs[0]; /* it's the game stack */ + +}/* z_pop_stack */ + +/* + * z_pull, pop a value off... + * + * a) ...the game or a user stack and store it (V6) + * + * zargs[0] = address of user stack (optional) + * + * b) ...the game stack and write it to a variable (other than V6) + * + * zargs[0] = variable to write value to + * + */ + +void z_pull (void) +{ + zword value; + + if (h_version != V6) { /* not a V6 game, pop stack and write */ + + value = *sp++; + + if (zargs[0] == 0) + *sp = value; + else if (zargs[0] < 16) + *(fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD (addr, value) + } + + } else { /* it's V6, but is there a user stack? */ + + if (zargc == 1) { /* it's a user stack */ + + zword size; + zword addr = zargs[0]; + + LOW_WORD (addr, size) + + size++; + storew (addr, size); + + addr += 2 * size; + LOW_WORD (addr, value) + + } else value = *sp++; /* it's the game stack */ + + store (value); + + } + +}/* z_pull */ + +/* + * z_push, push a value onto the game stack. + * + * zargs[0] = value to push onto the stack + * + */ + +void z_push (void) +{ + + *--sp = zargs[0]; + +}/* z_push */ + +/* + * z_push_stack, push a value onto a user stack then branch if successful. + * + * zargs[0] = value to push onto the stack + * zargs[1] = address of user stack + * + */ + +void z_push_stack (void) +{ + zword size; + zword addr = zargs[1]; + + LOW_WORD (addr, size) + + if (size != 0) { + + storew ((zword) (addr + 2 * size), zargs[0]); + + size--; + storew (addr, size); + + } + + branch (size); + +}/* z_push_stack */ + +/* + * z_store, write a value to a variable. + * + * zargs[0] = variable to be written to + * zargs[1] = value to write + * + */ + +void z_store (void) +{ + zword value = zargs[1]; + + if (zargs[0] == 0) + *sp = value; + else if (zargs[0] < 16) + *(fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD (addr, value) + } + +}/* z_store */ diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index 205bafb12a..985115672d 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -61,3 +61,11 @@ link,viewers/shortcuts_view,- lua,viewers/lua,- ipod,viewers/crypt_firmware,- ipodx,viewers/crypt_firmware,- +z1,viewers/frotz,- +z2,viewers/frotz,- +z3,viewers/frotz,- +z4,viewers/frotz,- +z5,viewers/frotz,- +z6,viewers/frotz,- +z7,viewers/frotz,- +z8,viewers/frotz,- diff --git a/manual/plugins/frotz.tex b/manual/plugins/frotz.tex new file mode 100644 index 0000000000..c1d59e4905 --- /dev/null +++ b/manual/plugins/frotz.tex @@ -0,0 +1,67 @@ +% $Id$ % +\subsection{Frotz} +Frotz is a Z-Machine interpreter for playing Infocom's interactive fiction +games, and newer games using the same format. To start a game open a +\fname{.z1 - .z8} file in the \setting{File Browser}. Most modern games are +in the \fname{.z5} or \fname{.z8} format but the older formats used by +Infocom are supported. + +Z-Machine games are text based and most depend heavily on typed commands. +The virtual keyboard is used for text entry, both for typing entire lines +and for typing single characters when the game requires single character +input. + +Sounds, pictures, colour and Unicode are not currently supported, but +the interpreter informs the game of this and almost all games will +adapt so that they are still playable. This port of Frotz attempts to be +compliant with the Z-Machine Specification version 1.0. + +Some places where you can find Z-Machine games, and information about +interactive fiction: +\begin{itemize} +\item The Interactive Fiction Archive, where many free modern works +can be downloaded: +\url{http://www.ifarchive.org/} +\item The specific folder on the if-archive containing Z-Machine games: +\url{http://www.ifarchive.org/indexes/if-archiveXgamesXzcode.html} +\item The Infocom homepage, with information about how to get the +classic commercial Infocom games: +\url{http://www.csd.uwo.ca/Infocom/} +\item The Frotz homepage (for the original Unix port): +\url{http://frotz.sourceforge.net/} +\item A Beginner's Guide to Playing Interactive Fiction: +\url{http://www.microheaven.com/IFGuide/} +\end{itemize} + +\begin{table} + \begin{btnmap}{}{} + \opt{RECORDER_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOn} + \opt{IPOD_4G_PAD,IPOD_3G_PAD,IRIVER_H10_PAD,GIGABEAT_S_PAD}{\ButtonPlay} + \opt{ONDIO_PAD}{\ButtonMenu} + \opt{IAUDIO_X5_PAD,MROBE100_PAD}{\ButtonPower} + \opt{SANSA_E200_PAD,SANSA_C200_PAD}{\ButtonUp} + \opt{GIGABEAT_PAD}{\ButtonA} + \opt{HAVEREMOTEKEYMAP}{& + \opt{IRIVER_RC_H100_PAD}{\ButtonRCOn} + } + & Display keyboard to enter text\\ + \opt{IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD,IPOD_4G_PAD,IPOD_3G_PAD% + ,SANSA_E200_PAD,SANSA_C200_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD}{\ButtonSelect} + \opt{RECORDER_PAD}{\ButtonPlay} + \opt{ONDIO_PAD}{\ButtonUp} + \opt{IRIVER_H10_PAD}{\ButtonRew} + \opt{COWON_D2_PAD}{\ButtonMenu{}, \TouchCenter{} or \TouchBottomMiddle} + \opt{HAVEREMOTEKEYMAP}{& } + & Press enter\\ + \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonOff} + \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonMenu} + \opt{IAUDIO_X5_PAD,IRIVER_H10_PAD,SANSA_E200_PAD,SANSA_C200_PAD,GIGABEAT_PAD% + ,MROBE100_PAD}{\ButtonPower} + \opt{GIGABEAT_S_PAD}{\ButtonBack} + \opt{COWON_D2_PAD}{\ButtonPower{} or \TouchBottomRight} + \opt{HAVEREMOTEKEYMAP}{& + \opt{IRIVER_RC_H100_PAD}{\ButtonRCStop} + } + & Open Frotz menu (not available at MORE prompts)\\ + \end{btnmap} +\end{table} diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index a7dc3d7bf0..ff332b70c5 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex @@ -135,6 +135,7 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} {}{} Shortcuts & \fname{.link} & \\ Chip-8 Emulator & \fname{.ch8} & \\ + Frotz & \fname{.z1 - .z8} & \\ JPEG Viewer & \fname{.jpg, .jpeg} & \\ Lua scripting language& \fname{.lua} & \\ Midiplay & \fname{.mid, .midi} & \\ @@ -160,6 +161,8 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} \opt{lcd_bitmap}{\input{plugins/chip8emulator.tex}} +\opt{lcd_bitmap}{\input{plugins/frotz.tex}} + \opt{lcd_bitmap}{\input{plugins/jpegviewer.tex}} \opt{large_plugin_buffer}{\input{plugins/lua.tex}}