Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/bitmap_font.h"
namespace Glk {
namespace ZCode {
BitmapFont::BitmapFont(const Graphics::Surface &src, const Common::Point &size,
uint srcWidth, uint srcHeight, unsigned char startingChar, bool isFixedWidth) :
_startingChar(startingChar), _size(size) {
assert(src.format.bytesPerPixel == 1);
assert((src.w % srcWidth) == 0);
assert((src.h % srcHeight) == 0);
// Set up a characters array
_chars.resize((src.w / srcWidth) * (src.h / srcHeight));
// Iterate through loading characters
Common::Rect r(srcWidth, srcHeight);
int charsPerRow = src.w / srcWidth;
for (uint idx = 0; idx < _chars.size(); ++idx) {
r.moveTo((idx % charsPerRow) * srcWidth, (idx / charsPerRow) * srcHeight);
int srcCharWidth = isFixedWidth ? r.width() : getSourceCharacterWidth(idx, src, r);
int destCharWidth = (size.x * srcCharWidth + (srcWidth - 1)) / srcWidth;
Common::Rect charBounds(r.left, r.top, r.left + srcCharWidth, r.bottom);
_chars[idx].create(destCharWidth, size.y, src.format);
_chars[idx].blitFrom(src, charBounds, Common::Rect(0, 0, _chars[idx].w, _chars[idx].h));
}
}
void BitmapFont::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const {
const Graphics::ManagedSurface &c = _chars[chr - _startingChar];
for (int yCtr = 0; yCtr < c.h; ++yCtr) {
const byte *srcP = (const byte *)c.getBasePtr(0, yCtr);
for (int xCtr = 0; xCtr < c.w; ++xCtr, ++srcP) {
if (*srcP)
dst->hLine(x + xCtr, y + yCtr, x + xCtr, color);
}
}
}
int BitmapFont::getSourceCharacterWidth(uint charIndex, const Graphics::Surface &src,
const Common::Rect &charBounds) {
if (charIndex == 0)
// The space character is treated as half the width of bounding area
return charBounds.width() / 2;
// Scan through the rows to find the right most pixel, getting the width from that
int maxWidth = 0, rowX;
for (int y = charBounds.top; y < charBounds.bottom; ++y) {
rowX = 0;
const byte *srcP = (const byte *)src.getBasePtr(charBounds.left, y);
for (int x = 0; x < charBounds.width(); ++x, ++srcP) {
if (*srcP)
rowX = x;
}
maxWidth = MAX(maxWidth, MIN(rowX + 2, (int)charBounds.width()));
}
return maxWidth;
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,106 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_BITMAP_FONT
#define GLK_ZCODE_BITMAP_FONT
#include "common/array.h"
#include "common/rect.h"
#include "graphics/font.h"
#include "graphics/managed_surface.h"
namespace Glk {
namespace ZCode {
/**
* Implements a font stored as a grid on a passed surface
*/
class BitmapFont : public Graphics::Font {
private:
Common::Array<Graphics::ManagedSurface> _chars;
size_t _startingChar;
Common::Point _size;
protected:
/**
* Calculate a character width
*/
int getSourceCharacterWidth(uint charIndex, const Graphics::Surface &src,
const Common::Rect &charBounds);
/**
* Constructor
*/
BitmapFont(const Graphics::Surface &src, const Common::Point &size,
uint srcWidth, uint srcHeight, unsigned char startingChar, bool isFixedWidth);
public:
/**
* Get the font height
*/
int getFontHeight() const override { return _size.y; }
/**
* Get the maximum character width
*/
int getMaxCharWidth() const override { return _size.x; }
/**
* Get the width of the given character
*/
int getCharWidth(uint32 chr) const override { return _chars[chr - _startingChar].w; }
/**
* Draw a character
*/
void drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const override;
};
/**
* Subclass for fixed width fonts
*/
class FixedWidthBitmapFont : public BitmapFont {
public:
/**
* Constructor
*/
FixedWidthBitmapFont(const Graphics::Surface &src, const Common::Point &size,
uint srcWidth = 8, uint srcHeight = 8, unsigned char startingChar = ' ') :
BitmapFont(src, size, srcWidth, srcHeight, startingChar, true) {}
};
/**
* Subclass for fixed width fonts
*/
class VariableWidthBitmapFont : public BitmapFont {
public:
/**
* Constructor
*/
VariableWidthBitmapFont(const Graphics::Surface &src, const Common::Point &size,
uint srcWidth = 8, uint srcHeight = 8, unsigned char startingChar = ' ') :
BitmapFont(src, size, srcWidth, srcHeight, startingChar, false) {}
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,187 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/config.h"
#include "glk/zcode/detection.h"
#include "glk/glk.h"
#include "common/config-manager.h"
#include "common/textconsole.h"
namespace Glk {
namespace ZCode {
const Header::StoryEntry Header::RECORDS[26] = {
{ 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" },
{ MILLIWAYS, 184, "890412" },
{ UNKNOWN, 0, "------" }
};
static uint getConfigBool(const Common::String &profileName, bool defaultVal = false) {
return ConfMan.hasKey(profileName) ? ConfMan.getBool(profileName) : defaultVal;
}
static uint getConfigInt(const Common::String &profileName, uint defaultVal, uint maxVal) {
uint val = ConfMan.hasKey(profileName) ? ConfMan.getInt(profileName) : defaultVal;
if (val > maxVal)
error("Invalid value for configuration value %s", profileName.c_str());
return val;
}
Header::Header() : h_version(0), h_config(0), h_release(0), h_resident_size(0), h_start_pc(0),
h_dictionary(0), h_objects(0), h_globals(0), h_dynamic_size(0), h_flags(0),
h_abbreviations(0), h_file_size(0), h_checksum(0),
h_interpreter_version(0), h_screen_rows(0), h_screen_cols(0), h_screen_width(0),
h_screen_height(0), h_font_height(1), h_font_width(1), h_functions_offset(0),
h_strings_offset(0), h_default_background(0), h_default_foreground(0),
h_terminating_keys(0), h_line_width(0), h_standard_high(1), h_standard_low(1),
h_alphabet(0), h_extension_table(0),
hx_table_size(0), hx_mouse_x(0), hx_mouse_y(0), hx_unicode_table(0),
hx_flags(0), hx_fore_colour(0), hx_back_colour(0), _storyId(UNKNOWN) {
Common::fill(&h_serial[0], &h_serial[6], '\0');
Common::fill(&h_user_name[0], &h_user_name[8], '\0');
h_interpreter_number = getConfigInt("interpreter_number", INTERP_AMIGA, INTERP_TANDY);
if (ConfMan.hasKey("username")) {
Common::String username = ConfMan.get("username");
strncpy((char *)h_user_name, username.c_str(), 7);
}
}
void Header::loadHeader(Common::SeekableReadStream &f) {
f.seek(0);
h_version = f.readByte();
h_config = f.readByte();
if (h_version < V1 || h_version > V8)
error("Unknown Z-code version");
if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED))
error("Byte swapped story file");
h_release = f.readUint16BE();
h_resident_size = f.readUint16BE();
h_start_pc = f.readUint16BE();
h_dictionary = f.readUint16BE();
h_objects = f.readUint16BE();
h_globals = f.readUint16BE();
h_dynamic_size = f.readUint16BE();
h_flags = f.readUint16BE();
f.read(h_serial, 6);
/* Auto-detect buggy story files that need special fixes */
_storyId = UNKNOWN;
for (int i = 0; RECORDS[i]._storyId != UNKNOWN; ++i) {
if (h_release == RECORDS[i]._release) {
if (!strncmp((const char *)h_serial, RECORDS[i]._serial, 6)) {
_storyId = RECORDS[i]._storyId;
break;
}
}
}
h_abbreviations = f.readUint16BE();
h_file_size = f.readUint16BE();
h_checksum = f.readUint16BE();
f.seek(H_FUNCTIONS_OFFSET);
h_functions_offset = f.readUint16BE();
h_strings_offset = f.readUint16BE();
f.seek(H_TERMINATING_KEYS);
h_terminating_keys = f.readUint16BE();
f.seek(H_ALPHABET);
h_alphabet = f.readUint16BE();
h_extension_table = f.readUint16BE();
// Zork Zero Macintosh doesn't have the graphics flag set
if (_storyId == ZORK_ZERO && h_release == 296)
h_flags |= GRAPHICS_FLAG;
}
/*--------------------------------------------------------------------------*/
UserOptions::UserOptions() : _undo_slots(MAX_UNDO_SLOTS), _sound(true), _quetzal(true), _color_enabled(false),
_err_report_mode(ERR_REPORT_ONCE), _ignore_errors(false), _expand_abbreviations(false), _tandyBit(false),
_piracy(false), _script_cols(0), _left_margin(0), _right_margin(0), _defaultBackground(0), _defaultForeground(0) {
}
void UserOptions::initialize(uint hVersion, uint storyId) {
_err_report_mode = getConfigInt("err_report_mode", ERR_REPORT_ONCE, ERR_REPORT_FATAL);
_ignore_errors = getConfigBool("ignore_errors");
_expand_abbreviations = getConfigBool("expand_abbreviations");
_tandyBit = getConfigBool("tandy_bit");
_piracy = getConfigBool("piracy");
_script_cols = getConfigInt("wrap_script_lines", 80, 999);
_left_margin = getConfigInt("left_margin", 0, 999);
_right_margin = getConfigInt("right_margin", 0, 999);
// Debugging flags
_attribute_assignment = getConfigBool("attribute_assignment");
_attribute_testing = getConfigBool("attribute_testing");
_object_locating = getConfigBool("object_locating");
_object_movement = getConfigBool("object_movement");
int defaultFg = hVersion == V6 ? 0 : 0xffffff;
int defaultBg = hVersion == V6 ? 0xffffff : 0x80;
if (storyId == BEYOND_ZORK)
defaultBg = 0;
defaultFg = getConfigInt("foreground", defaultFg, 0xffffff);
defaultBg = getConfigInt("background", defaultBg, 0xffffff);
Graphics::PixelFormat format = g_system->getScreenFormat();
_defaultForeground = format.RGBToColor((defaultFg >> 16) & 0xff, (defaultFg >> 8) & 0xff, defaultFg & 0xff);
_defaultBackground = format.RGBToColor((defaultBg >> 16) & 0xff, (defaultBg >> 8) & 0xff, defaultBg & 0xff);
}
bool UserOptions::isInfocom() const {
return g_vm->getOptions() & OPTION_INFOCOM;
}
} // End of namespace ZCode
} // End of namespace Glk

240
engines/glk/zcode/config.h Normal file
View File

@@ -0,0 +1,240 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_CONFIG
#define GLK_ZCODE_CONFIG
#include "glk/zcode/frotz_types.h"
namespace Glk {
namespace ZCode {
/**
* Configuration flags
*/
enum ConfigFlag {
CONFIG_BYTE_SWAPPED = 0x01, ///< Story file is byte swapped - V3
CONFIG_TIME = 0x02, ///< Status line displays time - V3
CONFIG_TWODISKS = 0x04, ///< Story file occupied two disks - V3
CONFIG_TANDY = 0x08, ///< Tandy licensed game - V3
CONFIG_NOSTATUSLINE = 0x10, ///< Interpr can't support status lines - V3
CONFIG_SPLITSCREEN = 0x20, ///< Interpr supports split screen mode - V3
CONFIG_PROPORTIONAL = 0x40, ///< Interpr uses proportional font - V3
CONFIG_COLOUR = 0x01, ///< Interpr supports colour - V5+
CONFIG_PICTURES = 0x02, ///< Interpr supports pictures - V6
CONFIG_BOLDFACE = 0x04, ///< Interpr supports boldface style - V4+
CONFIG_EMPHASIS = 0x08, ///< Interpr supports emphasis style - V4+
CONFIG_FIXED = 0x10, ///< Interpr supports fixed width style - V4+
CONFIG_SOUND = 0x20, ///< Interpr supports sound - V6
CONFIG_TIMEDINPUT = 0x80, ///< Interpr supports timed input - V4+
SCRIPTING_FLAG = 0x0001, ///< Outputting to transscription file - V1+
FIXED_FONT_FLAG = 0x0002, ///< Use fixed width font - V3+
REFRESH_FLAG = 0x0004, ///< Refresh the screen - V6
GRAPHICS_FLAG = 0x0008, ///< Game wants to use graphics - V5+
OLD_SOUND_FLAG = 0x0010, ///< Game wants to use sound effects - V3
UNDO_FLAG = 0x0010, ///< Game wants to use UNDO feature - V5+
MOUSE_FLAG = 0x0020, ///< Game wants to use a mouse - V5+
COLOUR_FLAG = 0x0040, ///< Game wants to use colours - V5+
SOUND_FLAG = 0x0080, ///< Game wants to use sound effects - V5+
MENU_FLAG = 0x0100 ///< Game wants to use menus - V6
};
/**
* 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.
*/
enum ErrorReport {
ERR_REPORT_NEVER = 0,
ERR_REPORT_ONCE = 1,
ERR_REPORT_ALWAYS = 2,
ERR_REPORT_FATAL = 3,
ERR_DEFAULT_REPORT_MODE = ERR_REPORT_NEVER
};
/**
* Enumeration of the game header byte indexes
*/
enum HeaderByte {
H_VERSION = 0,
H_CONFIG = 1,
H_RELEASE = 2,
H_RESIDENT_SIZE = 4,
H_START_PC = 6,
H_DICTIONARY = 8,
H_OBJECTS = 10,
H_GLOBALS = 12,
H_DYNAMIC_SIZE = 14,
H_FLAGS = 16,
H_SERIAL = 18,
H_ABBREVIATIONS = 24,
H_FILE_SIZE = 26,
H_CHECKSUM = 28,
H_INTERPRETER_NUMBER = 30,
H_INTERPRETER_VERSION = 31,
H_SCREEN_ROWS = 32,
H_SCREEN_COLS = 33,
H_SCREEN_WIDTH = 34,
H_SCREEN_HEIGHT = 36,
H_FONT_HEIGHT = 38, ///< this is the font width in V5
H_FONT_WIDTH = 39, ///< this is the font height in V5
H_FUNCTIONS_OFFSET = 40,
H_STRINGS_OFFSET = 42,
H_DEFAULT_BACKGROUND = 44,
H_DEFAULT_FOREGROUND = 45,
H_TERMINATING_KEYS = 46,
H_LINE_WIDTH = 48,
H_STANDARD_HIGH = 50,
H_STANDARD_LOW = 51,
H_ALPHABET = 52,
H_EXTENSION_TABLE = 54,
H_USER_NAME = 56
};
/**
* Header extension fields
*/
enum {
HX_TABLE_SIZE = 0,
HX_MOUSE_X = 1,
HX_MOUSE_Y = 2,
HX_UNICODE_TABLE = 3,
HX_FLAGS = 4,
HX_FORE_COLOUR = 5,
HX_BACK_COLOUR = 6
};
/**
* User options
*/
struct UserOptions {
bool _attribute_assignment;
bool _attribute_testing;
bool _object_locating;
bool _object_movement;
bool _expand_abbreviations;
bool _ignore_errors;
bool _piracy;
bool _quetzal;
bool _sound;
bool _tandyBit;
int _left_margin;
int _right_margin;
int _undo_slots;
int _script_cols;
int _err_report_mode;
uint _defaultForeground;
uint _defaultBackground;
bool _color_enabled;
/**
* Constructor
*/
UserOptions();
/**
* Initializes the options
*/
void initialize(uint hVersion, uint storyId);
/**
* Returns true if the game being played is one of the original Infocom releases
*/
bool isInfocom() const;
};
/**
* Story file header data
*/
struct Header {
private:
struct StoryEntry {
Story _storyId;
zword _release;
char _serial[7];
};
static const StoryEntry RECORDS[26];
public:
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;
zword hx_flags;
zword hx_fore_colour;
zword hx_back_colour;
Story _storyId;
/**
* Constructor
*/
Header();
/**
* Load the header
*/
void loadHeader(Common::SeekableReadStream &f);
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,188 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/detection.h"
#include "glk/zcode/detection_tables.h"
#include "glk/zcode/quetzal.h"
#include "glk/blorb.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/md5.h"
#include "common/translation.h"
namespace Glk {
namespace ZCode {
void ZCodeMetaEngine::getSupportedGames(PlainGameList &games) {
for (const PlainGameDescriptor *pd = INFOCOM_GAME_LIST; pd->gameId; ++pd)
games.push_back(*pd);
for (const PlainGameDescriptor *pd = ZCODE_GAME_LIST; pd->gameId; ++pd)
games.push_back(*pd);
}
const GlkDetectionEntry* ZCodeMetaEngine::getDetectionEntries() {
static Common::Array<GlkDetectionEntry> entries;
for (const FrotzGameDescription *entry = FROTZ_GAMES; entry->_gameId; ++entry) {
GlkDetectionEntry detection = {
entry->_gameId,
entry->_extra,
entry->_md5,
entry->_filesize,
entry->_language,
Common::kPlatformUnknown
};
entries.push_back(detection);
}
entries.push_back({nullptr,
nullptr,
nullptr,
0,
Common::UNK_LANG,
Common::kPlatformUnknown});
return entries.data();
}
GameDescriptor ZCodeMetaEngine::findGame(const char *gameId) {
for (const PlainGameDescriptor *pd = INFOCOM_GAME_LIST; pd->gameId; ++pd) {
if (!strcmp(gameId, pd->gameId)) {
GameDescriptor gd(*pd);
gd._options |= OPTION_INFOCOM;
if (!strcmp(gameId, "questforexcalibur") ||
!strcmp(gameId, "journey") ||
!strcmp(gameId, "shogun") ||
!strcmp(gameId, "zork0"))
gd._supportLevel = kUnstableGame;
return gd;
}
}
for (const PlainGameDescriptor *pd = ZCODE_GAME_LIST; pd->gameId; ++pd) {
if (!strcmp(gameId, pd->gameId)) {
GameDescriptor gd = *pd;
/*
* Tested against ScummVM 2.8.0git, following entries are confirmed not to be playable
*/
if (!strcmp(gameId, "bureaucrocy_zcode") ||
!strcmp(gameId, "scopa") ||
!strcmp(gameId, "sunburst"))
gd._supportLevel = kUnstableGame;
return gd;
}
}
return GameDescriptor::empty();
}
bool ZCodeMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
const char *const EXTENSIONS[] = { ".z1", ".z2", ".z3", ".z4", ".z5", ".z6", ".z7", ".z8",
".dat", ".data", ".zip", nullptr };
// Loop through the files of the folder
for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
// Check for a recognised filename
if (file->isDirectory())
continue;
Common::String filename = file->getName();
bool hasExt = Blorb::hasBlorbExt(filename), isBlorb = false;
for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext)
hasExt = filename.hasSuffixIgnoreCase(*ext);
if (!hasExt)
continue;
// Open up the file and calculate the md5, and get the serial
Common::File gameFile;
if (!gameFile.open(*file))
continue;
Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
size_t filesize = gameFile.size();
char serial[9] = "";
bool emptyBlorb = false;
gameFile.seek(0);
isBlorb = Blorb::isBlorb(gameFile, ID_ZCOD);
if (!isBlorb) {
if (Blorb::hasBlorbExt(filename)) {
gameFile.close();
continue;
}
gameFile.seek(18);
Common::strcpy_s(&serial[0], sizeof(serial), "\"");
gameFile.read(&serial[1], 6);
Common::strcpy_s(&serial[7], sizeof(serial)-7, "\"");
} else {
Blorb b(*file, INTERPRETER_ZCODE);
Common::SeekableReadStream *f = b.createReadStreamForMember("game");
emptyBlorb = f == nullptr;
if (!emptyBlorb) {
f->seek(18);
Common::strcpy_s(&serial[0], sizeof(serial), "\"");
f->read(&serial[1], 6);
Common::strcpy_s(&serial[7], sizeof(serial) - 7, "\"");
delete f;
}
}
gameFile.close();
// Check for known games. Note that there has been some variation in exact filesizes
// for Infocom games due to padding at the end of files. So we match on md5s for the
// first 5Kb, and only worry about filesize for more recent Blorb based Zcode games
const FrotzGameDescription *p = FROTZ_GAMES;
while (p->_gameId && p->_md5 && (md5 != p->_md5 ||
(filesize != p->_filesize && isBlorb)))
++p;
if (!p->_gameId) {
// Generic .dat/.data/.zip files don't get reported as matches unless they have a known md5
if (filename.hasSuffixIgnoreCase(".dat") || filename.hasSuffixIgnoreCase(".data") || filename.hasSuffixIgnoreCase(".zip") || emptyBlorb)
continue;
const PlainGameDescriptor &desc = ZCODE_GAME_LIST[0];
gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
} else {
GameDescriptor gameDesc = findGame(p->_gameId);
DetectedGame gd = DetectedGame("glk", p->_gameId, gameDesc._description, p->_language, Common::kPlatformUnknown, p->_extra);
gd.setGUIOptions(p->_guiOptions);
gd.addExtraEntry("filename", filename);
gameList.push_back(gd);
}
}
return !gameList.empty();
}
void ZCodeMetaEngine::detectClashes(Common::StringMap &map) {
for (int idx = 0; idx < 2; ++idx) {
for (const PlainGameDescriptor *pd = (idx == 0) ? INFOCOM_GAME_LIST : ZCODE_GAME_LIST; pd->gameId; ++pd) {
if (map.contains(pd->gameId))
error("Duplicate game Id found - %s", pd->gameId);
map[pd->gameId] = "";
}
}
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,72 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_DETECTION
#define GLK_ZCODE_DETECTION
#include "common/fs.h"
#include "common/hash-str.h"
#include "engines/game.h"
#include "glk/streams.h"
#include "glk/detection.h"
namespace Glk {
namespace ZCode {
/**
* Game descriptor detection options
*/
enum DetectionOption {
OPTION_INFOCOM = 1
};
class ZCodeMetaEngine {
public:
/**
* Get a list of supported games
*/
static void getSupportedGames(PlainGameList &games);
/**
* Get the detection entries
*/
static const GlkDetectionEntry* getDetectionEntries();
/**
* Returns a game description for the given game Id, if it's supported
*/
static GameDescriptor findGame(const char *gameId);
/**
* Detect supported games
*/
static bool detectGames(const Common::FSList &fslist, DetectedGames &gameList);
/**
* Check for game Id clashes with other sub-engines
*/
static void detectClashes(Common::StringMap &map);
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_FROTZ_TYPES
#define GLK_ZCODE_FROTZ_TYPES
#include "glk/glk_types.h"
#include "common/algorithm.h"
namespace Glk {
namespace ZCode {
#define MAX_UNDO_SLOTS 500
#define STACK_SIZE 32768
#define lo(v) (v & 0xff)
#define hi(v) (v >> 8)
/**
* Character codes
*/
enum ZCodeKey {
ZC_TIME_OUT = 0x00,
ZC_NEW_STYLE = 0x01,
ZC_NEW_FONT = 0x02,
ZC_BACKSPACE = 0x08,
ZC_INDENT = 0x09,
ZC_GAP = 0x0b,
ZC_RETURN = 0x0d,
ZC_HKEY_MIN = 0x0e,
ZC_HKEY_RECORD = 0x0e,
ZC_HKEY_PLAYBACK = 0x0f,
ZC_HKEY_SEED = 0x10,
ZC_HKEY_UNDO = 0x11,
ZC_HKEY_RESTART = 0x12,
ZC_HKEY_QUIT = 0x13,
ZC_HKEY_DEBUG = 0x14,
ZC_HKEY_HELP = 0x15,
ZC_HKEY_MAX = 0x15,
ZC_ESCAPE = 0x1b,
ZC_ASCII_MIN = 0x20,
ZC_ASCII_MAX = 0x7e,
ZC_BAD = 0x7f,
ZC_ARROW_MIN = 0x81,
ZC_ARROW_UP = 0x81,
ZC_ARROW_DOWN = 0x82,
ZC_ARROW_LEFT = 0x83,
ZC_ARROW_RIGHT = 0x84,
ZC_ARROW_MAX = 0x84,
ZC_FKEY_MIN = 0x85,
ZC_FKEY_MAX = 0x90,
ZC_NUMPAD_MIN = 0x91,
ZC_NUMPAD_MAX = 0x9a,
ZC_SINGLE_CLICK = 0x9b,
ZC_DOUBLE_CLICK = 0x9c,
ZC_MENU_CLICK = 0x9d,
ZC_LATIN1_MIN = 0xa0,
ZC_LATIN1_MAX = 0xff
};
enum Story {
BEYOND_ZORK,
SHERLOCK,
ZORK_ZERO,
SHOGUN,
ARTHUR,
JOURNEY,
LURKING_HORROR,
MILLIWAYS,
UNKNOWN
};
enum Version {
V1 = 1,
V2 = 2,
V3 = 3,
V4 = 4,
V5 = 5,
V6 = 6,
V7 = 7,
V8 = 8,
V9 = 9
};
enum {
TRANSPARENT_FLAG = 0x0001 ///< Game wants to use transparency - V6
};
enum ErrorCode {
ERR_TEXT_BUF_OVF = 1, ///< Text buffer overflow
ERR_STORE_RANGE = 2, ///< Store out of dynamic memory
ERR_DIV_ZERO = 3, ///< Division by zero
ERR_ILL_OBJ = 4, ///< Illegal object
ERR_ILL_ATTR = 5, ///< Illegal attribute
ERR_NO_PROP = 6, ///< No such property
ERR_STK_OVF = 7, ///< Stack overflow
ERR_ILL_CALL_ADDR = 8, ///< Call to illegal address
ERR_CALL_NON_RTN = 9, ///< Call to non-routine
ERR_STK_UNDF = 10, ///< Stack underflow
ERR_ILL_OPCODE = 11, ///< Illegal opcode
ERR_BAD_FRAME = 12, ///< Bad stack frame
ERR_ILL_JUMP_ADDR = 13, ///< Jump to illegal address
ERR_SAVE_IN_INTER = 14, ///< Can't save while in interrupt
ERR_STR3_NESTING = 15, ///< Nesting stream #3 too deep
ERR_ILL_WIN = 16, ///< Illegal window
ERR_ILL_WIN_PROP = 17, ///< Illegal window property
ERR_ILL_PRINT_ADDR = 18, ///< Print at illegal address
ERR_DICT_LEN = 19, ///< Illegal dictionary word length
ERR_MAX_FATAL = 19,
// Less serious errors
ERR_JIN_0 = 20, ///< @jin called with object 0
ERR_GET_CHILD_0 = 21, ///< @get_child called with object 0
ERR_GET_PARENT_0 = 22, ///< @get_parent called with object 0
ERR_GET_SIBLING_0 = 23, ///< @get_sibling called with object 0
ERR_GET_PROP_ADDR_0 = 24, ///< @get_prop_addr called with object 0
ERR_GET_PROP_0 = 25, ///< @get_prop called with object 0
ERR_PUT_PROP_0 = 26, ///< @put_prop called with object 0
ERR_CLEAR_ATTR_0 = 27, ///< @clear_attr called with object 0
ERR_SET_ATTR_0 = 28, ///< @set_attr called with object 0
ERR_TEST_ATTR_0 = 29, ///< @test_attr called with object 0
ERR_MOVE_OBJECT_0 = 30, ///< @move_object called moving object 0
ERR_MOVE_OBJECT_TO_0 = 31, ///< @move_object called moving into object 0
ERR_REMOVE_OBJECT_0 = 32, ///< @remove_object called with object 0
ERR_GET_NEXT_PROP_0 = 33, ///< @get_next_prop called with object 0
ERR_NUM_ERRORS = 33
};
enum FrotzInterp {
INTERP_DEFAULT = 0,
INTERP_DEC_20 = 1,
INTERP_APPLE_IIE = 2,
INTERP_MACINTOSH = 3,
INTERP_AMIGA = 4,
INTERP_ATARI_ST = 5,
INTERP_MSDOS = 6,
INTERP_CBM_128 = 7,
INTERP_CBM_64 = 8,
INTERP_APPLE_IIC = 9,
INTERP_APPLE_IIGS = 10,
INTERP_TANDY = 11
};
enum Colour {
BLACK_COLOUR = 2,
RED_COLOUR = 3,
GREEN_COLOUR = 4,
YELLOW_COLOUR = 5,
BLUE_COLOUR = 6,
MAGENTA_COLOUR = 7,
CYAN_COLOUR = 8,
WHITE_COLOUR = 9,
GREY_COLOUR = 10, ///< INTERP_MSDOS only
LIGHTGREY_COLOUR = 10, ///< INTERP_AMIGA only
MEDIUMGREY_COLOUR = 11, ///< INTERP_AMIGA only
DARKGREY_COLOUR = 12, ///< INTERP_AMIGA only
TRANSPARENT_COLOUR = 15 ///< ZSpec 1.1
};
enum Style {
REVERSE_STYLE = 1,
BOLDFACE_STYLE = 2,
EMPHASIS_STYLE = 4,
FIXED_WIDTH_STYLE = 8
};
enum FontStyle {
PREVIOUS_FONT = 0,
TEXT_FONT = 1,
PICTURE_FONT = 2,
GRAPHICS_FONT = 3,
FIXED_WIDTH_FONT = 4
};
/*** Constants for os_beep */
#define BEEP_HIGH 1
#define BEEP_LOW 2
/*** Constants for os_menu */
#define MENU_NEW 0
#define MENU_ADD 1
#define MENU_REMOVE 2
typedef byte zbyte;
typedef uint32 zchar;
typedef uint16 zword;
#define MAX_NESTING 16
struct Redirect {
zword _xSize;
zword _table;
zword _width;
zword _total;
Redirect() : _xSize(0), _table(0), _width(0), _total(0) {}
Redirect(zword xSize, zword table, zword width = 0, zword total = 0) :
_xSize(xSize), _table(table), _width(width), _total(total) {}
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,686 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/glk_interface.h"
#include "glk/zcode/pics.h"
#include "glk/zcode/sound_folder.h"
#include "glk/conf.h"
#include "glk/screen.h"
#include "common/config-manager.h"
#include "common/compression/unzip.h"
namespace Glk {
namespace ZCode {
GlkInterface::GlkInterface(OSystem *syst, const GlkGameDescription &gameDesc) :
GlkAPI(syst, gameDesc), _pics(nullptr), curr_status_ht(0), mach_status_ht(0), gos_status(nullptr),
gos_linepending(0), gos_linebuf(nullptr), gos_linewin(nullptr), gos_channel(nullptr), mwin(0),
mouse_x(0), mouse_y(0), fixforced(0), menu_selected(0), enable_wrapping(false), enable_scripting(false),
enable_scrolling(false), enable_buffering(false), next_sample(0), next_volume(0), _soundLocked(false),
_soundPlaying(false), _reverseVideo(false) {
Common::fill(&statusline[0], &statusline[256], '\0');
Common::fill(&zcolors[0], &zcolors[zcolor_NUMCOLORS], 0);
}
GlkInterface::~GlkInterface() {
delete _pics;
}
void GlkInterface::initialize() {
/* Setup options */
UserOptions::initialize(h_version, _storyId);
/* Setup colors array */
const int COLOR_MAP[zcolor_NUMCOLORS - 2] = {
0x0000, ///< 2 = black
0x001D, ///< 3 = red
0x0340, ///< 4 = green
0x03BD, ///< 5 = yellow
0x59A0, ///< 6 = blue
0x7C1F, ///< 7 = magenta
0x77A0, ///< 8 = cyan
0x7FFF, ///< 9 = white
0x5AD6, ///< 10 = light grey
0x4631, ///< 11 = medium grey
0x2D6B, ///< 12 = dark grey
};
zcolors[0] = zcolor_Current; // Current
zcolors[1] = zcolor_Default; // Default
for (int i = 2; i < zcolor_NUMCOLORS; ++i)
zcolors[i] = zRGB(COLOR_MAP[i - 2]);
/*
* Init glk stuff
*/
// monor
glk_stylehint_set(wintype_AllTypes, style_Preformatted, stylehint_Proportional, 0);
glk_stylehint_set(wintype_AllTypes, style_Preformatted, stylehint_Weight, 0);
glk_stylehint_set(wintype_AllTypes, style_Preformatted, stylehint_Oblique, 0);
// monob
glk_stylehint_set(wintype_AllTypes, style_Subheader, stylehint_Proportional, 0);
glk_stylehint_set(wintype_AllTypes, style_Subheader, stylehint_Weight, 1);
glk_stylehint_set(wintype_AllTypes, style_Subheader, stylehint_Oblique, 0);
// monoi
glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Proportional, 0);
glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Weight, 0);
glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Oblique, 1);
// monoz
glk_stylehint_set(wintype_AllTypes, style_BlockQuote, stylehint_Proportional, 0);
glk_stylehint_set(wintype_AllTypes, style_BlockQuote, stylehint_Weight, 1);
glk_stylehint_set(wintype_AllTypes, style_BlockQuote, stylehint_Oblique, 1);
// propr
glk_stylehint_set(wintype_TextBuffer, style_Normal, stylehint_Proportional, 1);
glk_stylehint_set(wintype_TextGrid, style_Normal, stylehint_Proportional, 0);
glk_stylehint_set(wintype_AllTypes, style_Normal, stylehint_Weight, 0);
glk_stylehint_set(wintype_AllTypes, style_Normal, stylehint_Oblique, 0);
// propb
glk_stylehint_set(wintype_TextBuffer, style_Header, stylehint_Proportional, 1);
glk_stylehint_set(wintype_TextGrid, style_Header, stylehint_Proportional, 0);
glk_stylehint_set(wintype_AllTypes, style_Header, stylehint_Weight, 1);
glk_stylehint_set(wintype_AllTypes, style_Header, stylehint_Oblique, 0);
// propi
glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Proportional, 1);
glk_stylehint_set(wintype_TextGrid, style_Emphasized, stylehint_Proportional, 0);
glk_stylehint_set(wintype_AllTypes, style_Emphasized, stylehint_Weight, 0);
glk_stylehint_set(wintype_AllTypes, style_Emphasized, stylehint_Oblique, 1);
// propi
glk_stylehint_set(wintype_TextBuffer, style_Note, stylehint_Proportional, 1);
glk_stylehint_set(wintype_TextGrid, style_Note, stylehint_Proportional, 0);
glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Weight, 1);
glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Oblique, 1);
/*
* Get the screen size
*/
gos_channel = nullptr;
h_screen_width = g_system->getWidth();
h_screen_height = g_system->getHeight();
h_font_width = g_conf->_monoInfo._cellW;
h_font_height = g_conf->_monoInfo._cellH;
h_screen_cols = h_screen_width / h_font_width;
h_screen_rows = h_screen_height / h_font_height;
// Must be after screen dimensions are computed
if (g_conf->_graphics) {
if (_blorb)
// Blorb file containers allow graphics
h_flags |= GRAPHICS_FLAG;
else if ((h_version == V6 || _storyId == BEYOND_ZORK) && initPictures())
// Earlier Infocom game with picture files
h_flags |= GRAPHICS_FLAG;
}
// Use the ms-dos interpreter number for v6, because that's the
// kind of graphics files we understand
h_interpreter_number = h_version == 6 ? INTERP_MSDOS : INTERP_AMIGA;
h_interpreter_version = 'F';
// Set these per spec 8.3.2.
h_default_foreground = WHITE_COLOUR;
h_default_background = BLACK_COLOUR;
// Set up the foreground & background
_color_enabled = ((h_version >= 5) && (h_flags & COLOUR_FLAG))
|| (_defaultForeground != zcolor_Transparent) || (_defaultBackground != zcolor_Transparent);
if (_color_enabled) {
h_config |= CONFIG_COLOUR;
h_flags |= COLOUR_FLAG; // FIXME: beyond zork handling?
if (h_version == 6) {
h_default_foreground = BLACK_COLOUR;
h_default_background = WHITE_COLOUR;
}
zcolors[h_default_foreground] = _defaultForeground;
zcolors[h_default_background] = _defaultBackground;
} else {
if (h_flags & COLOUR_FLAG)
h_flags &= ~COLOUR_FLAG;
}
/*
* Open the windows
*/
if (_storyId == BEYOND_ZORK)
showBeyondZorkTitle();
_wp.setup(h_version == 6);
for (uint i = 0; i < _wp.size(); ++i) {
_wp[i][TRUE_FG_COLOR] = zcolors[h_default_foreground];
_wp[i][TRUE_BG_COLOR] = zcolors[h_default_background];
}
/*
* Icky magic bit setting
*/
if (h_version == V3 && _tandyBit)
h_config |= CONFIG_TANDY;
if (h_version == V3 && _wp._upper)
h_config |= CONFIG_SPLITSCREEN;
if (h_version == V3 && !_wp._upper)
h_config |= CONFIG_NOSTATUSLINE;
if (h_version >= V4)
h_config |= CONFIG_BOLDFACE | CONFIG_EMPHASIS |
CONFIG_FIXED | CONFIG_TIMEDINPUT | CONFIG_COLOUR;
if (h_version >= V5)
h_flags &= ~(GRAPHICS_FLAG | MOUSE_FLAG | MENU_FLAG);
if ((h_version >= 5) && (h_flags & SOUND_FLAG))
h_flags |= SOUND_FLAG;
if ((h_version == 3) && (h_flags & OLD_SOUND_FLAG))
h_flags |= OLD_SOUND_FLAG;
if ((h_version == 6) && (_sound != 0))
h_config |= CONFIG_SOUND;
if (h_version >= V5 && (h_flags & UNDO_FLAG))
if (_undo_slots == 0)
h_flags &= ~UNDO_FLAG;
/*
* Miscellaneous
*/
// Add any sound folder or zip
addSound();
// For Beyond Zork the Page Up/Down keys are remapped to scroll the description area,
// since the arrow keys the original used are in use now for cycling prior commands
if (_storyId == BEYOND_ZORK) {
uint32 KEYCODES[2] = { keycode_PageUp, keycode_PageDown };
glk_set_terminators_line_event(_wp._lower, KEYCODES, 2);
}
}
void GlkInterface::addSound() {
Common::FSNode gameDir(ConfMan.getPath("path"));
SoundSubfolder::check(gameDir);
SoundZip::check(gameDir, _storyId);
}
bool GlkInterface::initPictures() {
if (Pics::exists()) {
_pics = new Pics();
SearchMan.add("Pics", _pics, 99, false);
return true;
}
if (h_version == V6 && _storyId != MILLIWAYS)
error("Could not locate MG1 file");
return false;
}
int GlkInterface::os_char_width(zchar z) {
return g_conf->_monoInfo._cellW;
}
int GlkInterface::os_string_width(const zchar *s) {
int width = 0;
zchar c;
while ((c = *s++) != 0)
if (c == ZC_NEW_STYLE || c == ZC_NEW_FONT)
s++;
else
width += os_char_width(c);
return width;
}
int GlkInterface::os_string_length(zchar *s) {
int length = 0;
while (*s++) length++;
return length;
}
void GlkInterface::os_prepare_sample(int a) {
glk_sound_load_hint(a, 1);
}
void GlkInterface::os_finish_with_sample(int a) {
glk_sound_load_hint(a, 0);
}
void GlkInterface::os_start_sample(int number, int volume, int repeats, zword eos) {
int vol;
if (!gos_channel) {
gos_channel = glk_schannel_create(0);
if (!gos_channel)
return;
}
switch (volume) {
case 1: vol = 0x02000; break;
case 2: vol = 0x04000; break;
case 3: vol = 0x06000; break;
case 4: vol = 0x08000; break;
case 5: vol = 0x0a000; break;
case 6: vol = 0x0c000; break;
case 7: vol = 0x0e000; break;
case 8: vol = 0x10000; break;
default: vol = 0x20000; break;
}
glk_schannel_play_ext(gos_channel, number, repeats, eos);
glk_schannel_set_volume(gos_channel, vol);
}
void GlkInterface::os_stop_sample(int a) {
if (!gos_channel)
return;
glk_schannel_stop(gos_channel);
}
void GlkInterface::os_beep(int volume) {
beep();
}
bool GlkInterface::os_picture_data(int picture, uint *height, uint *width) {
if (_pics && picture == 0) {
*width = _pics->version();
*height = _pics->size();
return true;
} else {
uint fullWidth, fullHeight;
bool result = glk_image_get_info(picture, &fullWidth, &fullHeight);
*width = fullWidth;
*height = fullHeight;
return result;
}
}
void GlkInterface::start_sample(int number, int volume, int repeats, zword eos) {
static zbyte LURKING_REPEATS[] = {
0x00, 0x00, 0x00, 0x01, 0xff,
0x00, 0x01, 0x01, 0x01, 0x01,
0xff, 0x01, 0x01, 0xff, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff
};
if (_storyId == LURKING_HORROR)
repeats = LURKING_REPEATS[number];
os_start_sample(number, volume, repeats, eos);
_soundPlaying = true;
}
void GlkInterface::start_next_sample() {
if (next_sample != 0)
start_sample(next_sample, next_volume, 0, 0);
next_sample = 0;
next_volume = 0;
}
void GlkInterface::gos_update_width() {
uint width;
if (_wp._upper) {
glk_window_get_size(_wp._upper, &width, nullptr);
h_screen_cols = width;
SET_BYTE(H_SCREEN_COLS, width);
uint curx = _wp._upper[X_CURSOR];
if (curx > width)
_wp._upper.setCursor(Point(1, _wp._upper[Y_CURSOR]));
}
}
void GlkInterface::gos_update_height() {
uint height_upper;
uint height_lower;
if (_wp.currWin()) {
glk_window_get_size(_wp._upper, nullptr, &height_upper);
glk_window_get_size(_wp._lower, nullptr, &height_lower);
h_screen_rows = height_upper + height_lower + 1;
SET_BYTE(H_SCREEN_ROWS, h_screen_rows);
}
}
void GlkInterface::reset_status_ht() {
uint height;
if (_wp._upper && h_version != 6) {
glk_window_get_size(_wp._upper, nullptr, &height);
if ((uint)mach_status_ht != height) {
glk_window_set_arrangement(glk_window_get_parent(_wp._upper),
winmethod_Above | winmethod_Fixed, mach_status_ht, nullptr);
}
}
}
void GlkInterface::erase_window(zword w) {
if (w == 0)
_wp._lower.clear();
else if (_wp._upper) {
//os_set_reverse_video(glk_window_get_stream(_wp._upper), true);
memset(statusline, ' ', sizeof statusline);
_wp._upper.clear();
reset_status_ht();
curr_status_ht = 0;
}
}
void GlkInterface::split_window(zword lines) {
if (!_wp._upper)
return;
// The top line is always set for V1 to V3 games
if (h_version < V4)
lines++;
if ((!lines || lines > curr_status_ht) && h_version != 6) {
uint height;
glk_window_get_size(_wp._upper, nullptr, &height);
if (lines != height)
glk_window_set_arrangement(glk_window_get_parent(_wp._upper),
winmethod_Above | winmethod_Fixed, lines, nullptr);
curr_status_ht = lines;
}
mach_status_ht = lines;
int curY = _wp._upper[Y_CURSOR];
if (curY > lines)
_wp._upper.setCursor(Point(1, 1));
gos_update_width();
if (h_version == V3)
_wp._upper.clear();
if (h_version == V6) {
_wp._upper.clear();
_wp._lower.clear();
_wp._background->fillRect(_defaultBackground, Rect(g_system->getWidth(), g_system->getHeight()));
}
}
void GlkInterface::restart_screen() {
erase_window(0);
erase_window(1);
split_window(0);
}
void GlkInterface::packspaces(zchar *src, zchar *dst) {
int killing = 0;
while (*src) {
if (*src == 0x20202020)
*src = ' ';
if (*src == ' ')
killing++;
else
killing = 0;
if (killing > 2)
src++;
else
*dst++ = *src++;
}
*dst = 0;
}
void GlkInterface::smartstatusline() {
zchar packed[256];
uint32 buf[256];
zchar *a, *b, *c, *d;
int roomlen, scorelen, scoreofs;
int len, tmp;
packspaces(statusline, packed);
len = os_string_length(packed);
a = packed;
while (a[0] == ' ')
a++;
b = a;
while (b[0] != 0 && !(b[0] == ' ' && b[1] == ' '))
b++;
c = b;
while (c[0] == ' ')
c++;
d = packed + len - 1;
while (d[0] == ' ' && d > c)
d--;
if (d[0] != ' ' && d[0] != 0)
d++;
if (d < c)
d = c;
roomlen = b - a;
scorelen = d - c;
scoreofs = h_screen_cols - scorelen - 2;
if (scoreofs <= roomlen)
scoreofs = roomlen + 2;
for (tmp = 0; tmp < h_screen_cols; tmp++)
buf[tmp] = ' ';
memcpy(buf + 1 + scoreofs, c, scorelen * sizeof(zchar));
memcpy(buf + 1, a, roomlen * sizeof(zchar));
Point cursPos(_wp._upper[X_CURSOR], _wp._upper[Y_CURSOR]);
_wp._upper.setCursor(Point(1, 1));
glk_put_buffer_uni(buf, h_screen_cols);
_wp._upper.setCursor(cursPos);
}
void GlkInterface::gos_cancel_pending_line() {
event_t ev;
glk_cancel_line_event(gos_linewin, &ev);
gos_linebuf[ev.val1] = '\0';
gos_linepending = 0;
}
void GlkInterface::showBeyondZorkTitle() {
int saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
if (saveSlot == -1) {
winid_t win = glk_window_open(nullptr, 0, 0, wintype_Graphics, 0);
if (glk_image_draw_scaled(win, 1, 0, 0, g_vm->_screen->w, g_vm->_screen->h))
_events->waitForPress();
glk_window_close(win, nullptr);
}
}
void GlkInterface::os_draw_picture(int picture, const Common::Point &pos) {
if (pos.x && pos.y) {
_wp._background->bringToFront();
Point pt(pos.x - 1, pos.y - 1);
if (h_version < V5) {
pt.x *= g_conf->_monoInfo._cellW;
pt.y *= g_conf->_monoInfo._cellH;
}
glk_image_draw(_wp._background, picture, pt.x, pt.y);
} else {
// Picture embedded within the lower text area
_wp.currWin().imageDraw(picture, imagealign_MarginLeft, 0);
}
}
int GlkInterface::os_peek_color() {
if (_color_enabled) {
return _defaultBackground;
} else {
return (_reverseVideo) ? h_default_foreground : h_default_background;
}
/*
if (u_setup.color_enabled) { */
#ifdef COLOR_SUPPORT
short fg, bg;
pair_content(PAIR_NUMBER(inch() & A_COLOR), &fg, &bg);
switch(bg) {
case COLOR_BLACK: return BLACK_COLOUR;
case COLOR_RED: return RED_COLOUR;
case COLOR_GREEN: return GREEN_COLOUR;
case COLOR_YELLOW: return YELLOW_COLOUR;
case COLOR_BLUE: return BLUE_COLOUR;
case COLOR_MAGENTA: return MAGENTA_COLOUR;
case COLOR_CYAN: return CYAN_COLOUR;
case COLOR_WHITE: return WHITE_COLOUR;
}
return 0;
#endif /* COLOR_SUPPORT */
}
zchar GlkInterface::os_read_key(int timeout, bool show_cursor) {
Window &win = _wp.currWin() ? _wp.currWin() : _wp._lower;
uint key;
if (win) {
// Get a keypress from a window
if (gos_linepending)
gos_cancel_pending_line();
glk_request_char_event_uni(win);
if (timeout != 0)
glk_request_timer_events(timeout * 100);
event_t ev;
while (!shouldQuit()) {
glk_select(&ev);
if (ev.type == evtype_Arrange) {
gos_update_height();
gos_update_width();
} else if (ev.type == evtype_Timer) {
glk_cancel_char_event(win);
glk_request_timer_events(0);
return ZC_TIME_OUT;
} else if (ev.type == evtype_CharInput)
break;
}
if (shouldQuit())
return 0;
glk_request_timer_events(0);
if (_wp._upper && mach_status_ht < curr_status_ht)
reset_status_ht();
curr_status_ht = 0;
key = ev.val1;
} else {
// No active window, so get a raw keypress
key = _events->getKeypress();
}
switch (key) {
case keycode_Escape: return ZC_ESCAPE;
case keycode_PageUp: return ZC_ARROW_MIN;
case keycode_PageDown: return ZC_ARROW_MAX;
case keycode_Left: return ZC_ARROW_LEFT;
case keycode_Right: return ZC_ARROW_RIGHT;
case keycode_Up: return ZC_ARROW_UP;
case keycode_Down: return ZC_ARROW_DOWN;
case keycode_Return: return ZC_RETURN;
case keycode_Delete: return ZC_BACKSPACE;
case keycode_Tab: return ZC_INDENT;
default:
return key;
}
}
zchar GlkInterface::os_read_line(int max, zchar *buf, int timeout, int width, int continued) {
event_t ev;
winid_t win = _wp.currWin() ? _wp.currWin() : _wp._lower;
if (!continued && gos_linepending)
gos_cancel_pending_line();
if (!continued || !gos_linepending) {
glk_request_line_event_uni(win, buf, max, os_string_length(buf));
if (timeout != 0)
glk_request_timer_events(timeout * 100);
}
gos_linepending = 0;
while (!shouldQuit()) {
glk_select(&ev);
if (ev.type == evtype_Arrange) {
gos_update_height();
gos_update_width();
} else if (ev.type == evtype_Timer) {
gos_linewin = win;
gos_linepending = 1;
gos_linebuf = buf;
return ZC_TIME_OUT;
} else if (ev.type == evtype_LineInput) {
break;
}
}
if (shouldQuit())
return 0;
glk_request_timer_events(0);
buf[ev.val1] = '\0';
// If the upper status line area was expanded to show a text box/quotation, restore it back
if (_wp._upper && mach_status_ht < curr_status_ht)
reset_status_ht();
curr_status_ht = 0;
if (ev.val2) {
// Line terminator specified, so return it
if (_storyId == BEYOND_ZORK && ev.val2 == keycode_PageUp)
return ZC_ARROW_UP;
else if (_storyId == BEYOND_ZORK && ev.val2 == keycode_PageDown)
return ZC_ARROW_DOWN;
return ev.val2;
}
return ZC_RETURN;
}
uint GlkInterface::roundDiv(uint x, uint y) {
uint quotient = x / y;
uint dblremain = (x % y) << 1;
if ((dblremain > y) || ((dblremain == y) && (quotient & 1)))
quotient++;
return quotient;
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,259 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_GLK_INTERFACE
#define GLK_ZCODE_GLK_INTERFACE
#include "glk/glk_api.h"
#include "glk/zcode/mem.h"
#include "glk/zcode/windows.h"
namespace Glk {
namespace ZCode {
#define zB(i) ((((i >> 10) & 0x1F) << 3) | (((i >> 10) & 0x1F) >> 2))
#define zG(i) ((((i >> 5) & 0x1F) << 3) | (((i >> 5) & 0x1F) >> 2))
#define zR(i) ((((i ) & 0x1F) << 3) | (((i ) & 0x1F) >> 2))
#define zRGB(i) _screen->format.RGBToColor(zR(i), zG(i), zB(i))
#define zcolor_NUMCOLORS (13)
enum SoundEffect {
EFFECT_PREPARE = 1,
EFFECT_PLAY = 2,
EFFECT_STOP = 3,
EFFECT_FINISH_WITH = 4
};
enum RestartAction {
RESTART_BEGIN = 0,
RESTART_WPROP_SET = 1,
RESTART_END = 2
};
class Pics;
/**
* Implements an intermediate interface on top of the GLK layer, providing screen
* and sound effect handling
*/
class GlkInterface : public GlkAPI, public virtual UserOptions, public virtual Mem {
private:
bool _reverseVideo;
public:
Pics *_pics;
zchar statusline[256];
uint zcolors[zcolor_NUMCOLORS];
int fixforced;
int curr_status_ht;
int mach_status_ht;
Windows _wp;
winid_t gos_status;
int gos_linepending;
zchar *gos_linebuf;
winid_t gos_linewin;
schanid_t gos_channel;
// Mouse data
int mwin;
int mouse_y;
int mouse_x;
int menu_selected;
// Window attributes
bool enable_wrapping;
bool enable_scripting;
bool enable_scrolling;
bool enable_buffering;
// Sound fields
int next_sample;
int next_volume;
bool _soundLocked;
bool _soundPlaying;
private:
/**
* Loads the pictures file for Infocom V6 games
*/
bool initPictures();
/**
* Displays the title screen for the game Beyond Zork
*/
void showBeyondZorkTitle();
/**
* Add any Sound subfolder or sound zip file for access
*/
void addSound();
/**
* Do a rounding division, rounding to even if fraction part is 1/2.
*/
uint roundDiv(uint x, uint y);
protected:
/**
* Return the length of the character in screen units.
*/
int os_char_width(zchar z);
/**
* Calculate the length of a word in screen units. Apart from letters,
* the word may contain special codes:
*
* ZC_NEW_STYLE - next character is a new text style
* ZC_NEW_FONT - next character is a new font
*/
int os_string_width(const zchar *s);
/**
* Return the length of a string
*/
int os_string_length(zchar *s);
/**
* Prepare a sample for playing
*/
void os_prepare_sample(int a);
/**
* Signal that a given sample is finished with
*/
void os_finish_with_sample(int a);
/**
* Play the given sample at the given volume (ranging from 1 to 8 and
* 255 meaning a default volume). The sound is played once or several
* times in the background (255 meaning forever). In Z-code 3 the
* repeats value is always 0 and the number of repeats is taken from
* the sound file itself. The end_of_sound function is called as soon
* as the sound finishes.
*/
void os_start_sample(int number, int volume, int repeats, zword eos);
/**
* Stop playing a given sound number
*/
void os_stop_sample(int a);
/**
* Make a beep sound
*/
void os_beep(int volume);
/**
* Return true if the given picture is available. If so, write the
* width and height of the picture into the appropriate variables.
* Only when picture 0 is asked for, write the number of available
* pictures and the release number instead.
*/
bool os_picture_data(int picture, uint *height, uint *width);
/**
* Display a picture at the given coordinates. Top left is (1,1).
*/
void os_draw_picture(int picture, const Common::Point &pos);
/**
* Return the colour of the pixel below the cursor. This is used by V6 games to print
* text on top of pictures. The coulor need not be in the standard set of Z-machine colours.
*/
int os_peek_color();
/**
* Call the IO interface to play a sample.
*/
void start_sample(int number, int volume, int repeats, zword eos);
void start_next_sample();
void gos_update_width();
void gos_update_height();
void reset_status_ht();
void erase_window(zword w);
void split_window(zword lines);
void restart_screen();
/**
* statusline overflowed the window size ... bad game!
* so ... split status text into regions, reformat and print anew.
*/
void packspaces(zchar *src, zchar *dst);
void smartstatusline();
/**
* Cancels any pending line
*/
void gos_cancel_pending_line();
/**
* Called during game restarts
*/
void os_restart_game(RestartAction stage) {}
/**
* Reads the mouse buttons
*/
zword os_read_mouse() {
// Not implemented
return 0;
}
void os_scrollback_char(zchar z) {
// Not implemented
}
void os_scrollback_erase(int amount) {
// Not implemented
}
/**
* Waits for a keypress
*/
zchar os_read_key(int timeout, bool show_cursor);
/**
* Waits for the user to type an input line
*/
zchar os_read_line(int max, zchar *buf, int timeout, int width, int continued);
public:
/**
* Constructor
*/
GlkInterface(OSystem *syst, const GlkGameDescription &gameDesc);
/**
* Destructor
*/
~GlkInterface() override;
/**
* Initialization
*/
void initialize();
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,42 @@
#define infocom6x8_width 192
#define infocom6x8_height 24
static unsigned char infocom6x8_bits[] = {
0x40,0x50,0x28,0x44,0x22,0x08,0x44,0x90,0x00,0x00,0x00,0x20,0x86,0x60,0x18,
0xc5,0xe3,0x3c,0x86,0x01,0x00,0x00,0x00,0x38,0x40,0x50,0x7c,0x1f,0x52,0x08,
0x82,0x60,0x10,0x00,0x00,0x20,0xc9,0x90,0x24,0x45,0x10,0x20,0x49,0x02,0x00,
0x04,0x10,0x44,0x40,0x00,0x28,0x05,0x21,0x04,0x01,0xf1,0x10,0x00,0x00,0x10,
0x89,0x40,0x10,0xcf,0x71,0x10,0x46,0x12,0x08,0xc2,0x23,0x20,0x40,0x00,0x7c,
0x9f,0x50,0x01,0x01,0x61,0x7c,0xc0,0x03,0x08,0x89,0x20,0x20,0x04,0x92,0x08,
0x89,0x03,0x00,0x01,0x40,0x10,0x00,0x00,0x28,0x54,0x90,0x00,0x01,0x91,0x10,
0x02,0x00,0x04,0x89,0x10,0x24,0x04,0x92,0x08,0x09,0x12,0x08,0xc2,0x23,0x00,
0x40,0x00,0x00,0x5f,0x62,0x01,0x82,0x00,0x10,0x02,0x10,0x04,0xc6,0xf1,0x18,
0xc4,0x61,0x08,0xc6,0x01,0x08,0x04,0x10,0x10,0x00,0x00,0x00,0x04,0x00,0x00,
0x44,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x86,0x71,0x38,
0xc7,0xf3,0x38,0xc9,0x81,0x24,0x41,0x94,0x18,0x87,0x71,0x38,0x5f,0x12,0x45,
0x51,0xf4,0x0c,0xc1,0x40,0x00,0x49,0x92,0x04,0x49,0x10,0x04,0x89,0x80,0x14,
0xc1,0xb6,0x24,0x49,0x92,0x04,0x44,0x12,0x45,0x4a,0x84,0x04,0x81,0xa0,0x00,
0x4d,0x72,0x04,0xc9,0x71,0x04,0x89,0x80,0x0c,0x41,0xd5,0x24,0x49,0x92,0x18,
0x44,0x12,0x45,0x84,0x42,0x04,0x82,0x10,0x01,0xcd,0x93,0x04,0x49,0x10,0x34,
0x8f,0x80,0x14,0x41,0x94,0x24,0x47,0x72,0x20,0x44,0xa2,0x54,0x04,0x21,0x04,
0x84,0x00,0x00,0x41,0x92,0x04,0x49,0x10,0x24,0x89,0x90,0x24,0x41,0x94,0x24,
0x41,0x33,0x24,0x44,0xa2,0x6c,0x0a,0x11,0x04,0x88,0x00,0x00,0x4e,0x72,0x38,
0xc7,0x13,0x38,0xc9,0x61,0x24,0x4f,0x94,0x18,0x81,0xd3,0x18,0x84,0x41,0x44,
0x11,0xf1,0x0c,0xc8,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x04,0x60,0x00,
0x41,0x20,0x04,0x01,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x10,
0x41,0x00,0x00,0xc1,0x71,0x1c,0xc7,0x21,0x1c,0x07,0x00,0x14,0xc1,0x77,0x1c,
0xc7,0x71,0x1c,0x47,0x51,0x54,0x45,0x71,0x08,0x81,0x00,0x00,0x02,0x51,0x04,
0x45,0x71,0x14,0x45,0x20,0x14,0x41,0x55,0x14,0x45,0x11,0x04,0x42,0x51,0x54,
0x45,0x41,0x08,0x81,0x20,0x00,0xc0,0x51,0x04,0xc5,0x21,0x14,0x45,0x20,0x0c,
0x41,0x55,0x14,0x45,0x11,0x1c,0x42,0x51,0x54,0x42,0x21,0x04,0x00,0x51,0x01,
0x40,0x51,0x04,0x45,0x20,0x14,0x45,0x20,0x14,0x41,0x55,0x14,0x45,0x11,0x10,
0x42,0x51,0x54,0x45,0x11,0x08,0x81,0x80,0x00,0xc0,0x71,0x1c,0xc7,0x21,0x1c,
0x45,0x20,0x14,0x41,0x55,0x1c,0xc7,0x11,0x1c,0xc2,0x21,0x7c,0xc5,0x71,0x08,
0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x20,0x00,0x00,0x00,0x00,
0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x41,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x1c,0x00,0x30,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,
0xc0,0x01,0x00,0x00,0x00,0x00};

View File

@@ -0,0 +1,55 @@
#define infocom_graphics_width 256
#define infocom_graphics_height 24
static unsigned char infocom_graphics_bits[] = {
0x00,0x00,0x00,0x80,0x01,0x00,0x00,0x00,0x10,0x08,0x10,0x00,0x10,0x08,0x08,
0x00,0x00,0x10,0x08,0x01,0x80,0x10,0xff,0xff,0x00,0x1f,0xf8,0x10,0xff,0x1f,
0xf8,0xf8,0x00,0x0c,0x30,0x40,0x02,0x00,0x00,0x00,0x10,0x08,0x10,0x00,0x10,
0x08,0x08,0x00,0x00,0x10,0x08,0x02,0x40,0x10,0xff,0xff,0x00,0x1f,0xf8,0x10,
0xff,0x1f,0xf8,0xf8,0x00,0x0e,0x70,0x20,0x04,0x00,0x00,0x00,0x10,0x08,0x10,
0x00,0x10,0x08,0x08,0x00,0x00,0x10,0x08,0x04,0x20,0x10,0xff,0xff,0x00,0x1f,
0xf8,0x10,0xff,0x1f,0xf8,0xf8,0x00,0xff,0xff,0x10,0x08,0x00,0x00,0xff,0x10,
0x08,0xff,0x00,0x10,0x08,0x08,0xf8,0x1f,0x10,0x08,0xf8,0x1f,0x10,0xff,0xff,
0xff,0x1f,0xf8,0xff,0xff,0x1f,0xf8,0xf8,0x00,0x0e,0x70,0x08,0x10,0x00,0xff,
0x00,0x10,0x08,0x00,0xff,0xf0,0x0f,0xf8,0x08,0x10,0x1f,0xf8,0x08,0x10,0x1f,
0xff,0xff,0xff,0x1f,0xf8,0xff,0xff,0xff,0xff,0xf8,0x00,0x0c,0x30,0x04,0x20,
0x00,0x00,0x00,0x10,0x08,0x00,0x10,0x10,0x08,0x00,0x08,0x10,0x00,0x04,0x08,
0x10,0x20,0xff,0x00,0xff,0x1f,0xf8,0xff,0x10,0x1f,0xf8,0x00,0x00,0x00,0x00,
0x02,0x40,0x00,0x00,0x00,0x10,0x08,0x00,0x10,0x10,0x08,0x00,0x08,0x10,0x00,
0x02,0x08,0x10,0x40,0xff,0x00,0xff,0x1f,0xf8,0xff,0x10,0x1f,0xf8,0x00,0x00,
0x00,0x00,0x01,0x80,0x00,0x00,0x00,0x10,0x08,0x00,0x10,0x10,0x08,0x00,0x08,
0x10,0x00,0x01,0x08,0x10,0x80,0xff,0x00,0xff,0x1f,0xf8,0xff,0x10,0x1f,0xf8,
0x00,0x00,0x00,0x1f,0xf8,0x01,0x80,0x1f,0x80,0x00,0x00,0x01,0xff,0x00,0x01,
0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x10,0x18,
0x00,0x18,0xff,0x00,0x00,0x1f,0xf8,0x02,0x40,0x1f,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x80,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x80,0x01,0x42,
0x10,0x3c,0x18,0x3c,0x81,0x00,0x00,0x1f,0xf8,0x04,0x20,0x1f,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x80,0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff,0x80,
0x01,0x24,0x10,0x7e,0x18,0x7e,0x81,0xf8,0x1f,0x1f,0xf8,0xf8,0x1f,0x1f,0x00,
0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,
0xff,0x80,0x01,0x18,0x10,0x18,0x18,0x18,0x81,0xf8,0x1f,0x1f,0xf8,0xf8,0x1f,
0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x00,0x01,0x03,0x07,0x0f,0x1f,
0x3f,0x7f,0xff,0x80,0x01,0x18,0xff,0x18,0x18,0x18,0x81,0xf8,0x1f,0x00,0x04,
0xf8,0x1f,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x00,0x01,0x03,0x07,
0x0f,0x1f,0x3f,0x7f,0xff,0x80,0x01,0x24,0x10,0x18,0x7e,0x7e,0x81,0xf8,0x1f,
0x00,0x02,0xf8,0x1f,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x80,0x01,0x42,0x10,0x18,0x3c,0x3c,0x81,
0xf8,0x1f,0x00,0x01,0xf8,0x1f,0x80,0x00,0x80,0x01,0x00,0x00,0xff,0x01,0x80,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x10,0x00,0x18,
0x18,0xff,0x00,0xc6,0x1e,0x00,0xc6,0xc6,0x92,0x00,0xc6,0x0c,0x18,0x06,0x1c,
0xee,0x18,0x8e,0x06,0x18,0x3e,0x06,0x18,0x0e,0x7e,0x0e,0xdb,0x1e,0x06,0xe7,
0xff,0xe7,0xff,0x00,0x3c,0x6e,0x76,0x78,0xc6,0xee,0x4a,0xc6,0xca,0x0c,0x18,
0x06,0x3c,0xd6,0x1b,0x56,0x06,0x18,0xc6,0xc6,0x3c,0x1e,0xc0,0x36,0x5a,0x26,
0xc6,0xc3,0xe7,0xc3,0xc3,0x00,0x66,0x3a,0xc6,0xd8,0xee,0xd6,0x26,0x6c,0xf2,
0x0c,0x7e,0x1e,0x6c,0xee,0x1e,0x26,0xc6,0x18,0xc6,0xe6,0x5a,0x36,0x7e,0xc6,
0x3c,0x4a,0x66,0x81,0xe7,0x81,0x99,0x00,0x60,0x06,0x3e,0x18,0x92,0xd6,0x16,
0x38,0xc6,0x0c,0x99,0x36,0x0c,0xc6,0x3c,0x8e,0xe6,0x18,0x3e,0xf6,0x99,0x66,
0xc0,0x36,0x18,0x92,0x36,0xe7,0xe7,0xe7,0x9f,0x00,0x18,0x1e,0xc6,0x18,0xee,
0xc6,0x0e,0x38,0x9e,0x0c,0x7e,0xe6,0x0c,0xc6,0x78,0x56,0xf6,0x3c,0x66,0xde,
0x18,0xc6,0xc0,0x1e,0x18,0xf2,0x1e,0xe7,0xe7,0xe7,0xe7,0x00,0x00,0x66,0x76,
0x1b,0xc6,0xc6,0x06,0x6c,0xa6,0x0c,0x18,0xc6,0x0c,0xc6,0xd8,0x26,0xde,0x5a,
0xc6,0xce,0x18,0xc6,0xc0,0x06,0x18,0x92,0x06,0xe7,0x81,0x81,0xff,0x00,0x18,
0x06,0x1e,0x1e,0xc6,0xc6,0x06,0xc6,0xc6,0x0c,0x18,0xc6,0x0c,0xc6,0x18,0x06,
0xce,0xdb,0xc6,0xc0,0x18,0xc6,0x3e,0x06,0x18,0x92,0x06,0xe7,0xc3,0xc3,0xe7,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xe7,
0xe7,0xff,0x00};

309
engines/glk/zcode/mem.cpp Normal file
View File

@@ -0,0 +1,309 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/mem.h"
#include "glk/zcode/zcode.h"
#include "common/memstream.h"
#include "common/textconsole.h"
namespace Glk {
namespace ZCode {
Mem::Mem() : story_fp(nullptr), story_size(0), first_undo(nullptr), last_undo(nullptr),
curr_undo(nullptr), undo_mem(nullptr), zmp(nullptr), pcp(nullptr), prev_zmp(nullptr),
undo_diff(nullptr), undo_count(0), reserve_mem(0) {
}
void Mem::initialize() {
initializeStoryFile();
loadGameHeader();
loadMemory();
initializeUndo();
// Read header extension table
hx_table_size = get_header_extension(HX_TABLE_SIZE);
hx_unicode_table = get_header_extension(HX_UNICODE_TABLE);
hx_flags = get_header_extension(HX_FLAGS);
}
void Mem::initializeStoryFile() {
if (story_fp->size() < 64)
error("This file is too small to be a Z-code file.");
}
void Mem::loadGameHeader() {
// Load header
zmp = (byte *)malloc(64);
story_fp->seek(0);
story_fp->read(zmp, 64);
Common::MemoryReadStream h(zmp, 64);
loadHeader(h);
// Calculate story file size in bytes
if (h_file_size != 0) {
story_size = (long)2 * h_file_size;
if (h_version >= V4)
story_size *= 2;
if (h_version >= V6)
story_size *= 2;
} else {
// Some old games lack the file size entry
story_size = story_fp->size();
}
}
void Mem::loadMemory() {
// Allocate memory for story data
if ((zmp = (zbyte *)realloc(zmp, story_size)) == nullptr)
error("Out of memory");
// Load story file in chunks of 32KB
uint n = 0x8000;
for (uint size = 64; size < story_size; size += n) {
if (story_size - size < 0x8000)
n = story_size - size;
if (story_fp->read(zmp + size, n) != n)
error("Story file read error");
}
}
void Mem::initializeUndo() {
byte *reserved = nullptr;
if (reserve_mem != 0) {
if ((reserved = new byte[reserve_mem]) == nullptr)
return;
}
// Allocate h_dynamic_size bytes for previous dynamic zmp state
// + 1.5 h_dynamic_size for Quetzal diff + 2.
undo_mem = new zbyte[(h_dynamic_size * 5) / 2 + 2];
if (undo_mem != nullptr) {
prev_zmp = undo_mem;
undo_diff = undo_mem + h_dynamic_size;
memcpy(prev_zmp, zmp, h_dynamic_size);
} else {
_undo_slots = 0;
}
if (reserve_mem)
delete[] reserved;
}
zword Mem::get_header_extension(int entry) {
zword addr;
zword val;
if (h_extension_table == 0 || entry > hx_table_size)
return 0;
addr = h_extension_table + 2 * entry;
LOW_WORD(addr, val);
return val;
}
void Mem::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);
}
void Mem::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);
set_header_extension(HX_FLAGS, hx_flags);
set_header_extension(HX_FORE_COLOUR, hx_fore_colour);
set_header_extension(HX_BACK_COLOUR, hx_back_colour);
}
void Mem::storeb(zword addr, zbyte value) {
if (addr >= h_dynamic_size)
runtimeError(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);
flagsChanged(value);
}
SET_BYTE(addr, value);
}
void Mem::storew(zword addr, zword value) {
storeb((zword)(addr + 0), hi(value));
storeb((zword)(addr + 1), lo(value));
}
void Mem::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;
free(p);
undo_count--;
}
if (first_undo)
first_undo->prev = nullptr;
else
last_undo = nullptr;
}
void Mem::reset_memory() {
story_fp = nullptr;
if (undo_mem) {
free_undo(undo_count);
delete[] undo_mem;
}
undo_mem = nullptr;
undo_count = 0;
free(zmp);
zmp = nullptr;
}
long Mem::mem_diff(zbyte *a, zbyte *b, zword mem_size, zbyte *diff) {
unsigned size = mem_size;
zbyte *p = diff;
unsigned j;
zbyte c = 0;
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;
}
void Mem::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;
}
}
}
} // End of namespace ZCode
} // End of namespace Glk

169
engines/glk/zcode/mem.h Normal file
View File

@@ -0,0 +1,169 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_MEM
#define GLK_ZCODE_MEM
#include "glk/zcode/frotz_types.h"
#include "glk/zcode/config.h"
namespace Glk {
namespace ZCode {
#define SET_WORD(addr,v) zmp[addr] = hi(v); zmp[addr+1] = lo(v)
#define LOW_WORD(addr,v) v = READ_BE_UINT16(&zmp[addr])
#define HIGH_WORD(addr,v) v = READ_BE_UINT16(&zmp[addr])
#define HIGH_LONG(addr,v) v = READ_BE_UINT32(&zmp[addr])
#define SET_BYTE(addr,v) zmp[addr] = v
#define LOW_BYTE(addr,v) v = zmp[addr]
typedef uint offset_t;
/**
* Stores undo information
*/
struct undo_struct {
undo_struct *next;
undo_struct *prev;
offset_t pc;
long diff_size;
zword frame_count;
zword stack_size;
zword frame_offset;
// undo diff and stack data follow
};
typedef undo_struct undo_t;
/**
* Handles the memory, header, and user options
*/
class Mem : public Header, public virtual UserOptions {
protected:
Common::SeekableReadStream *story_fp;
uint story_size;
byte *pcp;
byte *zmp;
undo_t *first_undo, *last_undo, *curr_undo;
zbyte *undo_mem, *prev_zmp, *undo_diff;
int undo_count;
int reserve_mem;
private:
/**
* Handles setting the story file, parsing it if it's a Blorb file
*/
void initializeStoryFile();
/**
* Handles loading the game header
*/
void loadGameHeader();
/**
* Initializes memory and loads the story data
*/
void loadMemory();
/**
* Setup undo data
*/
void initializeUndo();
protected:
/**
* Read a value from the header extension (former mouse table).
*/
zword get_header_extension(int entry);
/**
* Set an entry in the header extension (former mouse table).
*/
void set_header_extension(int entry, zword val);
/**
* Set all header fields which hold information about the interpreter.
*/
void restart_header();
/**
* Write a byte value to the dynamic Z-machine memory.
*/
void storeb(zword addr, zbyte value);
/**
* Write a word value to the dynamic Z-machine memory.
*/
void storew(zword addr, zword value);
/**
* Free count undo blocks from the beginning of the undo list
*/
void free_undo(int count);
/**
* Generates a runtime error
*/
virtual void runtimeError(ErrorCode errNum) = 0;
/**
* Called when the flags are changed
*/
virtual void flagsChanged(zbyte value) = 0;
/**
* Close the story file and deallocate memory.
*/
void reset_memory();
/**
* 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.
*
*/
long mem_diff(zbyte *a, zbyte *b, zword mem_size, zbyte *diff);
/**
* Applies a quetzal-like diff to dest
*/
void mem_undiff(zbyte *diff, long diff_length, zbyte *dest);
public:
/**
* Constructor
*/
Mem();
/**
* Destructor
*/
virtual ~Mem() {}
/**
* Initialize
*/
void initialize();
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

190
engines/glk/zcode/pics.cpp Normal file
View File

@@ -0,0 +1,190 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/pics.h"
#include "glk/zcode/pics_decoder.h"
#include "glk/glk.h"
#include "common/algorithm.h"
#include "common/memstream.h"
namespace Glk {
namespace ZCode {
enum {
PIC_FILE_HEADER_FLAGS = 1,
PIC_FILE_HEADER_NUM_IMAGES = 4,
PIC_FILE_HEADER_ENTRY_SIZE = 8,
PIC_FILE_HEADER_VERSION = 14
};
Pics::Pics() : Common::Archive(), _filename(getFilename()) {
Common::File f;
if (!f.open(Common::Path(_filename)))
error("Error reading Pics file");
_palette = new Common::Array<byte>();
Common::Array<uint> offsets;
byte buffer[16];
f.read(buffer, 16);
_index.resize(READ_LE_UINT16(&buffer[PIC_FILE_HEADER_NUM_IMAGES]));
_entrySize = buffer[PIC_FILE_HEADER_ENTRY_SIZE];
_version = buffer[PIC_FILE_HEADER_FLAGS];
assert(_entrySize >= 8 && _entrySize <= 14);
// Iterate through loading the index
for (uint idx = 0; idx < _index.size(); ++idx) {
Entry &e = _index[idx];
f.read(buffer, _entrySize);
e._number = READ_LE_UINT16(buffer);
e._width = READ_LE_UINT16(buffer + 2);
e._height = READ_LE_UINT16(buffer + 4);
e._flags = READ_LE_UINT16(buffer + 6);
if (_entrySize >= 11) {
e._dataOffset = READ_BE_UINT32(buffer + 7) & 0xffffff;
if (e._dataOffset)
offsets.push_back(e._dataOffset);
if (_entrySize == 14) {
e._paletteOffset = READ_BE_UINT32(buffer + 10) & 0xffffff;
}
}
if (e._dataOffset)
e._filename = Common::String::format("pic%u.raw", e._number);
else
e._filename = Common::String::format("pic%u.rect", e._number);
}
// Further processing of index to calculate data sizes
Common::sort(offsets.begin(), offsets.end());
for (uint idx = 0; idx < _index.size(); ++idx) {
Entry &e = _index[idx];
if (!e._dataOffset)
continue;
// Find the entry in the offsets array
uint oidx = 0;
while (oidx < offsets.size() && offsets[oidx] != e._dataOffset)
++oidx;
// Set the size
e._dataSize = (oidx == (offsets.size() - 1) ? f.size() : offsets[oidx + 1]) - e._dataOffset;
}
f.close();
}
Pics::~Pics() {
delete _palette;
}
Common::String Pics::getFilename() {
Common::String filename = g_vm->getFilename();
while (filename.contains('.'))
filename.deleteLastChar();
return filename + ".mg1";
}
bool Pics::exists() {
return Common::File::exists(Common::Path(getFilename()));
}
bool Pics::hasFile(const Common::Path &path) const {
Common::String name = path.toString();
for (uint idx = 0; idx < _index.size(); ++idx) {
if (_index[idx]._filename.equalsIgnoreCase(name))
return true;
}
return false;
}
int Pics::listMembers(Common::ArchiveMemberList &list) const {
for (uint idx = 0; idx < _index.size(); ++idx) {
list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(_index[idx]._filename, *this)));
}
return (int)_index.size();
}
const Common::ArchiveMemberPtr Pics::getMember(const Common::Path &path) const {
if (!hasFile(path))
return Common::ArchiveMemberPtr();
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
}
Common::SeekableReadStream *Pics::createReadStreamForMember(const Common::Path &path) const {
Common::String name = path.toString();
PictureDecoder decoder;
for (uint idx = 0; idx < _index.size(); ++idx) {
const Entry &e = _index[idx];
if (e._filename.equalsIgnoreCase(name)) {
Common::File f;
Common::SeekableReadStream *dest;
if (!f.open(path))
error("Reading failed");
if (e._dataSize) {
loadPalette(f, e, *_palette);
f.seek(e._dataOffset);
Common::SeekableReadStream *src = f.readStream(e._dataSize);
dest = decoder.decode(*src, e._flags, *_palette, kMCGA, e._width, e._height);
delete src;
} else {
byte *rect = (byte *)malloc(2 * sizeof(uint32));
WRITE_BE_UINT32(rect, e._width);
WRITE_BE_UINT32(rect + 4, e._height);
dest = new Common::MemoryReadStream(rect, 2 * sizeof(uint32), DisposeAfterUse::YES);
}
f.close();
return dest;
}
}
return nullptr;
}
void Pics::loadPalette(Common::File &f, const Entry &e, Common::Array<byte> &palette) const {
if (e._paletteOffset) {
// Read in the image's palette
assert(e._paletteOffset);
f.seek(e._paletteOffset);
_palette->resize(f.readByte() * 3);
f.read(&(*_palette)[0], _palette->size());
}
if (e._flags & 1) {
byte *entry = &palette[(e._flags >> 12) * 3];
Common::fill(entry, entry + 3, 0);
}
}
} // End of namespace ZCode
} // End of namespace Glk

136
engines/glk/zcode/pics.h Normal file
View File

@@ -0,0 +1,136 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_PICS_H
#define GLK_ZCODE_PICS_H
#include "common/archive.h"
#include "common/array.h"
#include "common/file.h"
namespace Glk {
namespace ZCode {
enum PicturesMode {
kMONO = 0,
kTEXT = 1,
kCGA = 2,
kMCGA = 3,
kEGA = 4,
kAmiga = 5
};
/**
* Infocom graphics file manager
*/
class Pics : public Common::Archive {
/**
* Describes a single index entry
*/
struct Entry {
uint _number;
size_t _width, _height;
uint _flags;
size_t _dataOffset;
size_t _dataSize;
size_t _paletteOffset;
Common::String _filename;
/**
* Constructor
*/
Entry() : _number(0), _width(0), _height(0), _flags(0), _dataOffset(0), _dataSize(0),
_paletteOffset(0) {}
};
private:
Common::String _filename;
Common::Array<Entry> _index; ///< list of entries
uint _entrySize;
uint _version;
Common::Array<byte> *_palette;
private:
/**
* Returns the filename for the pictures archive
*/
static Common::String getFilename();
/**
* Read in the palette
*/
void loadPalette(Common::File &f, const Entry &e, Common::Array<byte> &palette) const;
public:
/**
* Returns true if an mg1 file exists for the game
*/
static bool exists();
public:
/**
* Constructor
*/
Pics();
/**
* Destructor
*/
~Pics() override;
/**
* Return the number of entries in the file
*/
size_t size() const { return _index.size(); }
/**
* Return the version of the file
*/
uint version() const { return _version; }
/**
* Check if a member with the given name is present in the Archive.
* Patterns are not allowed, as this is meant to be a quick File::exists()
* replacement.
*/
bool hasFile(const Common::Path &path) const override;
/**
* Add all members of the Archive to list.
* Must only append to list, and not remove elements from it.
*
* @return the number of names added to list
*/
int listMembers(Common::ArchiveMemberList &list) const override;
/**
* Returns a ArchiveMember representation of the given file.
*/
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
/**
* Create a stream bound to a member with the specified name in the
* archive. If no member with this name exists, 0 is returned.
* @return the newly created input stream
*/
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,167 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/pics_decoder.h"
#include "glk/zcode/pics.h"
#include "common/memstream.h"
namespace Glk {
namespace ZCode {
#define MAX_BIT 512 /* Must be less than or equal to CODE_TABLE_SIZE */
#define CODE_SIZE 8
#define CODE_TABLE_SIZE 4096
#define PREFIX 0
#define PIXEL 1
/**
* Support class used for picture decompression
*/
class Compress {
private:
byte _codeBuffer[CODE_TABLE_SIZE];
public:
short _nextCode;
short _sLen;
short _sPtr;
short _tLen;
short _tPtr;
Compress() : _nextCode(0), _sLen(0), _sPtr(0), _tLen(0), _tPtr(0) {}
/**
* Read a code
*/
short readCode(Common::ReadStream &src);
};
static short MASK[16] = {
0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f,
0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff
};
short Compress::readCode(Common::ReadStream &src) {
short code, bsize, tlen, tptr;
code = 0;
tlen = _tLen;
tptr = 0;
while (tlen) {
if (_sLen == 0) {
if ((_sLen = src.read(_codeBuffer, MAX_BIT)) == 0) {
error("fread");
}
_sLen *= 8;
_sPtr = 0;
}
bsize = ((_sPtr + 8) & ~7) - _sPtr;
bsize = (tlen > bsize) ? bsize : tlen;
code |= (((uint)_codeBuffer[_sPtr >> 3] >> (_sPtr & 7)) & MASK[bsize]) << tptr;
tlen -= bsize;
tptr += bsize;
_sLen -= bsize;
_sPtr += bsize;
}
if ((_nextCode == MASK[_tLen]) && (_tLen < 12))
_tLen++;
return code;
}
/*--------------------------------------------------------------------------*/
PictureDecoder::PictureDecoder() {
_tableVal = new byte[3 * 3840];
_tableRef = (uint16 *)(_tableVal + 3840);
}
PictureDecoder::~PictureDecoder() {
delete[] _tableVal;
}
Common::SeekableReadStream *PictureDecoder::decode(Common::ReadStream &src, uint flags,
const Common::Array<byte> &palette, uint display, size_t width, size_t height) {
Common::MemoryWriteStreamDynamic out(DisposeAfterUse::NO);
short code_table[CODE_TABLE_SIZE][2];
byte buffer[CODE_TABLE_SIZE];
// Write out dimensions
out.writeUint16LE(width);
out.writeUint16LE(height);
// Write out palette
out.writeUint16LE(palette.size() / 3 + 2);
for (int idx = 0; idx < 6; ++idx)
out.writeByte((idx < 3) ? 0x77 : 0);
if (!palette.empty())
out.write(&palette[0], palette.size());
byte transparent = 0xff;
if (flags & 1)
transparent = flags >> 12;
out.writeByte(transparent);
int i;
short code, old = 0, first, clear_code;
Compress comp;
clear_code = 1 << CODE_SIZE;
comp._nextCode = clear_code + 2;
comp._tLen = CODE_SIZE + 1;
comp._tPtr = 0;
for (i = 0; i < CODE_TABLE_SIZE; i++) {
code_table[i][PREFIX] = CODE_TABLE_SIZE;
code_table[i][PIXEL] = i;
}
for (;;) {
if ((code = comp.readCode(src)) == (clear_code + 1))
break;
if (code == clear_code) {
comp._tLen = CODE_SIZE + 1;
comp._nextCode = clear_code + 2;
code = comp.readCode(src);
} else {
first = (code == comp._nextCode) ? old : code;
while (code_table[first][PREFIX] != CODE_TABLE_SIZE)
first = code_table[first][PREFIX];
code_table[comp._nextCode][PREFIX] = old;
code_table[comp._nextCode++][PIXEL] = code_table[first][PIXEL];
}
old = code;
i = 0;
do
buffer[i++] = (unsigned char)code_table[code][PIXEL];
while ((code = code_table[code][PREFIX]) != CODE_TABLE_SIZE);
do
out.writeByte(buffer[--i]);
while (i > 0);
}
return new Common::MemoryReadStream(out.getData(), out.size(), DisposeAfterUse::YES);
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,60 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_PICS_DECODER_H
#define GLK_ZCODE_PICS_DECODER_H
#include "common/stream.h"
#include "common/array.h"
namespace Glk {
namespace ZCode {
/**
* Decodes an Infocom encoded picture into a raw pixel stream that the outer
* Glk engine is capable of then loading into a picture object
*/
class PictureDecoder {
private:
byte *_tableVal;
uint16 *_tableRef;
public:
/**
* Constructor
*/
PictureDecoder();
/**
* Destructor
*/
~PictureDecoder();
/**
* Decode method
*/
Common::SeekableReadStream *decode(Common::ReadStream &src, uint flags,
const Common::Array<byte> &palette, uint display, size_t width, size_t height);
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,669 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
#include "glk/zcode/zcode.h"
#include "glk/conf.h"
namespace Glk {
namespace ZCode {
// TODO: Stubs to replace with actual code
zword save_undo() { return 0; }
zword restore_undo() { return 0; }
Opcode Processor::var_opcodes[64] = {
&Processor::__illegal__,
&Processor::z_je,
&Processor::z_jl,
&Processor::z_jg,
&Processor::z_dec_chk,
&Processor::z_inc_chk,
&Processor::z_jin,
&Processor::z_test,
&Processor::z_or,
&Processor::z_and,
&Processor::z_test_attr,
&Processor::z_set_attr,
&Processor::z_clear_attr,
&Processor::z_store,
&Processor::z_insert_obj,
&Processor::z_loadw,
&Processor::z_loadb,
&Processor::z_get_prop,
&Processor::z_get_prop_addr,
&Processor::z_get_next_prop,
&Processor::z_add,
&Processor::z_sub,
&Processor::z_mul,
&Processor::z_div,
&Processor::z_mod,
&Processor::z_call_s,
&Processor::z_call_n,
&Processor::z_set_colour,
&Processor::z_throw,
&Processor::__illegal__,
&Processor::__illegal__,
&Processor::__illegal__,
&Processor::z_call_s,
&Processor::z_storew,
&Processor::z_storeb,
&Processor::z_put_prop,
&Processor::z_read,
&Processor::z_print_char,
&Processor::z_print_num,
&Processor::z_random,
&Processor::z_push,
&Processor::z_pull,
&Processor::z_split_window,
&Processor::z_set_window,
&Processor::z_call_s,
&Processor::z_erase_window,
&Processor::z_erase_line,
&Processor::z_set_cursor,
&Processor::z_get_cursor,
&Processor::z_set_text_style,
&Processor::z_buffer_mode,
&Processor::z_output_stream,
&Processor::z_input_stream,
&Processor::z_sound_effect,
&Processor::z_read_char,
&Processor::z_scan_table,
&Processor::z_not,
&Processor::z_call_n,
&Processor::z_call_n,
&Processor::z_tokenise,
&Processor::z_encode_text,
&Processor::z_copy_table,
&Processor::z_print_table,
&Processor::z_check_arg_count
};
Opcode Processor::ext_opcodes[64] = {
&Processor::z_save,
&Processor::z_restore,
&Processor::z_log_shift,
&Processor::z_art_shift,
&Processor::z_set_font,
&Processor::z_draw_picture,
&Processor::z_picture_data,
&Processor::z_erase_picture,
&Processor::z_set_margins,
&Processor::z_save_undo,
&Processor::z_restore_undo,
&Processor::z_print_unicode,
&Processor::z_check_unicode,
&Processor::z_set_true_colour, // spec 1.1
&Processor::__illegal__,
&Processor::__illegal__,
&Processor::z_move_window,
&Processor::z_window_size,
&Processor::z_window_style,
&Processor::z_get_wind_prop,
&Processor::z_scroll_window,
&Processor::z_pop_stack,
&Processor::z_read_mouse,
&Processor::z_mouse_window,
&Processor::z_push_stack,
&Processor::z_put_wind_prop,
&Processor::z_print_form,
&Processor::z_make_menu,
&Processor::z_picture_table,
&Processor::z_buffer_screen // spec 1.1
};
Processor::Processor(OSystem *syst, const GlkGameDescription &gameDesc) :
GlkInterface(syst, gameDesc),
_finished(0), _sp(nullptr), _fp(nullptr), _frameCount(0),
zargc(0), _decoded(nullptr), _encoded(nullptr), _resolution(0),
_randomInterval(0), _randomCtr(0), first_restart(true), script_valid(false),
_bufPos(0), _locked(false), _prevC('\0'), script_width(0),
sfp(nullptr), rfp(nullptr), pfp(nullptr), ostream_screen(true), ostream_script(false),
ostream_memory(false), ostream_record(false), istream_replay(false), message(false) {
static const Opcode OP0_OPCODES[16] = {
&Processor::z_rtrue,
&Processor::z_rfalse,
&Processor::z_print,
&Processor::z_print_ret,
&Processor::z_nop,
&Processor::z_save,
&Processor::z_restore,
&Processor::z_restart,
&Processor::z_ret_popped,
&Processor::z_catch,
&Processor::z_quit,
&Processor::z_new_line,
&Processor::z_show_status,
&Processor::z_verify,
&Processor::__extended__,
&Processor::z_piracy
};
static const Opcode OP1_OPCODES[16] = {
&Processor::z_jz,
&Processor::z_get_sibling,
&Processor::z_get_child,
&Processor::z_get_parent,
&Processor::z_get_prop_len,
&Processor::z_inc,
&Processor::z_dec,
&Processor::z_print_addr,
&Processor::z_call_s,
&Processor::z_remove_obj,
&Processor::z_print_obj,
&Processor::z_ret,
&Processor::z_jump,
&Processor::z_print_paddr,
&Processor::z_load,
&Processor::z_call_n
};
op0_opcodes.resize(16);
op1_opcodes.resize(16);
Common::copy(&OP0_OPCODES[0], &OP0_OPCODES[16], &op0_opcodes[0]);
Common::copy(&OP1_OPCODES[0], &OP1_OPCODES[16], &op1_opcodes[0]);
Common::fill(&_stack[0], &_stack[STACK_SIZE], 0);
Common::fill(&zargs[0], &zargs[8], 0);
Common::fill(&_buffer[0], &_buffer[TEXT_BUFFER_SIZE], '\0');
Common::fill(&_errorCount[0], &_errorCount[ERR_NUM_ERRORS], 0);
}
void Processor::initialize() {
Mem::initialize();
GlkInterface::initialize();
if (h_version <= V4) {
op0_opcodes[9] = &Processor::z_pop;
op1_opcodes[15] = &Processor::z_not;
} else {
op0_opcodes[9] = &Processor::z_catch;
op1_opcodes[15] = &Processor::z_call_n;
}
}
void Processor::load_operand(zbyte type) {
zword value;
if (type & 2) {
// variable
zbyte variable;
CODE_BYTE(variable);
if (variable == 0)
value = *_sp++;
else if (variable < 16)
value = *(_fp - variable);
else {
zword addr = h_globals + 2 * (variable - 16);
LOW_WORD(addr, value);
}
} else if (type & 1) {
// small constant
zbyte bvalue;
CODE_BYTE(bvalue);
value = bvalue;
} else {
// large constant
CODE_WORD(value);
}
zargs[zargc++] = value;
}
void Processor::load_all_operands(zbyte specifier) {
for (int i = 6; i >= 0; i -= 2) {
zbyte type = (specifier >> i) & 0x03;
if (type == 3)
break;
load_operand(type);
}
}
void Processor::interpret() {
do {
zbyte opcode;
CODE_BYTE(opcode);
zargc = 0;
if (opcode < 0x80) {
// 2OP opcodes
load_operand((zbyte)(opcode & 0x40) ? 2 : 1);
load_operand((zbyte)(opcode & 0x20) ? 2 : 1);
(*this.*var_opcodes[opcode & 0x1f])();
} else if (opcode < 0xb0) {
// 1OP opcodes
load_operand((zbyte)(opcode >> 4));
(*this.*op1_opcodes[opcode & 0x0f])();
} else if (opcode < 0xc0) {
// 0OP opcodes
(*this.*op0_opcodes[opcode - 0xb0])();
} else {
// VAR opcodes
zbyte specifier1;
zbyte specifier2;
if (opcode == 0xec || opcode == 0xfa) { // opcodes 0xec
CODE_BYTE(specifier1); // and 0xfa are
CODE_BYTE(specifier2); // call opcodes
load_all_operands(specifier1); // with up to 8
load_all_operands(specifier2); // arguments
} else {
CODE_BYTE(specifier1);
load_all_operands(specifier1);
}
(*this.*var_opcodes[opcode - 0xc0])();
}
#if defined(DJGPP) && defined(SOUND_SUPPORT)
if (end_of_sound_flag)
end_of_sound();
#endif
} while (!shouldQuit() && !_finished);
_finished--;
}
void Processor::call(zword routine, int argc, zword *args, int ct) {
uint32 pc;
zword value;
zbyte count;
int i;
if (_sp - _stack < 4)
runtimeError(ERR_STK_OVF);
GET_PC(pc);
*--_sp = (zword)(pc >> 9);
*--_sp = (zword)(pc & 0x1ff);
*--_sp = (zword)(_fp - _stack - 1);
*--_sp = (zword)(argc | (ct << (_quetzal ? 12 : 8)));
_fp = _sp;
_frameCount++;
// Calculate byte address of routine
if (h_version <= V3)
pc = (long)routine << 1;
else if (h_version <= V5)
pc = (long)routine << 2;
else if (h_version <= V7)
pc = ((long)routine << 2) + ((long)h_functions_offset << 3);
else if (h_version <= V8)
pc = (long)routine << 3;
else {
// h_version == V9
long indirect = (long)routine << 2;
HIGH_LONG(indirect, pc);
}
if ((uint)pc >= story_size)
runtimeError(ERR_ILL_CALL_ADDR);
SET_PC(pc);
// Initialise local variables
CODE_BYTE(count);
if (count > 15)
runtimeError(ERR_CALL_NON_RTN);
if (_sp - _stack < count)
runtimeError(ERR_STK_OVF);
if (_quetzal)
_fp[0] |= (zword)count << 8; // Save local var count for Quetzal.
value = 0;
for (i = 0; i < count; i++) {
if (h_version <= V4) // V1 to V4 games provide default
CODE_WORD(value); // values for all local variables
*--_sp = (zword)((argc-- > 0) ? args[i] : value);
}
// Start main loop for direct calls
if (ct == 2)
interpret();
}
void Processor::ret(zword value) {
offset_t pc;
int ct;
if (_sp > _fp)
runtimeError(ERR_STK_UNDF);
_sp = _fp;
ct = *_sp++ >> (_quetzal ? 12 : 8);
_frameCount--;
_fp = _stack + 1 + *_sp++;
pc = *_sp++;
pc = ((offset_t)*_sp++ << 9) | pc;
SET_PC(pc);
// Handle resulting value
if (ct == 0)
store(value);
if (ct == 2)
*--_sp = value;
// Stop main loop for direct calls
if (ct == 2)
_finished++;
}
void Processor::branch(bool flag) {
offset_t pc;
zword offset;
zbyte specifier;
zbyte off1;
zbyte off2;
CODE_BYTE(specifier);
off1 = specifier & 0x3f;
if (!flag)
specifier ^= 0x80;
if (!(specifier & 0x40)) {
// it's a long branch
if (off1 & 0x20) // propagate sign bit
off1 |= 0xc0;
CODE_BYTE(off2);
offset = (off1 << 8) | off2;
} else {
// It's a short branch
offset = off1;
}
if (specifier & 0x80) {
if (offset > 1) {
// normal branch
GET_PC(pc);
pc += (short)offset - 2;
SET_PC(pc);
} else {
// special case, return 0 or 1
ret(offset);
}
}
}
void Processor::store(zword value) {
zbyte variable;
CODE_BYTE(variable);
if (variable == 0)
*--_sp = value;
else if (variable < 16)
*(_fp - variable) = value;
else {
zword addr = h_globals + 2 * (variable - 16);
SET_WORD(addr, value);
}
}
int Processor::direct_call(zword addr) {
zword saved_zargs[8];
int saved_zargc;
int i;
// Calls to address 0 return false
if (addr == 0)
return 0;
// Save operands and operand count
for (i = 0; i < 8; i++)
saved_zargs[i] = zargs[i];
saved_zargc = zargc;
// Call routine directly
call(addr, 0, nullptr, 2);
// Restore operands and operand count
for (i = 0; i < 8; i++)
zargs[i] = saved_zargs[i];
zargc = saved_zargc;
// Resulting value lies on top of the stack
return (short)*_sp++;
}
void Processor::seed_random(int value) {
if (value == 0) {
// Now using random values
_randomInterval = 0;
} else if (value < 1000) {
// special seed value
_randomCtr = 0;
_randomInterval = value;
} else {
// standard seed value
_random.setSeed(value);
_randomInterval = 0;
}
}
void Processor::__extended__() {
zbyte opcode;
zbyte specifier;
CODE_BYTE(opcode);
CODE_BYTE(specifier);
load_all_operands(specifier);
if (opcode < 0x1e) // extended opcodes from 0x1e on
(*this.*ext_opcodes[opcode])(); // are reserved for future spec'
}
void Processor::__illegal__() {
runtimeError(ERR_ILL_OPCODE);
}
void Processor::z_catch() {
store(_quetzal ? _frameCount : (zword)(_fp - _stack));
}
void Processor::z_throw() {
if (_quetzal) {
if (zargs[1] > _frameCount)
runtimeError(ERR_BAD_FRAME);
// Unwind the stack a frame at a time.
for (; _frameCount > zargs[1]; --_frameCount)
_fp = _stack + 1 + _fp[1];
} else {
if (zargs[1] > STACK_SIZE)
runtimeError(ERR_BAD_FRAME);
_fp = _stack + zargs[1];
}
ret(zargs[0]);
}
void Processor::z_call_n() {
if (zargs[0] != 0)
call(zargs[0], zargc - 1, zargs + 1, 1);
}
void Processor::z_call_s() {
if (zargs[0] != 0)
call(zargs[0], zargc - 1, zargs + 1, 0);
else
store(0);
}
void Processor::z_check_arg_count() {
if (_fp == _stack + STACK_SIZE)
branch(zargs[0] == 0);
else
branch(zargs[0] <= (*_fp & 0xff));
}
void Processor::z_jump() {
offset_t pc;
GET_PC(pc);
pc += (short)zargs[0] - 2;
if (pc >= story_size)
runtimeError(ERR_ILL_JUMP_ADDR);
SET_PC(pc);
}
void Processor::z_nop() {
// Do nothing
}
void Processor::z_quit() {
_finished = 9999;
}
void Processor::z_ret() {
ret(zargs[0]);
}
void Processor::z_ret_popped() {
ret(*_sp++);
}
void Processor::z_rfalse() {
ret(0);
}
void Processor::z_rtrue() {
ret(1);
}
void Processor::z_random() {
if ((short) zargs[0] <= 0) {
// set random seed
seed_random(- (short) zargs[0]);
store(0);
} else {
// generate random number
zword result;
if (_randomInterval != 0) {
// ...in special mode
result = _randomCtr++;
if (_randomCtr == _randomInterval)
_randomCtr = 0;
} else {
// ...in standard mode
result = _random.getRandomNumber(0xffff);
}
store((zword)(result % zargs[0] + 1));
}
}
void Processor::z_sound_effect() {
zword number = zargs[0];
zword effect = zargs[1];
zword volume = zargs[2];
if (zargc < 1)
number = 0;
if (zargc < 2)
effect = EFFECT_PLAY;
if (zargc < 3)
volume = 8;
if (number >= 3 || number == 0) {
_soundLocked = true;
if (_storyId == LURKING_HORROR && (number == 9 || number == 16)) {
if (effect == EFFECT_PLAY) {
next_sample = number;
next_volume = volume;
_soundLocked = false;
if (!_soundPlaying)
start_next_sample();
} else {
_soundLocked = false;
}
return;
}
_soundPlaying = false;
switch (effect) {
case EFFECT_PREPARE:
os_prepare_sample(number);
break;
case EFFECT_PLAY:
start_sample(number, lo(volume), hi(volume), (zargc == 4) ? zargs[3] : 0);
break;
case EFFECT_STOP:
os_stop_sample (number);
break;
case EFFECT_FINISH_WITH:
os_finish_with_sample (number);
break;
default:
break;
}
_soundLocked = false;
} else {
os_beep(number);
}
}
void Processor::z_piracy() {
branch(!_piracy);
}
void Processor::z_save_undo(void) {
store((zword)save_undo());
}
void Processor::z_restore_undo(void) {
store((zword)restore_undo());
}
} // End of namespace ZCode
} // End of namespace Glk

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,200 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
#include "common/algorithm.h"
#include "common/textconsole.h"
namespace Glk {
namespace ZCode {
const char *const Processor::ERR_MESSAGES[ERR_NUM_ERRORS] = {
"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",
"Illegal dictionary word length",
"@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"
};
void Processor::flush_buffer() {
/* 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 || bufferEmpty())
return;
// Send the buffer to the output streams
_buffer[_bufPos] = '\0';
_locked = true;
stream_word(_buffer);
_locked = false;
// Reset the buffer
_bufPos = 0;
_prevC = '\0';
}
void Processor::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 || (_prevC == '-' && 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
_prevC = c;
} else {
flag = false;
}
// Insert the character into the buffer
_buffer[_bufPos++] = c;
if (_bufPos == TEXT_BUFFER_SIZE)
error("Text buffer overflow");
} else {
stream_char(c);
}
}
void Processor::print_string(const char *s) {
char c;
while ((c = *s++) != 0) {
if (c == '\n')
new_line();
else
print_char(c);
}
}
void Processor::print_string_uni(const uint32 *s) {
uint32 c;
while ((c = *s++) != 0) {
if (c == '\n')
new_line();
else
print_char(c);
}
}
void Processor::print_long(uint 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));
}
}
}
void Processor::new_line() {
flush_buffer();
stream_new_line();
}
void Processor::runtimeError(ErrorCode errNum) {
int wasfirst;
if (errNum <= 0 || errNum > ERR_NUM_ERRORS)
return;
if (_err_report_mode == ERR_REPORT_FATAL
|| (!_ignore_errors && errNum <= ERR_MAX_FATAL)) {
flush_buffer();
error("%s", ERR_MESSAGES[errNum - 1]);
return;
}
wasfirst = (_errorCount[errNum - 1] == 0);
_errorCount[errNum - 1]++;
if ((_err_report_mode == ERR_REPORT_ALWAYS)
|| (_err_report_mode == ERR_REPORT_ONCE && wasfirst)) {
offset_t pc;
GET_PC(pc);
print_string("Warning: ");
print_string(ERR_MESSAGES[errNum - 1]);
print_string(" (PC = ");
print_long(pc, 16);
print_char(')');
if (_err_report_mode == ERR_REPORT_ONCE) {
print_string(" (will ignore further occurrences)");
} else {
print_string(" (occurrence ");
print_long(_errorCount[errNum - 1], 10);
print_char(')');
}
new_line();
}
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,222 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
namespace Glk {
namespace ZCode {
#define INPUT_BUFFER_SIZE 200
bool Processor::read_yes_or_no(const char *s) {
zchar key;
print_string(s);
print_string("? (y/n) >");
key = stream_read_key(0, 0, false);
if (key == 'y' || key == 'Y') {
print_string("y\n");
return true;
} else {
print_string("n\n");
return false;
}
}
void Processor::read_string(int max, zchar *buffer) {
zchar key;
buffer[0] = 0;
do {
key = stream_read_input(max, buffer, 0, 0, false, false);
} while (key != ZC_RETURN);
}
bool Processor::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;
}
void Processor::z_make_menu() {
// This opcode was only used for the Macintosh version of Journey.
// It controls menus with numbers greater than 2 (menus 0, 1 and 2
// are system menus).
branch(false);
}
int Processor::read_number() {
zchar buffer[6];
int value = 0;
int i;
read_string(5, buffer);
for (i = 0; buffer[i] != 0; i++)
if (buffer[i] >= '0' && buffer[i] <= '9')
value = 10 * value + buffer[i] - '0';
return value;
}
void Processor::z_read() {
zchar buffer[INPUT_BUFFER_SIZE];
zword addr;
zchar key;
zbyte max, size;
zbyte c;
int i;
// Supply default arguments
if (zargc < 3)
zargs[2] = 0;
// Get maximum input size
addr = zargs[0];
LOW_BYTE(addr, max);
if (h_version <= V4)
max--;
if (max >= INPUT_BUFFER_SIZE)
max = INPUT_BUFFER_SIZE - 1;
// Get initial input size
if (h_version >= V5) {
addr++;
LOW_BYTE(addr, size);
} else {
size = 0;
}
// Copy initial input to local buffer
for (i = 0; i < size; i++) {
addr++;
LOW_BYTE(addr, c);
buffer[i] = translate_from_zscii(c);
}
buffer[i] = 0;
// Draw status line for V1 to V3 games
if (h_version <= V3)
z_show_status();
// Read input from current input stream
key = stream_read_input(
max, buffer, // buffer and size
zargs[2], // timeout value
zargs[3], // timeout routine
false, // enable hot keys
h_version == V6 // no script in V6
);
if (key == ZC_BAD)
return;
// Perform save_undo for V1 to V4 games
if (h_version <= V4)
save_undo();
// Copy local buffer back to dynamic memory
for (i = 0; buffer[i] != 0; i++) {
if (key == ZC_RETURN) {
buffer[i] = unicode_tolower (buffer[i]);
}
storeb((zword)(zargs[0] + ((h_version <= V4) ? 1 : 2) + i), translate_to_zscii(buffer[i]));
}
// Add null character (V1-V4) or write input length into 2nd byte
if (h_version <= V4)
storeb((zword)(zargs[0] + 1 + i), 0);
else
storeb((zword)(zargs[0] + 1), i);
// Tokenise line if a token buffer is present
if (key == ZC_RETURN && zargs[1] != 0)
tokenise_line (zargs[0], zargs[1], 0, false);
// Store key
if (h_version >= V5)
store(translate_to_zscii(key));
}
void Processor::z_read_char() {
zchar key;
// Supply default arguments
if (zargc < 2)
zargs[1] = 0;
// Read input from the current input stream
key = stream_read_key(
zargs[1], // timeout value
zargs[2], // timeout routine
false // enable hot keys
);
if (key == ZC_BAD)
return;
// Store key
store(translate_to_zscii(key));
}
void Processor::z_read_mouse() {
zword btn;
// Read the mouse position, the last menu click and which buttons are down
btn = os_read_mouse();
hx_mouse_y = mouse_y;
hx_mouse_x = mouse_x;
storew((zword)(zargs[0] + 0), hx_mouse_y);
storew((zword)(zargs[0] + 2), hx_mouse_x);
storew((zword)(zargs[0] + 4), btn); // mouse button bits
storew((zword)(zargs[0] + 6), menu_selected); // menu selection
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,104 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
namespace Glk {
namespace ZCode {
void Processor::z_add() {
store((zword)((short)zargs[0] + (short)zargs[1]));
}
void Processor::z_and() {
store((zword)(zargs[0] & zargs[1]));
}
void Processor::z_art_shift() {
if ((short)zargs[1] > 0)
store((zword)((short)zargs[0] << (short)zargs[1]));
else
store((zword)((short)zargs[0] >> - (short)zargs[1]));
}
void Processor::z_div() {
if (zargs[1] == 0)
runtimeError(ERR_DIV_ZERO);
store((zword)((short)zargs[0] / (short)zargs[1]));
}
void Processor::z_je() {
branch(
zargc > 1 && (zargs[0] == zargs[1] || (
zargc > 2 && (zargs[0] == zargs[2] || (
zargc > 3 && (zargs[0] == zargs[3])))))
);
}
void Processor::z_jg() {
branch((short)zargs[0] > (short)zargs[1]);
}
void Processor::z_jl() {
branch((short)zargs[0] < (short)zargs[1]);
}
void Processor::z_jz() {
branch((short)zargs[0] == 0);
}
void Processor::z_log_shift() {
if ((short)zargs[1] > 0)
store((zword)(zargs[0] << (short)zargs[1]));
else
store((zword)(zargs[0] >> - (short)zargs[1]));
}
void Processor::z_mod() {
if (zargs[1] == 0)
runtimeError(ERR_DIV_ZERO);
store((zword)((short)zargs[0] % (short)zargs[1]));
}
void Processor::z_mul() {
store((zword)((short)zargs[0] * (short)zargs[1]));
}
void Processor::z_not() {
store((zword)~zargs[0]);
}
void Processor::z_or() {
store((zword)(zargs[0] | zargs[1]));
}
void Processor::z_sub() {
store((zword)((short)zargs[0] - (short)zargs[1]));
}
void Processor::z_test() {
branch((zargs[0] & zargs[1]) == zargs[1]);
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,217 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
namespace Glk {
namespace ZCode {
void Processor::flagsChanged(zbyte value) {
if (value & SCRIPTING_FLAG) {
if (!ostream_script)
script_open();
} else {
if (ostream_script)
script_close();
}
}
int Processor::save_undo() {
long diff_size;
zword stack_size;
undo_t *p;
if (_undo_slots == 0)
// undo feature unavailable
return -1;
// save undo possible
while (last_undo != curr_undo) {
p = last_undo;
last_undo = last_undo->prev;
delete p;
undo_count--;
}
if (last_undo)
last_undo->next = nullptr;
else
first_undo = nullptr;
if (undo_count == _undo_slots)
free_undo(1);
diff_size = mem_diff(zmp, prev_zmp, h_dynamic_size, undo_diff);
stack_size = _stack + STACK_SIZE - _sp;
do {
p = (undo_t *) malloc(sizeof(undo_t) + diff_size + stack_size * sizeof(*_sp));
if (p == nullptr)
free_undo(1);
} while (!p && undo_count);
if (p == nullptr)
return -1;
GET_PC(p->pc);
p->frame_count = _frameCount;
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 = nullptr;
first_undo = p;
} else {
last_undo->next = p;
p->prev = last_undo;
}
p->next = nullptr;
curr_undo = last_undo = p;
undo_count++;
return 1;
}
int Processor::restore_undo(void) {
if (_undo_slots == 0)
// undo feature unavailable
return -1;
if (curr_undo == nullptr)
// 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;
_frameCount = 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;
}
/**
* TOR: glkify -- this is for V6 only
*/
static zword get_max_width(zword win) { return 80; }
void Processor::memory_open(zword table, zword xsize, bool buffering) {
if (_redirect.size() < MAX_NESTING) {
if (!buffering)
xsize = 0xffff;
if (buffering && (short)xsize <= 0)
xsize = get_max_width((zword)(-(short)xsize));
storew(table, 0);
_redirect.push(Redirect(xsize, table));
ostream_memory = true;
} else {
runtimeError(ERR_STR3_NESTING);
}
}
void Processor::memory_new_line() {
zword size;
zword addr;
Redirect &r = _redirect.top();
r._total += r._width;
r._width = 0;
addr = r._table;
LOW_WORD(addr, size);
addr += 2;
if (r._xSize != 0xffff) {
r._table = addr + size;
size = 0;
} else {
storeb((zword)(addr + (size++)), 13);
}
storew(r._table, size);
}
void Processor::memory_word(const zchar *s) {
zword size;
zword addr;
zchar c;
Redirect &r = _redirect.top();
if (h_version == V6) {
int width = os_string_width(s);
if (r._xSize != 0xffff) {
if (r._width + width > r._xSize) {
if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP)
width = os_string_width(++s);
memory_new_line();
}
}
r._width += width;
}
addr = r._table;
LOW_WORD(addr, size);
addr += 2;
while ((c = *s++) != 0)
storeb((zword)(addr + (size++)), translate_to_zscii(c));
storew(r._table, size);
}
void Processor::memory_close(void) {
if (!_redirect.empty()) {
Redirect &r = _redirect.top();
if (r._xSize != 0xffff)
memory_new_line();
if (h_version == V6) {
h_line_width = (r._xSize != 0xffff) ? r._total : r._width;
SET_WORD(H_LINE_WIDTH, h_line_width);
}
_redirect.pop();
if (_redirect.empty())
ostream_memory = false;
}
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,731 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
namespace Glk {
namespace ZCode {
#define MAX_OBJECT 2000
enum O1 {
O1_PARENT = 4,
O1_SIBLING = 5,
O1_CHILD = 6,
O1_PROPERTY_OFFSET = 7,
O1_SIZE = 9
};
enum O4 {
O4_PARENT = 6,
O4_SIBLING = 8,
O4_CHILD = 10,
O4_PROPERTY_OFFSET = 12,
O4_SIZE = 14
};
zword Processor::object_address(zword obj) {
// Check object number
if (obj > ((h_version <= V3) ? 255 : MAX_OBJECT)) {
print_string("@Attempt to address illegal object ");
print_num(obj);
print_string(". This is normally fatal.");
new_line();
runtimeError(ERR_ILL_OBJ);
}
// Return object address
if (h_version <= V3)
return h_objects + ((obj - 1) * O1_SIZE + 62);
else
return h_objects + ((obj - 1) * O4_SIZE + 126);
}
zword Processor::object_name(zword object) {
zword obj_addr;
zword name_addr;
obj_addr = object_address(object);
// The object name address is found at the start of the properties
if (h_version <= V3)
obj_addr += O1_PROPERTY_OFFSET;
else
obj_addr += O4_PROPERTY_OFFSET;
LOW_WORD(obj_addr, name_addr);
return name_addr;
}
zword Processor::first_property(zword obj) {
zword prop_addr;
zbyte size;
// Fetch address of object name
prop_addr = object_name (obj);
// Get length of object name
LOW_BYTE(prop_addr, size);
// Add name length to pointer
return prop_addr + 1 + 2 * size;
}
zword Processor::next_property(zword prop_addr) {
zbyte value;
// Load the current property id
LOW_BYTE(prop_addr, value);
prop_addr++;
// Calculate the length of this property
if (h_version <= V3)
value >>= 5;
else if (!(value & 0x80))
value >>= 6;
else {
LOW_BYTE(prop_addr, value);
value &= 0x3f;
if (value == 0)
// demanded by Spec 1.0
value = 64;
}
// Add property length to current property pointer
return prop_addr + value + 1;
}
void Processor::unlink_object(zword object) {
zword obj_addr;
zword parent_addr;
zword sibling_addr;
if (object == 0) {
runtimeError(ERR_REMOVE_OBJECT_0);
return;
}
obj_addr = object_address(object);
if (h_version <= V3) {
zbyte parent;
zbyte younger_sibling;
zbyte older_sibling;
zbyte zero = 0;
// Get parent of object, and return if no parent
obj_addr += O1_PARENT;
LOW_BYTE(obj_addr, parent);
if (!parent)
return;
// Get (older) sibling of object and set both parent and sibling pointers to 0
SET_BYTE(obj_addr, zero);
obj_addr += O1_SIBLING - O1_PARENT;
LOW_BYTE(obj_addr, older_sibling);
SET_BYTE(obj_addr, zero);
// Get first child of parent (the youngest sibling of the object)
parent_addr = object_address(parent) + O1_CHILD;
LOW_BYTE(parent_addr, younger_sibling);
// Remove object from the list of siblings
if (younger_sibling == object)
SET_BYTE(parent_addr, older_sibling);
else {
do {
sibling_addr = object_address(younger_sibling) + O1_SIBLING;
LOW_BYTE(sibling_addr, younger_sibling);
} while (younger_sibling != object);
SET_BYTE(sibling_addr, older_sibling);
}
} else {
zword parent;
zword younger_sibling;
zword older_sibling;
zword zero = 0;
// Get parent of object, and return if no parent
obj_addr += O4_PARENT;
LOW_WORD(obj_addr, parent);
if (!parent)
return;
// Get (older) sibling of object and set both parent and sibling pointers to 0
SET_WORD(obj_addr, zero);
obj_addr += O4_SIBLING - O4_PARENT;
LOW_WORD(obj_addr, older_sibling);
SET_WORD(obj_addr, zero);
// Get first child of parent (the youngest sibling of the object)
parent_addr = object_address(parent) + O4_CHILD;
LOW_WORD(parent_addr, younger_sibling);
// Remove object from the list of siblings
if (younger_sibling == object) {
SET_WORD(parent_addr, older_sibling);
} else {
do {
sibling_addr = object_address(younger_sibling) + O4_SIBLING;
LOW_WORD(sibling_addr, younger_sibling);
} while (younger_sibling != object);
SET_WORD(sibling_addr, older_sibling);
}
}
}
void Processor::z_clear_attr() {
zword obj_addr;
zbyte value;
if (_storyId == SHERLOCK)
if (zargs[1] == 48)
return;
if (zargs[1] > ((h_version <= V3) ? 31 : 47))
runtimeError(ERR_ILL_ATTR);
// If we are monitoring attribute assignment display a short note
if (_attribute_assignment) {
stream_mssg_on();
print_string("@clear_attr ");
print_object(zargs[0]);
print_string(" ");
print_num(zargs[1]);
stream_mssg_off();
}
if (zargs[0] == 0) {
runtimeError(ERR_CLEAR_ATTR_0);
return;
}
// Get attribute address
obj_addr = object_address(zargs[0]) + zargs[1] / 8;
// Clear attribute bit
LOW_BYTE(obj_addr, value);
value &= ~(0x80 >> (zargs[1] & 7));
SET_BYTE(obj_addr, value);
}
void Processor::z_jin() {
zword obj_addr;
// If we are monitoring object locating display a short note
if (_object_locating) {
stream_mssg_on();
print_string("@jin ");
print_object(zargs[0]);
print_string(" ");
print_object(zargs[1]);
stream_mssg_off();
}
if (zargs[0] == 0) {
runtimeError(ERR_JIN_0);
branch(0 == zargs[1]);
return;
}
obj_addr = object_address(zargs[0]);
if (h_version <= V3) {
zbyte parent;
// Get parent id from object
obj_addr += O1_PARENT;
LOW_BYTE(obj_addr, parent);
// Branch if the parent is obj2
branch(parent == zargs[1]);
} else {
zword parent;
// Get parent id from object
obj_addr += O4_PARENT;
LOW_WORD(obj_addr, parent);
// Branch if the parent is obj2
branch(parent == zargs[1]);
}
}
void Processor::z_get_child() {
zword obj_addr;
// If we are monitoring object locating display a short note
if (_object_locating) {
stream_mssg_on();
print_string("@get_child ");
print_object(zargs[0]);
stream_mssg_off();
}
if (zargs[0] == 0) {
runtimeError(ERR_GET_CHILD_0);
store(0);
branch(false);
return;
}
obj_addr = object_address(zargs[0]);
if (h_version <= V3) {
zbyte child;
// Get child id from object
obj_addr += O1_CHILD;
LOW_BYTE(obj_addr, child);
// Store child id and branch
store(child);
branch(child);
} else {
zword child;
// Get child id from object
obj_addr += O4_CHILD;
LOW_WORD(obj_addr, child);
// Store child id and branch
store(child);
branch(child);
}
}
void Processor::z_get_next_prop() {
zword prop_addr;
zbyte value;
zbyte mask;
if (zargs[0] == 0) {
runtimeError(ERR_GET_NEXT_PROP_0);
store(0);
return;
}
// Property id is in bottom five (six) bits
mask = (h_version <= V3) ? 0x1f : 0x3f;
// Load address of first property
prop_addr = first_property(zargs[0]);
if (zargs[1] != 0) {
// Scan down the property list
do {
LOW_BYTE(prop_addr, value);
prop_addr = next_property(prop_addr);
} while ((value & mask) > zargs[1]);
// Exit if the property does not exist
if ((value & mask) != zargs[1])
runtimeError(ERR_NO_PROP);
}
// Return the property id
LOW_BYTE(prop_addr, value);
store((zword)(value & mask));
}
void Processor::z_get_parent() {
zword obj_addr;
// If we are monitoring object locating display a short note
if (_object_locating) {
stream_mssg_on();
print_string("@get_parent ");
print_object(zargs[0]);
stream_mssg_off();
}
if (zargs[0] == 0) {
runtimeError(ERR_GET_PARENT_0);
store(0);
return;
}
obj_addr = object_address(zargs[0]);
if (h_version <= V3) {
zbyte parent;
// Get parent id from object
obj_addr += O1_PARENT;
LOW_BYTE(obj_addr, parent);
// Store parent
store(parent);
} else {
zword parent;
// Get parent id from object
obj_addr += O4_PARENT;
LOW_WORD(obj_addr, parent);
// Store parent
store(parent);
}
}
void Processor::z_get_prop() {
zword prop_addr;
zword wprop_val;
zbyte bprop_val;
zbyte value;
zbyte mask;
if (zargs[0] == 0) {
runtimeError(ERR_GET_PROP_0);
store(0);
return;
}
// Property id is in bottom five (six) bits
mask = (h_version <= V3) ? 0x1f : 0x3f;
// Load address of first property
prop_addr = first_property(zargs[0]);
// Scan down the property list
for (;;) {
LOW_BYTE(prop_addr, value);
if ((value & mask) <= zargs[1])
break;
prop_addr = next_property(prop_addr);
}
if ((value & mask) == zargs[1]) {
// property found
// Load property(byte or word sized)
prop_addr++;
if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) {
LOW_BYTE(prop_addr, bprop_val);
wprop_val = bprop_val;
} else {
LOW_WORD(prop_addr, wprop_val);
}
} else {
// property not found
// Load default value
prop_addr = h_objects + 2 * (zargs[1] - 1);
LOW_WORD(prop_addr, wprop_val);
}
// Store the property value
store(wprop_val);
}
void Processor::z_get_prop_addr() {
zword prop_addr;
zbyte value;
zbyte mask;
if (zargs[0] == 0) {
runtimeError(ERR_GET_PROP_ADDR_0);
store(0);
return;
}
if (_storyId == BEYOND_ZORK)
if (zargs[0] > MAX_OBJECT)
{ store(0); return; }
// Property id is in bottom five (six) bits
mask = (h_version <= V3) ? 0x1f : 0x3f;
// Load address of first property
prop_addr = first_property(zargs[0]);
// Scan down the property list
for (;;) {
LOW_BYTE(prop_addr, value);
if ((value & mask) <= zargs[1])
break;
prop_addr = next_property(prop_addr);
}
// Calculate the property address or return zero
if ((value & mask) == zargs[1]) {
if (h_version >= V4 && (value & 0x80))
prop_addr++;
store((zword)(prop_addr + 1));
} else {
store(0);
}
}
void Processor::z_get_prop_len() {
zword addr;
zbyte value;
// Back up the property pointer to the property id
addr = zargs[0] - 1;
LOW_BYTE(addr, value);
// Calculate length of property
if (h_version <= V3)
value = (value >> 5) + 1;
else if (!(value & 0x80))
value = (value >> 6) + 1;
else {
value &= 0x3f;
if (value == 0)
value = 64; // demanded by Spec 1.0
}
// Store length of property
store(value);
}
void Processor::z_get_sibling() {
zword obj_addr;
if (zargs[0] == 0) {
runtimeError(ERR_GET_SIBLING_0);
store(0);
branch(false);
return;
}
obj_addr = object_address(zargs[0]);
if (h_version <= V3) {
zbyte sibling;
// Get sibling id from object
obj_addr += O1_SIBLING;
LOW_BYTE(obj_addr, sibling);
// Store sibling and branch
store(sibling);
branch(sibling);
} else {
zword sibling;
// Get sibling id from object
obj_addr += O4_SIBLING;
LOW_WORD(obj_addr, sibling);
// Store sibling and branch
store(sibling);
branch(sibling);
}
}
void Processor::z_insert_obj() {
zword obj1 = zargs[0];
zword obj2 = zargs[1];
zword obj1_addr;
zword obj2_addr;
// If we are monitoring object movements display a short note
if (_object_movement) {
stream_mssg_on();
print_string("@move_obj ");
print_object(obj1);
print_string(" ");
print_object(obj2);
stream_mssg_off();
}
if (obj1 == 0) {
runtimeError(ERR_MOVE_OBJECT_0);
return;
}
if (obj2 == 0) {
runtimeError(ERR_MOVE_OBJECT_TO_0);
return;
}
// Get addresses of both objects
obj1_addr = object_address(obj1);
obj2_addr = object_address(obj2);
// Remove object 1 from current parent
unlink_object(obj1);
// Make object 1 first child of object 2
if (h_version <= V3) {
zbyte child;
obj1_addr += O1_PARENT;
SET_BYTE(obj1_addr, obj2);
obj2_addr += O1_CHILD;
LOW_BYTE(obj2_addr, child);
SET_BYTE(obj2_addr, obj1);
obj1_addr += O1_SIBLING - O1_PARENT;
SET_BYTE(obj1_addr, child);
} else {
zword child;
obj1_addr += O4_PARENT;
SET_WORD(obj1_addr, obj2);
obj2_addr += O4_CHILD;
LOW_WORD(obj2_addr, child);
SET_WORD(obj2_addr, obj1);
obj1_addr += O4_SIBLING - O4_PARENT;
SET_WORD(obj1_addr, child);
}
}
void Processor::z_put_prop() {
zword prop_addr;
zword value;
zbyte mask;
if (zargs[0] == 0) {
runtimeError(ERR_PUT_PROP_0);
return;
}
// Property id is in bottom five or six bits
mask = (h_version <= V3) ? 0x1f : 0x3f;
// Load address of first property
prop_addr = first_property(zargs[0]);
// Scan down the property list
for (;;) {
LOW_BYTE(prop_addr, value);
if ((value & mask) <= zargs[1])
break;
prop_addr = next_property(prop_addr);
}
// Exit if the property does not exist
if ((value & mask) != zargs[1])
runtimeError(ERR_NO_PROP);
// Store the new property value (byte or word sized)
prop_addr++;
if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) {
zbyte v = zargs[2];
SET_BYTE(prop_addr, v);
} else {
zword v = zargs[2];
SET_WORD(prop_addr, v);
}
}
void Processor::z_remove_obj() {
// If we are monitoring object movements display a short note
if (_object_movement) {
stream_mssg_on();
print_string("@remove_obj ");
print_object(zargs[0]);
stream_mssg_off();
}
// Call unlink_object to do the job
unlink_object(zargs[0]);
}
void Processor::z_set_attr() {
zword obj_addr;
zbyte value;
if (_storyId == SHERLOCK)
if (zargs[1] == 48)
return;
if (zargs[1] > ((h_version <= V3) ? 31 : 47))
runtimeError(ERR_ILL_ATTR);
// If we are monitoring attribute assignment display a short note
if (_attribute_assignment) {
stream_mssg_on();
print_string("@set_attr ");
print_object(zargs[0]);
print_string(" ");
print_num(zargs[1]);
stream_mssg_off();
}
if (zargs[0] == 0) {
runtimeError(ERR_SET_ATTR_0);
return;
}
// Get attribute address
obj_addr = object_address(zargs[0]) + zargs[1] / 8;
// Load attribute byte
LOW_BYTE(obj_addr, value);
// Set attribute bit
value |= 0x80 >> (zargs[1] & 7);
// Store attribute byte
SET_BYTE(obj_addr, value);
}
void Processor::z_test_attr() {
zword obj_addr;
zbyte value;
if (zargs[1] > ((h_version <= V3) ? 31 : 47))
runtimeError(ERR_ILL_ATTR);
// If we are monitoring attribute testing display a short note
if (_attribute_testing) {
stream_mssg_on();
print_string("@test_attr ");
print_object(zargs[0]);
print_string(" ");
print_num(zargs[1]);
stream_mssg_off();
}
if (zargs[0] == 0) {
runtimeError(ERR_TEST_ATTR_0);
branch(false);
return;
}
// Get attribute address
obj_addr = object_address(zargs[0]) + zargs[1] / 8;
// Load attribute byte
LOW_BYTE(obj_addr, value);
// Test attribute
branch(value & (0x80 >> (zargs[1] & 7)));
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,506 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
#include "glk/zcode/zcode.h"
#include "glk/conf.h"
#include "glk/events.h"
namespace Glk {
namespace ZCode {
void Processor::screen_mssg_on() {
Window &w = _wp.currWin();
if (w == _wp._lower) {
w._oldStyle = w._currStyle;
glk_set_style(style_Preformatted);
glk_put_string("\n ");
}
}
void Processor::screen_mssg_off() {
Window &w = _wp.currWin();
if (w == _wp._lower) {
glk_put_char('\n');
w.setStyle(0);
w.setStyle(w._oldStyle);
}
}
static const uint32 zchar_runes[] = {
// This mapping is based on the Amiga font in the Z-Machine
// specification, with some liberties taken.
0x16AA, // RUNIC LETTER AC A
0x16D2, // RUNIC LETTER BERKANAN BEORC BJARKAN B
0x16C7, // RUNIC LETTER IWAZ EOH
0x16D1, // RUNIC LETTER DAGAZ DAEG D
0x16D6, // RUNIC LETTER EHWAZ EH E
0x16A0, // RUNIC LETTER FEHU FEOH FE F
0x16B7, // RUNIC LETTER GEBO GYFU G
0x16BB, // RUNIC LETTER HAEGL H
0x16C1, // RUNIC LETTER ISAZ IS ISS I
0x16C4, // RUNIC LETTER GER
0x16E6, // RUNIC LETTER LONG-BRANCH-YR
0x16DA, // RUNIC LETTER LAUKAZ LAGU LOGR L
0x16D7, // RUNIC LETTER MANNAZ MAN M
0x16BE, // RUNIC LETTER NAUDIZ NYD NAUD N
0x16A9, // RUNIC LETTER OS O
0x16C8, // RUNIC LETTER PERTHO PEORTH P
0x16B3, // RUNIC LETTER CEN
0x16B1, // RUNIC LETTER RAIDO RAD REID R
0x16CB, // RUNIC LETTER SIGEL LONG-BRANCH-SOL S
0x16CF, // RUNIC LETTER TIWAZ TIR TYR T
0x16A2, // RUNIC LETTER URUZ UR U
0x16E0, // RUNIC LETTER EAR
0x16B9, // RUNIC LETTER WUNJO WYNN W
0x16C9, // RUNIC LETTER ALGIZ EOLHX
0x16A5, // RUNIC LETTER W
0x16DF // RUNIC LETTER OTHALAN ETHEL O
};
uint32 Processor::zchar_to_unicode_rune(zchar c) {
// There are only runic characters for a-z. Some versions of Beyond
// Zork will render the conversation between Prince Foo and the black
// rider in runic script, even though it contained upper case letters.
// This produced an ugly mix of runes and map-drawing characters, etc.
// which is probably why it was removed in later versions.
//
// Still, it's probably a good idea to convert the upper case letters
// to lower case to get an appropriate rune. As far as I can tell, the
// upper case letters are all used for drawing maps and progress bars.
// I don't think they're ever intended for the lower window.
//
// Apart from the runes, the arrow glyphs could perhaps also be
// sensibly converted to Unicode?
if (c >= 'a' && c <= 'z')
return zchar_runes[c - 'a'];
else if (c >= 'A' && c <= 'Z')
return zchar_runes[c - 'A'];
else
return 0;
}
void Processor::screen_char(zchar c) {
Window &w = _wp.currWin();
w.ensureTextWindow();
if (h_version == V6)
_wp.showTextWindows();
if (gos_linepending && (w == gos_linewin)) {
gos_cancel_pending_line();
if (_wp.currWin() == _wp._upper) {
_wp._upper.setCursor(Point(1, _wp._upper[Y_CURSOR] + 1));
}
if (c == '\n')
return;
}
// check fixed flag in header, game can change it at whim
int forcefix = ((h_flags & FIXED_FONT_FLAG) != 0);
int curfix = ((w._currStyle & FIXED_WIDTH_STYLE) != 0);
if (forcefix && !curfix) {
w.setStyle();
fixforced = true;
} else if (!forcefix && fixforced) {
w.setStyle();
fixforced = false;
}
if (_wp._upper && _wp.currWin() == _wp._upper) {
if (c == '\n' || c == ZC_RETURN) {
glk_put_char('\n');
_wp._upper.setCursor(Point(1, _wp._upper[Y_CURSOR] + 1));
} else {
int curx = _wp._upper[X_CURSOR], cury = _wp._upper[Y_CURSOR];
if (cury == 1) {
if (curx <= (int)((sizeof statusline / sizeof(zchar)) - 1)) {
statusline[curx - 1] = c;
statusline[curx] = 0;
}
if (curx < h_screen_cols) {
glk_put_char_uni(c);
} else if (curx == h_screen_cols) {
glk_put_char_uni(c);
glk_window_move_cursor(_wp.currWin(), curx-1, cury-1);
} else {
smartstatusline();
}
curx++;
} else {
if (curx < h_screen_cols) {
glk_put_char_uni(c);
} else if (curx == (h_screen_cols)) {
glk_put_char_uni(c);
glk_window_move_cursor(_wp.currWin(), curx-1, cury-1);
}
curx++;
}
}
} else if (w == _wp._lower) {
if (c == ZC_RETURN)
glk_put_char('\n');
else {
if (w._currFont == GRAPHICS_FONT) {
uint32 runic_char = zchar_to_unicode_rune(c);
if (runic_char != 0) {
glk_set_style(style_User2);
glk_put_char_uni(runic_char);
glk_set_style(style_User1);
} else
glk_put_char_uni(c);
} else
glk_put_char_uni(c);
}
}
}
void Processor::screen_new_line() {
screen_char('\n');
}
void Processor::screen_word(const zchar *s) {
zchar c;
while ((c = *s++) != 0) {
if (c == ZC_NEW_FONT)
s++;
else if (c == ZC_NEW_STYLE)
s++;
else
screen_char(c);
}
}
void Processor::erase_screen(zword win) {
if ((short)win == -1) {
if (_wp._upper) {
_wp._upper.updateColors();
_wp._upper.clear();
}
_wp._lower.clear();
split_window(0);
_wp.setWindow(0);
}
}
void Processor::erase_window(zword win) {
if (h_version == V6 && win != _wp._cwin && h_interpreter_number != INTERP_AMIGA)
_wp[win].updateColors();
_wp[win].clear();
if (h_version == V6 && win != _wp._cwin && h_interpreter_number != INTERP_AMIGA)
_wp[_wp._cwin].updateColors();
}
void Processor::z_buffer_mode() {
// No implementation
}
void Processor::z_buffer_screen() {
store(0);
}
void Processor::z_erase_line() {
int i;
flush_buffer();
if (_wp._upper && _wp.currWin() == _wp._upper) {
int curx = _wp[_wp._cwin][X_CURSOR], cury = _wp[_wp._cwin][Y_CURSOR];
for (i = 0; i < h_screen_cols + 1 - curx; i++)
glk_put_char(' ');
_wp._upper.setCursor(Point(curx, cury));
}
}
void Processor::z_erase_window() {
short w = (short)zargs[0];
flush_buffer();
if (w == -1 || w == -2)
erase_screen(w);
else
erase_window(winarg0());
}
void Processor::z_get_cursor() {
zword y, x;
flush_buffer();
x = _wp[_wp._cwin][X_CURSOR];
y = _wp[_wp._cwin][Y_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);
}
void Processor::z_print_table() {
zword addr = zargs[0];
int curx = _wp[_wp._cwin][X_CURSOR], cury = _wp[_wp._cwin][Y_CURSOR];
zword xs = curx;
int i, j;
zbyte c;
// Supply default arguments
if (zargc < 3)
zargs[2] = 1;
if (zargc < 4)
zargs[3] = 0;
// Write text in width x height rectangle
for (i = 0; i < zargs[2]; i++, curx = xs, cury++) {
_wp[_wp._cwin].setCursor(Point(xs, cury));
for (j = 0; j < zargs[1]; j++) {
LOW_BYTE(addr, c);
addr++;
print_char(c);
}
addr += zargs[3];
}
}
void Processor::z_set_true_colour() {
int zfore = zargs[0];
int zback = zargs[1];
if (!(zfore < 0))
zfore = zRGB(zargs[0]);
if (!(zback < 0))
zback = zRGB(zargs[1]);
_wp[_wp._cwin].updateColors(zfore, zback);
}
void Processor::z_set_colour() {
int fg = (short)zargs[0];
int bg = (short)zargs[1];
zword win = (h_version == V6) ? winarg2() : 0;
if (win == 1 && h_version == V6)
bg = zcolor_Transparent;
flush_buffer();
if (fg == -1)
// Get color at cursor
fg = os_peek_color();
if (bg == -1)
bg = zcolor_Transparent;
if (fg == 0)
// keep current colour
fg = _wp[win][TRUE_FG_COLOR];
if (bg == 0)
bg = _wp[win][TRUE_BG_COLOR];
if (fg == 1)
fg = h_default_foreground;
if (bg == 1)
bg = h_default_background;
if (fg >= 0 && fg < zcolor_NUMCOLORS)
fg = zcolors[fg];
if (bg >= 0 && bg < zcolor_NUMCOLORS)
bg = zcolors[bg];
if (h_version == V6 && h_interpreter_number == INTERP_AMIGA) {
// Changing colours of window 0 affects the entire screen
if (win == 0) {
for (int i = 1; i < 8; ++i) {
int bg2 = _wp[i][TRUE_BG_COLOR];
int fg2 = _wp[i][TRUE_FG_COLOR];
if (bg2 < 16)
bg2 = (bg2 == (int)_wp[0][TRUE_BG_COLOR]) ? fg : bg;
if (fg2 < 16)
fg2 = (fg2 == (int)_wp[0][TRUE_FG_COLOR]) ? fg : bg;
_wp[i][TRUE_FG_COLOR] = fg2;
_wp[i][TRUE_BG_COLOR] = bg2;
}
}
}
_wp[win][TRUE_FG_COLOR] = fg;
_wp[win][TRUE_BG_COLOR] = bg;
if (win == _wp._cwin || h_version != V6)
_wp.currWin().updateColors(fg, bg);
}
void Processor::z_set_font() {
zword font = zargs[0];
store(_wp.currWin().setFont(font));
}
void Processor::z_set_cursor() {
int x = (int16)zargs[1], y = (int16)zargs[0];
int win = (h_version == V6) ? winarg2() : _wp._cwin;
if (zargc < 3)
zargs[2] = (zword)-3;
flush_buffer();
_wp[win].setCursor(Point(x, y));
if (_wp.currWin() == _wp._upper && _wp[win][Y_CURSOR] > (uint)mach_status_ht) {
mach_status_ht = _wp[win][Y_CURSOR];
reset_status_ht();
}
}
void Processor::z_set_text_style() {
_wp[_wp._cwin].setStyle(zargs[0]);
}
void Processor::z_set_window() {
_wp.setWindow(zargs[0]);
if (_wp._cwin == 0)
enable_scripting = true;
else
enable_scripting = false;
zargs[0] = 0xf000; // tickle tickle!
z_set_text_style();
}
void Processor::pad_status_line(int column) {
int curx = _wp._upper[X_CURSOR];
int spaces = (h_screen_cols + 1 - curx) - column;
while (spaces-- > 0)
print_char(' ');
}
void Processor::z_show_status() {
zword global0;
zword global1;
zword global2;
zword addr;
bool brief = false;
if (!_wp._upper)
return;
// One V5 game (Wishbringer Solid Gold) contains this opcode by accident,
// so just return if the version number does not fit
if (h_version >= V4)
return;
// Read all relevant global variables from the memory of the Z-machine
// into local variables
addr = h_globals;
LOW_WORD(addr, global0);
addr += 2;
LOW_WORD(addr, global1);
addr += 2;
LOW_WORD(addr, global2);
// Move to top of the status window, and print in reverse style.
_wp.setWindow(1);
_wp._upper.setReverseVideo(true);
_wp._upper.setCursor(Point(1, 1));
// 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
_wp.setWindow(0);
}
void Processor::z_split_window() {
split_window(zargs[0]);
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,625 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
#include "glk/zcode/quetzal.h"
namespace Glk {
namespace ZCode {
zchar Processor::console_read_input(int max, zchar *buf, zword timeout, bool continued) {
return os_read_line(max, buf, timeout, max, continued);
}
zchar Processor::console_read_key(zword timeout) {
return os_read_key(timeout, 0);
}
void Processor::scrollback_char(zchar c) {
if (c == ZC_INDENT)
{ scrollback_char (' '); scrollback_char (' '); scrollback_char (' '); return; }
if (c == ZC_GAP)
{ scrollback_char (' '); scrollback_char (' '); return; }
os_scrollback_char(c);
}
void Processor::scrollback_word(const zchar *s) {
int i;
for (i = 0; s[i] != 0; i++) {
if (s[i] == ZC_NEW_FONT || s[i] == ZC_NEW_STYLE)
i++;
else
scrollback_char(s[i]);
}
}
void Processor::scrollback_write_input(const zchar *buf, zchar key) {
int i;
for (i = 0; buf[i] != 0; i++)
scrollback_char (buf[i]);
if (key == ZC_RETURN)
scrollback_char ('\n');
}
void Processor::scrollback_erase_input(const zchar *buf) {
int width;
int i;
for (i = 0, width = 0; buf[i] != 0; i++)
width++;
os_scrollback_erase(width);
}
void Processor::stream_mssg_on() {
flush_buffer();
if (ostream_screen)
screen_mssg_on();
if (ostream_script && enable_scripting)
script_mssg_on();
message = true;
}
void Processor::stream_mssg_off() {
flush_buffer();
if (ostream_screen)
screen_mssg_off();
if (ostream_script && enable_scripting)
script_mssg_off();
message = false;
}
void Processor::stream_char(zchar c) {
if (ostream_screen)
screen_char(c);
if (ostream_script && enable_scripting)
script_char(c);
if (enable_scripting)
scrollback_char(c);
}
void Processor::stream_word(const zchar *s) {
if (ostream_memory && !message)
memory_word(s);
else {
if (ostream_screen)
screen_word(s);
if (ostream_script && enable_scripting)
script_word(s);
if (enable_scripting)
scrollback_word(s);
}
}
void Processor::stream_new_line() {
if (ostream_memory && !message)
memory_new_line();
else {
if (ostream_screen)
screen_new_line();
if (ostream_script && enable_scripting)
script_new_line();
if (enable_scripting)
os_scrollback_char ('\n');
}
}
zchar Processor::stream_read_key(zword timeout, zword routine, bool hot_keys) {
zchar key = ZC_BAD;
flush_buffer();
// Read key from current input stream
continue_input:
do {
if (istream_replay)
key = replay_read_key();
else
key = console_read_key(timeout);
if (shouldQuit())
return ZC_BAD;
} while (key == ZC_BAD);
// Copy key to the command file
if (ostream_record && !istream_replay)
record_write_key(key);
// Handle timeouts
if (key == ZC_TIME_OUT)
if (direct_call(routine) == 0)
goto continue_input;
// Return key
return key;
}
zchar Processor::stream_read_input(int max, zchar *buf, zword timeout, zword routine,
bool hot_keys, bool no_scripting) {
zchar key = ZC_BAD;
flush_buffer();
// Remove initial input from the transscript file or from the screen
if (ostream_script && enable_scripting && !no_scripting)
script_erase_input(buf);
// Read input line from current input stream
continue_input:
do {
if (istream_replay)
key = replay_read_input(buf);
else
key = console_read_input(max, buf, timeout, key != ZC_BAD);
if (shouldQuit())
return ZC_BAD;
} while (key == ZC_BAD);
// Copy input line to the command file
if (ostream_record && !istream_replay)
record_write_input(buf, key);
// Handle timeouts
if (key == ZC_TIME_OUT)
if (direct_call(routine) == 0)
goto continue_input;
// Copy input line to transscript file or to the screen
if (ostream_script && enable_scripting && !no_scripting)
script_write_input(buf, key);
// Return terminating key
return key;
}
void Processor::script_open() {
h_flags &= ~SCRIPTING_FLAG;
frefid_t fref = glk_fileref_create_by_prompt(fileusage_Transcript,
filemode_WriteAppend);
sfp = glk_stream_open_file(fref, filemode_WriteAppend);
if (sfp != nullptr) {
sfp->setPosition(0, seekmode_End);
h_flags |= SCRIPTING_FLAG;
script_valid = true;
ostream_script = true;
script_width = 0;
} else {
print_string("Cannot open file\n");
}
SET_WORD(H_FLAGS, h_flags);
}
void Processor::script_close() {
h_flags &= ~SCRIPTING_FLAG;
SET_WORD(H_FLAGS, h_flags);
glk_stream_close(sfp);
ostream_script = false;
}
void Processor::script_new_line() {
script_char('\n');
script_width = 0;
}
void Processor::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;
}
sfp->putCharUni(c);
script_width++;
}
void Processor::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 (_script_cols != 0 && script_width + width > _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]);
}
}
void Processor::script_write_input(const zchar *buf, zchar key) {
int width;
int i;
for (i = 0, width = 0; buf[i] != 0; i++)
width++;
if (_script_cols != 0 && script_width + width > _script_cols)
script_new_line();
for (i = 0; buf[i] != 0; i++)
script_char(buf[i]);
if (key == ZC_RETURN)
script_new_line();
}
void Processor::script_erase_input(const zchar *buf) {
int width;
int i;
for (i = 0, width = 0; buf[i] != 0; i++)
width++;
sfp->setPosition(-width, seekmode_Current);
script_width -= width;
}
void Processor::script_mssg_on() {
if (script_width != 0)
script_new_line();
script_char(ZC_INDENT);
}
void Processor::script_mssg_off() {
script_new_line();
}
void Processor::record_open() {
frefid_t fref = glk_fileref_create_by_prompt(fileusage_Transcript, filemode_Write);
if ((rfp = glk_stream_open_file(fref, filemode_Write)) != nullptr)
ostream_record = true;
else
print_string("Cannot open file\n");
}
void Processor::record_close() {
glk_stream_close(rfp);
ostream_record = false;
}
void Processor::record_code(int c, bool force_encoding) {
if (force_encoding || c == '[' || c < 0x20 || c > 0x7e) {
int i;
rfp->putChar('[');
for (i = 10000; i != 0; i /= 10)
if (c >= i || i == 1)
rfp->putChar('0' + (c / i) % 10);
rfp->putChar(']');
} else {
rfp->putChar(c);
}
}
void Processor::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);
}
}
}
void Processor::record_write_key(zchar key) {
record_char(key);
rfp->putChar('\n');
}
void Processor::record_write_input(const zchar *buf, zchar key) {
zchar c;
while ((c = *buf++) != 0)
record_char(c);
record_write_key(key);
}
void Processor::replay_open() {
frefid_t fref = glk_fileref_create_by_prompt(fileusage_Transcript, filemode_Read);
if ((pfp = glk_stream_open_file(fref, filemode_Read)) != nullptr)
istream_replay = true;
else
print_string("Cannot open file\n");
}
void Processor::replay_close() {
glk_stream_close(pfp);
istream_replay = false;
}
int Processor::replay_code() {
int c;
if ((c = pfp->getChar()) == '[') {
int c2;
c = 0;
while ((c2 = pfp->getChar()) != EOF && c2 >= '0' && c2 <= '9')
c = 10 * c + c2 - '0';
return (c2 == ']') ? c : EOF;
} else {
return c;
}
}
zchar Processor::replay_char() {
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;
}
}
pfp->unputBuffer("\n", 1);
return ZC_RETURN;
} else {
return ZC_BAD;
}
}
zchar Processor::replay_read_key() {
zchar key = replay_char();
if (pfp->getChar() != '\n') {
replay_close();
return ZC_BAD;
} else {
return key;
}
}
zchar Processor::replay_read_input(zchar *buf) {
zchar c;
for (;;) {
c = replay_char();
if (c == ZC_BAD || is_terminator(c))
break;
*buf++ = c;
}
*buf = 0;
if (pfp->getChar() != '\n') {
replay_close();
return ZC_BAD;
} else {
return c;
}
}
void Processor::z_input_stream() {
flush_buffer();
if (zargs[0] == 0 && istream_replay)
replay_close();
if (zargs[0] == 1 && !istream_replay)
replay_open();
}
void Processor::z_output_stream() {
flush_buffer();
switch ((short) zargs[0]) {
case 1:
ostream_screen = true;
break;
case -1:
ostream_screen = false;
break;
case 2:
if (!ostream_script)
script_open();
break;
case -2:
if (ostream_script)
script_close();
break;
case 3:
memory_open(zargs[1], zargs[2], zargc >= 3);
break;
case -3:
memory_close();
break;
case 4:
if (!ostream_record)
record_open();
break;
case -4:
if (ostream_record)
record_close();
break;
default:
break;
}
}
void Processor::z_restart() {
flush_buffer();
os_restart_game(RESTART_BEGIN);
seed_random(0);
if (!first_restart) {
story_fp->seek(0);
if (story_fp->read(zmp, h_dynamic_size) != h_dynamic_size)
error("Story file read error");
} else {
first_restart = false;
}
restart_header();
restart_screen();
_sp = _fp = _stack + STACK_SIZE;
_frameCount = 0;
if (h_version != V6 && h_version != V9) {
offset_t pc = (offset_t)h_start_pc;
SET_PC(pc);
} else {
SET_PC(0);
call(h_start_pc, 0, nullptr, 0);
}
os_restart_game(RESTART_END);
}
void Processor::z_save() {
bool success = false;
if (zargc != 0) {
// Open auxiliary file
frefid_t ref = glk_fileref_create_by_prompt(fileusage_Data | fileusage_BinaryMode,
filemode_Write, 0);
if (ref != nullptr) {
// Write data
strid_t f = glk_stream_open_file(ref, filemode_Write);
glk_put_buffer_stream(f, (const char *)zmp + zargs[0], zargs[1]);
glk_stream_close(f);
success = true;
}
} else {
success = saveGame().getCode() == Common::kNoError;
}
if (h_version <= V3)
branch(success);
else
store(success);
}
void Processor::z_restore() {
bool success = false;
if (zargc != 0) {
frefid_t ref = glk_fileref_create_by_prompt(fileusage_Data | fileusage_BinaryMode,
filemode_Read, 0);
if (ref != nullptr) {
// Write data
strid_t f = glk_stream_open_file(ref, filemode_Read);
glk_get_buffer_stream(f, (char *)zmp + zargs[0], zargs[1]);
glk_stream_close(f);
success = true;
}
} else {
success = loadGame().getCode() == Common::kNoError;
}
int result = success ? 2 : -1;
if (h_version <= V3)
branch(result);
else
store(result);
}
void Processor::z_verify() {
zword checksum = 0;
// Sum all bytes in story file except header bytes
story_fp->seek(64);
for (uint i = 64; i < story_size; i++)
checksum += story_fp->readByte();
// Branch if the checksums are equal
branch(checksum == h_checksum);
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,119 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
namespace Glk {
namespace ZCode {
void Processor::z_copy_table() {
zword addr;
zword size = zargs[2];
zbyte value;
int i;
if (zargs[1] == 0) {
// zero table
for (i = 0; i < size; i++)
storeb((zword)(zargs[0] + i), 0);
} else if ((short) size < 0 || zargs[0] > zargs[1]) {
// copy forwards
for (i = 0; i < (((short)size < 0) ? -(short)size : size); i++) {
addr = zargs[0] + i;
LOW_BYTE(addr, value);
storeb((zword)(zargs[1] + i), value);
}
} else {
// copy backwards
for (i = size - 1; i >= 0; i--) {
addr = zargs[0] + i;
LOW_BYTE(addr, value);
storeb((zword)(zargs[1] + i), value);
}
}
}
void Processor::z_loadb() {
zword addr = zargs[0] + zargs[1];
zbyte value;
LOW_BYTE(addr, value);
store(value);
}
void Processor::z_loadw() {
zword addr = zargs[0] + 2 * zargs[1];
zword value;
LOW_WORD(addr, value);
store(value);
}
void Processor::z_scan_table() {
zword addr = zargs[1];
int i;
// Supply default arguments
if (zargc < 4)
zargs[3] = 0x82;
// Scan byte or word array
for (i = 0; i < zargs[2]; i++) {
if (zargs[3] & 0x80) {
// scan word array
zword wvalue;
LOW_WORD(addr, wvalue);
if (wvalue == zargs[0])
goto finished;
} else {
// scan byte array
zbyte bvalue;
LOW_BYTE(addr, bvalue);
if (bvalue == zargs[0])
goto finished;
}
addr += zargs[3] & 0x7f;
}
addr = 0;
finished:
store(addr);
branch(addr);
}
void Processor::z_storeb() {
storeb((zword)(zargs[0] + zargs[1]), zargs[2]);
}
void Processor::z_storew() {
storew((zword)(zargs[0] + 2 * zargs[1]), zargs[2]);
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,909 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
#include "common/ustr.h"
namespace Glk {
namespace ZCode {
zchar Processor::ZSCII_TO_LATIN1[] = {
0x0e4, 0x0f6, 0x0fc, 0x0c4, 0x0d6, 0x0dc, 0x0df, 0x0bb,
0x0ab, 0x0eb, 0x0ef, 0x0ff, 0x0cb, 0x0cf, 0x0e1, 0x0e9,
0x0ed, 0x0f3, 0x0fa, 0x0fd, 0x0c1, 0x0c9, 0x0cd, 0x0d3,
0x0da, 0x0dd, 0x0e0, 0x0e8, 0x0ec, 0x0f2, 0x0f9, 0x0c0,
0x0c8, 0x0cc, 0x0d2, 0x0d9, 0x0e2, 0x0ea, 0x0ee, 0x0f4,
0x0fb, 0x0c2, 0x0ca, 0x0ce, 0x0d4, 0x0db, 0x0e5, 0x0c5,
0x0f8, 0x0d8, 0x0e3, 0x0f1, 0x0f5, 0x0c3, 0x0d1, 0x0d5,
0x0e6, 0x0c6, 0x0e7, 0x0c7, 0x0fe, 0x0f0, 0x0de, 0x0d0,
0x0a3, 0x153, 0x152, 0x0a1, 0x0bf
};
zchar Processor::translate_from_zscii(zbyte c) {
if (c == 0xfc)
return ZC_MENU_CLICK;
if (c == 0xfd)
return ZC_DOUBLE_CLICK;
if (c == 0xfe)
return ZC_SINGLE_CLICK;
if (c >= 0x9b && _storyId != BEYOND_ZORK) {
if (hx_unicode_table != 0) {
// game has its own Unicode table
zbyte N;
LOW_BYTE(hx_unicode_table, N);
if (c - 0x9b < N) {
zword addr = hx_unicode_table + 1 + 2 * (c - 0x9b);
zword unicode;
LOW_WORD(addr, unicode);
if (unicode < 0x20)
return '?';
return unicode;
} else {
return '?';
}
} else {
// game uses standard set
if (c <= 0xdf) {
return ZSCII_TO_LATIN1[c - 0x9b];
} else {
return '?';
}
}
}
return (zchar)c;
}
zbyte Processor::unicode_to_zscii(zchar c) {
int i;
if (c >= ZC_LATIN1_MIN) {
if (hx_unicode_table != 0) {
// game has its own Unicode table
zbyte N;
LOW_BYTE(hx_unicode_table, N);
for (i = 0x9b; i < 0x9b + N; i++) {
zword addr = hx_unicode_table + 1 + 2 * (i - 0x9b);
zword unicode;
LOW_WORD(addr, unicode);
if (c == unicode)
return (zbyte)i;
}
return 0;
} else {
// game uses standard set
for (i = 0x9b; i <= 0xdf; i++)
if (c == ZSCII_TO_LATIN1[i - 0x9b])
return (zbyte)i;
return 0;
}
}
return (zbyte)c;
}
zbyte Processor::translate_to_zscii(zchar c) {
if (c == ZC_SINGLE_CLICK)
return 0xfe;
if (c == ZC_DOUBLE_CLICK)
return 0xfd;
if (c == ZC_MENU_CLICK)
return 0xfc;
if (c == 0)
return 0;
c = unicode_to_zscii(c);
if (c == 0)
c = '?';
return (zbyte)c;
}
zchar Processor::alphabet(int set, int index) {
if (h_version > V1 && set == 2 && index == 1)
// always newline
return '\r';
if (h_alphabet != 0) {
// game uses its own alphabet
zbyte c;
zword addr = h_alphabet + 26 * set + index;
LOW_BYTE(addr, c);
return translate_from_zscii(c);
} else {
// game uses default alphabet
if (set == 0)
return 'a' + index;
else if (set == 1)
return 'A' + index;
else if (h_version == V1)
return " 0123456789.,!?_#'\"/\\<-:()"[index];
else
return " ^0123456789.,!?_#'\"/\\-:()"[index];
}
}
void Processor::find_resolution() {
zword dct = h_dictionary;
zword entry_count;
zbyte sep_count;
zbyte entry_len;
LOW_BYTE(dct, sep_count);
dct += 1 + sep_count; // skip word separators
LOW_BYTE(dct, entry_len);
dct += 1; // skip entry length
LOW_WORD(dct, entry_count);
dct += 2; // get number of entries
if (h_version < V9) {
_resolution = (h_version <= V3) ? 2 : 3;
} else {
zword addr = dct;
zword code;
if (entry_count == 0)
runtimeError(ERR_DICT_LEN);
// check the first word in the dictionary
do {
LOW_WORD(addr, code);
addr += 2;
} while (!(code & 0x8000) && (addr - dct < entry_len + 1));
_resolution = (addr - dct) / 2;
}
if (2 * _resolution > entry_len) {
runtimeError(ERR_DICT_LEN);
}
_decoded = (zchar *)malloc(sizeof(zchar) * (3 * _resolution) + 1);
_encoded = (zchar *)malloc(sizeof(zchar) * _resolution);
}
void Processor::load_string(zword addr, zword length) {
int i = 0;
if (_resolution == 0)
find_resolution();
while (i < 3 * _resolution) {
if (i < length) {
zbyte c;
LOW_BYTE(addr, c);
addr++;
_decoded[i++] = translate_from_zscii(c);
} else {
_decoded[i++] = 0;
}
}
}
void Processor::encode_text(int padding) {
static const zchar again[] = { 'a', 'g', 'a', 'i', 'n', 0, 0, 0, 0 };
static const zchar examine[] = { 'e', 'x', 'a', 'm', 'i', 'n', 'e', 0, 0 };
static const zchar wait[] = { 'w', 'a', 'i', 't', 0, 0, 0, 0, 0 };
zbyte *zchars;
const zchar *ptr;
zchar c;
int i = 0;
if (_resolution == 0) find_resolution();
zchars = new byte[3 * (_resolution + 1)];
ptr = _decoded;
// Expand abbreviations that some old Infocom games lack
if (_expand_abbreviations && (h_version <= V8)) {
if (padding == 0x05 && _decoded[1] == 0) {
switch (_decoded[0]) {
case 'g': ptr = again; break;
case 'x': ptr = examine; break;
case 'z': ptr = wait; break;
default: break;
}
}
}
// Translate string to a sequence of Z-characters
while (i < 3 * _resolution) {
if ((c = *ptr++) != 0) {
int index, set;
zbyte c2;
if (c == ' ') {
zchars[i++] = 0;
continue;
}
// Search character in the alphabet
for (set = 0; set < 3; set++)
for (index = 0; index < 26; index++)
if (c == alphabet(set, index))
goto letter_found;
// Character not found, store its ZSCII value
c2 = translate_to_zscii(c);
zchars[i++] = 5;
zchars[i++] = 6;
zchars[i++] = c2 >> 5;
zchars[i++] = c2 & 0x1f;
continue;
letter_found:
// Character found, store its index
if (set != 0)
zchars[i++] = ((h_version <= V2) ? 1 : 3) + set;
zchars[i++] = index + 6;
} else {
zchars[i++] = padding;
}
}
// Three Z-characters make a 16bit word
for (i = 0; i < _resolution; i++)
_encoded[i] =
(zchars[3 * i + 0] << 10) |
(zchars[3 * i + 1] << 5) |
(zchars[3 * i + 2]);
_encoded[_resolution - 1] |= 0x8000;
delete[] zchars;
}
#define outchar(c) if (st == VOCABULARY) *ptr++=c; else print_char(c)
void Processor::decode_text(enum string_type st, zword addr) {
zchar *ptr = nullptr;
long byte_addr = 0;
zchar c2;
zword code;
zbyte c, prev_c = 0;
int shift_state = 0;
int shift_lock = 0;
int status = 0;
if (_resolution == 0)
find_resolution();
// Calculate the byte address if necessary
if (st == ABBREVIATION)
byte_addr = (long)addr << 1;
else if (st == HIGH_STRING) {
if (h_version <= V3)
byte_addr = (long)addr << 1;
else if (h_version <= V5)
byte_addr = (long)addr << 2;
else if (h_version <= V7)
byte_addr = ((long)addr << 2) + ((long)h_strings_offset << 3);
else if (h_version <= V8)
byte_addr = (long)addr << 3;
else {
// h_version == V9
long indirect = (long)addr << 2;
HIGH_LONG(indirect, byte_addr);
}
if ((uint)byte_addr >= story_size)
runtimeError(ERR_ILL_PRINT_ADDR);
}
// Loop until a 16bit word has the highest bit set
if (st == VOCABULARY)
ptr = _decoded;
do {
int i;
// Fetch the next 16bit word
if (st == LOW_STRING || st == VOCABULARY) {
LOW_WORD(addr, code);
addr += 2;
} else if (st == HIGH_STRING || st == ABBREVIATION) {
HIGH_WORD(byte_addr, code);
byte_addr += 2;
} else {
CODE_WORD(code);
}
// Read its three Z-characters
for (i = 10; i >= 0; i -= 5) {
zword abbr_addr;
zword ptr_addr;
zchar zc;
c = (code >> i) & 0x1f;
switch (status) {
case 0:
// normal operation
if (shift_state == 2 && c == 6)
status = 2;
else if (h_version == V1 && c == 1)
new_line();
else if (h_version >= V2 && shift_state == 2 && c == 7)
new_line();
else if (c >= 6)
outchar(alphabet(shift_state, c - 6));
else if (c == 0)
outchar(' ');
else if (h_version >= V2 && c == 1)
status = 1;
else if (h_version >= V3 && c <= 3)
status = 1;
else {
shift_state = (shift_lock + (c & 1) + 1) % 3;
if (h_version <= V2 && c >= 4)
shift_lock = shift_state;
break;
}
shift_state = shift_lock;
break;
case 1:
// abbreviation
ptr_addr = h_abbreviations + 64 * (prev_c - 1) + 2 * c;
LOW_WORD(ptr_addr, abbr_addr);
decode_text(ABBREVIATION, abbr_addr);
status = 0;
break;
case 2:
// ZSCII character - first part
status = 3;
break;
case 3:
// ZSCII character - second part
zc = (prev_c << 5) | c;
if (zc > 767) {
// Unicode escape
while (zc-- > 767) {
if (st == LOW_STRING || st == VOCABULARY) {
LOW_WORD(addr, c2);
addr += 2;
} else if (st == HIGH_STRING || st == ABBREVIATION) {
HIGH_WORD(byte_addr, c2);
byte_addr += 2;
} else
CODE_WORD(c2);
outchar(c2 ^ 0xFFFF);
}
} else {
c2 = translate_from_zscii(zc);
outchar(c2);
}
status = 0;
break;
default:
break;
}
prev_c = c;
}
} while (!(code & 0x8000));
if (st == VOCABULARY)
*ptr = 0;
}
#undef outchar
void Processor::print_num(zword value) {
int i;
// Print sign
if ((short)value < 0) {
print_char('-');
value = -(short)value;
}
// Print absolute value
for (i = 10000; i != 0; i /= 10)
if (value >= i || i == 1)
print_char('0' + (value / i) % 10);
}
void Processor::print_object(zword object) {
zword addr = object_name(object);
zword code = 0x94a5;
zbyte length;
LOW_BYTE(addr, length);
addr++;
if (length != 0)
LOW_WORD(addr, code);
if (code == 0x94a5) {
// _encoded text 0x94a5 == empty string
print_string("object#"); // supply a generic name
print_num(object); // for anonymous objects
} else {
decode_text(LOW_STRING, addr);
}
}
zword Processor::lookup_text(int padding, zword dct) {
zword entry_addr;
zword entry_count;
zword entry;
zword addr;
zbyte entry_len;
zbyte sep_count;
int entry_number;
int lower, upper;
int i;
bool sorted;
if (_resolution == 0)
find_resolution();
encode_text(padding);
LOW_BYTE(dct, sep_count); // skip word separators
dct += 1 + sep_count;
LOW_BYTE(dct, entry_len); // get length of entries
dct += 1;
LOW_WORD(dct, entry_count); // get number of entries
dct += 2;
if ((short)entry_count < 0) {
// bad luck, entries aren't sorted
entry_count = -(short)entry_count;
sorted = false;
} else {
sorted = true; // entries are sorted
}
lower = 0;
upper = entry_count - 1;
while (lower <= upper) {
if (sorted)
// binary search
entry_number = (lower + upper) / 2;
else
// linear search
entry_number = lower;
entry_addr = dct + entry_number * entry_len;
// Compare word to dictionary entry
addr = entry_addr;
for (i = 0; i < _resolution; i++) {
LOW_WORD(addr, entry);
if (_encoded[i] != entry)
goto continuing;
addr += 2;
}
return entry_addr; // exact match found, return now
continuing:
if (sorted) {
// binary search
if (_encoded[i] > entry)
lower = entry_number + 1;
else
upper = entry_number - 1;
} else {
// linear search
lower++;
}
}
// No exact match has been found
if (padding == 0x05)
return 0;
entry_number = (padding == 0x00) ? lower : upper;
if (entry_number == -1 || entry_number == entry_count)
return 0;
return dct + entry_number * entry_len;
}
void Processor::handleAbbreviations() {
// Construct a unicode string containing the word
int wordSize = 0;
while (wordSize < (_resolution * 3) && _decoded[wordSize])
++wordSize;
Common::U32String word(_decoded, _decoded + wordSize);
// Check for standard abbreviations
if (word == "g")
word = "again";
else if (word == "o")
word = "oops";
else if (word == "x")
word = "examine";
else if (word == "z")
word = "wait";
else
return;
// Found abbreviation, so copy it's long form into buffer
Common::copy(word.c_str(), word.c_str() + MIN((int)word.size() + 1, _resolution * 3), _decoded);
}
void Processor::tokenise_text(zword text, zword length, zword from, zword parse, zword dct, bool flag) {
zword addr;
zbyte token_max, token_count;
LOW_BYTE(parse, token_max);
parse++;
LOW_BYTE(parse, token_count);
if (token_count < token_max) {
// sufficient space left for token?
storeb(parse++, token_count + 1);
load_string((zword)(text + from), length);
if ((from == 1) && isInfocom() && h_version < 5)
handleAbbreviations();
addr = lookup_text(0x05, dct);
if (addr != 0 || !flag) {
parse += 4 * token_count;
storew((zword)(parse + 0), addr);
storeb((zword)(parse + 2), length);
storeb((zword)(parse + 3), from);
}
}
}
void Processor::tokenise_line(zword text, zword token, zword dct, bool flag) {
zword addr1;
zword addr2;
zbyte length = 0;
zbyte c;
// Use standard dictionary if the given dictionary is zero
if (dct == 0)
dct = h_dictionary;
// Remove all tokens before inserting new ones
storeb((zword)(token + 1), 0);
// Move the first pointer across the text buffer searching for the beginning
// of a word. If this succeeds, store the position in a second pointer.
// Move the first pointer searching for the end of the word. When it is found,
// "tokenise" the word. Continue until the end of the buffer is reached.
addr1 = text;
addr2 = 0;
if (h_version >= V5) {
addr1++;
LOW_BYTE(addr1, length);
}
do {
zword sep_addr;
zbyte sep_count;
zbyte separator;
// Fetch next ZSCII character
addr1++;
if (h_version >= V5 && addr1 == text + 2 + length)
c = 0;
else
LOW_BYTE(addr1, c);
// Check for separator
sep_addr = dct;
LOW_BYTE(sep_addr, sep_count);
sep_addr++;
do {
LOW_BYTE(sep_addr, separator);
sep_addr++;
} while (c != separator && --sep_count != 0);
// This could be the start or the end of a word
if (sep_count == 0 && c != ' ' && c != 0) {
if (addr2 == 0)
addr2 = addr1;
} else if (addr2 != 0) {
tokenise_text(text, (zword)(addr1 - addr2), (zword)(addr2 - text),
token, dct, flag);
addr2 = 0;
}
// Translate separator (which is a word in its own right)
if (sep_count != 0)
tokenise_text(text, (zword)(1), (zword)(addr1 - text), token, dct, flag);
} while (c != 0);
}
int Processor::completion(const zchar *buffer, zchar *result) {
zword minaddr;
zword maxaddr;
zchar *ptr;
zchar c;
int len;
int i;
*result = 0;
if (_resolution == 0)
find_resolution();
// Copy last word to "_decoded" string
len = 0;
while ((c = *buffer++) != 0)
if (c != ' ') {
if (len < 3 * _resolution)
_decoded[len++] = c;
} else {
len = 0;
}
_decoded[len] = 0;
// Search the dictionary for first and last possible extensions
minaddr = lookup_text(0x00, h_dictionary);
maxaddr = lookup_text(0x1f, h_dictionary);
if (minaddr == 0 || maxaddr == 0 || minaddr > maxaddr)
return 2;
// Copy first extension to "result" string
decode_text(VOCABULARY, minaddr);
ptr = result;
for (i = len; (c = _decoded[i]) != 0; i++)
*ptr++ = c;
*ptr = 0;
// Merge second extension with "result" string
decode_text(VOCABULARY, maxaddr);
for (i = len, ptr = result; (c = _decoded[i]) != 0; i++, ptr++) {
if (*ptr != c)
break;
}
*ptr = 0;
// Search was ambiguous or successful
return (minaddr == maxaddr) ? 0 : 1;
}
zchar Processor::unicode_tolower(zchar c) {
static const byte tolower_basic_latin[0x100] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F,
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xD7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xDF,
0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
};
static const byte tolower_latin_extended_a[0x80] = {
0x01,0x01,0x03,0x03,0x05,0x05,0x07,0x07,0x09,0x09,0x0B,0x0B,0x0D,0x0D,0x0F,0x0F,
0x11,0x11,0x13,0x13,0x15,0x15,0x17,0x17,0x19,0x19,0x1B,0x1B,0x1D,0x1D,0x1F,0x1F,
0x21,0x21,0x23,0x23,0x25,0x25,0x27,0x27,0x29,0x29,0x2B,0x2B,0x2D,0x2D,0x2F,0x2F,
0x00,0x31,0x33,0x33,0x35,0x35,0x37,0x37,0x38,0x3A,0x3A,0x3C,0x3C,0x3E,0x3E,0x40,
0x40,0x42,0x42,0x44,0x44,0x46,0x46,0x48,0x48,0x49,0x4B,0x4B,0x4D,0x4D,0x4F,0x4F,
0x51,0x51,0x53,0x53,0x55,0x55,0x57,0x57,0x59,0x59,0x5B,0x5B,0x5D,0x5D,0x5F,0x5F,
0x61,0x61,0x63,0x63,0x65,0x65,0x67,0x67,0x69,0x69,0x6B,0x6B,0x6D,0x6D,0x6F,0x6F,
0x71,0x71,0x73,0x73,0x75,0x75,0x77,0x77,0x00,0x7A,0x7A,0x7C,0x7C,0x7E,0x7E,0x7F
};
static const byte tolower_greek[0x50] = {
0x80,0x81,0x82,0x83,0x84,0x85,0xAC,0x87,0xAD,0xAE,0xAF,0x8B,0xCC,0x8D,0xCD,0xCE,
0x90,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
0xC0,0xC1,0xA2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xAC,0xAD,0xAE,0xAF,
0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF
};
static const byte tolower_cyrillic[0x60] = {
0x00,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F
};
if (c < 0x0100)
c = tolower_basic_latin[c];
else if (c == 0x0130)
c = 0x0069; // Capital I with dot -> lower case i
else if (c == 0x0178)
c = 0x00FF; // Capital Y diaeresis -> lower case y diaeresis
else if (c < 0x0180)
c = tolower_latin_extended_a[c - 0x100] + 0x100;
else if (c >= 0x380 && c < 0x3D0)
c = tolower_greek[c - 0x380] + 0x300;
else if (c >= 0x400 && c < 0x460)
c = tolower_cyrillic[c - 0x400] + 0x400;
return c;
}
void Processor::z_check_unicode() {
zword c = zargs[0];
zword result = 0;
if (c <= 0x1f) {
if ((c == 0x08) || (c == 0x0d) || (c == 0x1b))
result = 2;
} else if (c <= 0x7e) {
result = 3;
} else {
// we support unicode
result = 1;
}
store(result);
}
void Processor::z_encode_text() {
int i;
load_string((zword)(zargs[0] + zargs[2]), zargs[1]);
encode_text(0x05);
for (i = 0; i < _resolution; i++)
storew((zword)(zargs[3] + 2 * i), _encoded[i]);
}
void Processor::z_new_line() {
new_line();
}
void Processor::z_print() {
decode_text(EMBEDDED_STRING, 0);
}
void Processor::z_print_addr() {
decode_text(LOW_STRING, zargs[0]);
}
void Processor::z_print_char() {
print_char(translate_from_zscii(zargs[0]));
}
void Processor::z_print_form() {
zword count;
zword addr = zargs[0];
bool first = true;
for (;;) {
LOW_WORD(addr, count);
addr += 2;
if (count == 0)
break;
if (!first)
new_line();
while (count--) {
zbyte c;
LOW_BYTE(addr, c);
addr++;
print_char(translate_from_zscii(c));
}
first = false;
}
}
void Processor::z_print_num() {
print_num(zargs[0]);
}
void Processor::z_print_obj() {
print_object(zargs[0]);
}
void Processor::z_print_paddr() {
decode_text(HIGH_STRING, zargs[0]);
}
void Processor::z_print_ret() {
decode_text(EMBEDDED_STRING, 0);
new_line();
ret(1);
}
void Processor::z_print_unicode() {
if (zargs[0] < 0x20)
print_char('?');
else
print_char(zargs[0]);
}
void Processor::z_tokenise() {
// Supply default arguments
if (zargc < 3)
zargs[2] = 0;
if (zargc < 4)
zargs[3] = 0;
// Call tokenise_line to do the real work
tokenise_line(zargs[0], zargs[1], zargs[2], zargs[3] != 0);
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,198 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
namespace Glk {
namespace ZCode {
void Processor::z_dec() {
zword value;
if (zargs[0] == 0)
(*_sp)--;
else if (zargs[0] < 16)
(*(_fp - zargs[0]))--;
else {
zword addr = h_globals + 2 * (zargs[0] - 16);
LOW_WORD(addr, value);
value--;
SET_WORD(addr, value);
}
}
void Processor::z_dec_chk() {
zword value;
if (zargs[0] == 0)
value = --(*_sp);
else if (zargs[0] < 16)
value = --(*(_fp - zargs[0]));
else {
zword addr = h_globals + 2 * (zargs[0] - 16);
LOW_WORD(addr, value);
value--;
SET_WORD(addr, value);
}
branch((short)value < (short)zargs[1]);
}
void Processor::z_inc() {
zword value;
if (zargs[0] == 0)
(*_sp)++;
else if (zargs[0] < 16)
(*(_fp - zargs[0]))++;
else {
zword addr = h_globals + 2 * (zargs[0] - 16);
LOW_WORD(addr, value);
value++;
SET_WORD(addr, value);
}
}
void Processor::z_inc_chk() {
zword value;
if (zargs[0] == 0)
value = ++(*_sp);
else if (zargs[0] < 16)
value = ++(*(_fp - zargs[0]));
else {
zword addr = h_globals + 2 * (zargs[0] - 16);
LOW_WORD(addr, value);
value++;
SET_WORD(addr, value);
}
branch((short)value > (short)zargs[1]);
}
void Processor::z_load() {
zword value;
if (zargs[0] == 0)
value = *_sp;
else if (zargs[0] < 16)
value = *(_fp - zargs[0]);
else {
zword addr = h_globals + 2 * (zargs[0] - 16);
LOW_WORD(addr, value);
}
store(value);
}
void Processor::z_pop() {
_sp++;
}
void Processor::z_pop_stack() {
if (zargc == 2) {
// it's a user stack
zword size;
zword addr = zargs[1];
LOW_WORD(addr, size);
size += zargs[0];
storew(addr, size);
} else {
// it's the game stack
_sp += zargs[0];
}
}
void Processor::z_pull() {
zword value;
if (h_version != V6) {
// not a V6 game, pop stack and write
value = *_sp++;
if (zargs[0] == 0)
*_sp = value;
else if (zargs[0] < 16)
*(_fp - zargs[0]) = value;
else {
zword addr = h_globals + 2 * (zargs[0] - 16);
SET_WORD(addr, value);
}
} else {
// it's V6, but is there a user stack?
if (zargc == 1) {
// it's a user stack
zword size;
zword addr = zargs[0];
LOW_WORD(addr, size);
size++;
storew(addr, size);
addr += 2 * size;
LOW_WORD(addr, value);
} else {
// it's the game stack
value = *_sp++;
}
store(value);
}
}
void Processor::z_push() {
*--_sp = zargs[0];
}
void Processor::z_push_stack() {
zword size;
zword addr = zargs[1];
LOW_WORD(addr, size);
if (size != 0) {
storew((zword)(addr + 2 * size), zargs[0]);
size--;
storew(addr, size);
}
branch(size);
}
void Processor::z_store() {
zword value = zargs[1];
if (zargs[0] == 0)
*_sp = value;
else if (zargs[0] < 16)
*(_fp - zargs[0]) = value;
else {
zword addr = h_globals + 2 * (zargs[0] - 16);
SET_WORD(addr, value);
}
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,300 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/processor.h"
namespace Glk {
namespace ZCode {
static struct {
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 }
};
void Processor::z_draw_picture() {
zword pic = zargs[0];
zword y = zargs[1];
zword x = zargs[2];
int i;
flush_buffer();
Window &win = _wp[_wp._cwin];
if (_storyId == ZORK_ZERO && _wp._cwin == 0) {
// WORKAROUND: Zork Zero has pictures for graphics embedded in the text with specific
// co-prdinates. We need to reset it to 0,0 to flag it should be drawn at the cursor
x = y = 0;
} else {
assert(x && y);
x += win[X_POS] - 1;
y += win[Y_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 (_storyId == mapper[i].story_id && pic == mapper[i].pic) {
uint height1, width1;
uint height2, width2;
int delta = 0;
os_picture_data(pic, &height1, &width1);
os_picture_data(mapper[i].pic2, &height2, &width2);
if (_storyId == ARTHUR && pic == 54)
delta = h_screen_width / 160;
assert(x && y);
os_draw_picture(mapper[i].pic1, Point(x + delta, y + height1));
os_draw_picture(mapper[i].pic2, Point(x + width1 - width2 - delta, y + height1));
}
}
os_draw_picture(pic, Point(x, y));
if (_storyId == SHOGUN && pic == 3) {
uint height, width;
os_picture_data(59, &height, &width);
os_draw_picture(59, Point(h_screen_width - width + 1, y));
}
}
void Processor::z_picture_data() {
zword pic = zargs[0];
zword table = zargs[1];
uint height, width;
int i;
bool avail = os_picture_data(pic, &height, &width);
for (i = 0; mapper[i].story_id != UNKNOWN; i++) {
if (_storyId == mapper[i].story_id) {
if (pic == mapper[i].pic) {
uint 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);
}
void Processor::z_erase_picture() {
#ifdef TODO
int height, width;
zword y = zargs[1];
zword x = zargs[2];
flush_buffer();
/* Do nothing if the background is transparent */
if (hi(cwp->colour) == TRANSPARENT_COLOUR)
return;
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, -1);
#endif
}
void Processor::z_set_margins() {
#ifdef TODO
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();
}
#endif
}
void Processor::z_move_window(void) {
flush_buffer();
zword win = winarg0();
_wp[win].setPosition(Point(zargs[2], zargs[1]));
}
void Processor::z_window_size() {
flush_buffer();
zword win = winarg0();
_wp[win].setSize(Point(zargs[2], zargs[1]));
}
void Processor::z_window_style() {
#ifdef TODO
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();
#endif
}
void Processor::z_get_wind_prop() {
flush_buffer();
zword win = winarg0();
zword prop = zargs[1];
if (prop <= TRUE_BG_COLOR)
store(_wp[win][(WindowProperty)prop]);
else
runtimeError(ERR_ILL_WIN_PROP);
}
void Processor::z_put_wind_prop() {
flush_buffer();
zword win = winarg0();
WindowProperty prop = (WindowProperty)zargs[1];
zword val = zargs[2];
if (prop >= TRUE_FG_COLOR)
runtimeError(ERR_ILL_WIN_PROP);
_wp[win][prop] = val;
}
void Processor::z_scroll_window() {
#ifdef TODO
zword win = winarg0();
zword y, x;
flush_buffer();
/* Use the correct set of colours when scrolling the window */
if (win != cwin && !amiga_screen_model())
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 && !amiga_screen_model())
os_set_colour(lo(cwp->colour), hi(cwp->colour));
#endif
}
void Processor::z_mouse_window() {
// No implementation - since ScummVM can run as a window, it's better
// not to constrain the area the mouse can move
}
void Processor::z_picture_table() {
/* 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.
*/
}
zword Processor::winarg0() {
if (h_version == V6 && (short)zargs[0] == -3)
return _wp._cwin;
if (zargs[0] >= ((h_version == V6) ? 8 : 2))
runtimeError(ERR_ILL_WIN);
return zargs[0];
}
zword Processor::winarg2() {
if (zargc < 3 || (short)zargs[2] == -3)
return _wp._cwin;
if (zargs[2] >= 8)
runtimeError(ERR_ILL_WIN);
return zargs[2];
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,420 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/quetzal.h"
#include "glk/zcode/processor.h"
#include "common/memstream.h"
namespace Glk {
namespace ZCode {
/**
* Various parsing states within restoration.
*/
enum ParseState {
GOT_HEADER = 0x01,
GOT_STACK = 0x02,
GOT_MEMORY = 0x04,
GOT_NONE = 0x00,
GOT_ALL = 0x07,
GOT_ERROR = 0x80
};
#define WRITE_RUN(RUN) ws.writeByte(0); ws.writeByte((byte)(RUN))
bool Quetzal::save(Common::WriteStream *svf, Processor *proc, const Common::String &desc) {
Processor &p = *proc;
offset_t pc;
zword i, j, n;
zword nvars, nargs, nstk;
zbyte var;
int c;
// Reset Quetzal writer
_writer.clear();
// Write `IFhd' chunk
{
Common::WriteStream &ws = _writer.add(ID_IFhd);
pc = p.getPC();
ws.writeUint16BE(p.h_release);
ws.write(&p[H_SERIAL], 6);
ws.writeUint16BE(p.h_checksum);
ws.writeByte((pc >> 16) & 0xff);
ws.writeByte((pc >> 8) & 0xff);
ws.writeByte(pc & 0xff);
}
// Write `CMem' chunk.
{
Common::WriteStream &ws = _writer.add(ID_CMem);
_storyFile->seek(0);
// j holds current run length.
for (i = 0, j = 0; i < p.h_dynamic_size; ++i) {
c = _storyFile->readByte();
c ^= p[i];
if (c == 0) {
// It's a run of equal bytes
++j;
} else {
// Write out any run there may be.
if (j > 0) {
for (; j > 0x100; j -= 0x100) {
WRITE_RUN(0xFF);
}
WRITE_RUN(j - 1);
j = 0;
}
// Any runs are now written. Write this (nonzero) byte
ws.writeByte(c);
}
}
}
// Write `Stks' chunk. You are not expected to understand this. ;)
{
Common::WriteStream &ws = _writer.add(ID_Stks);
// 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] = p._sp - p._stack; // The frame we'd get by doing a call now.
for (i = p._fp - p._stack + 4, n = 0; i < STACK_SIZE + 4; i = p._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 (p.h_version != V6) {
for (i = 0; i < 6; ++i)
ws.writeByte(0);
nstk = STACK_SIZE - frames[n];
ws.writeUint16BE(nstk);
for (j = STACK_SIZE - 1; j >= frames[n]; --j)
ws.writeUint16BE(p._stack[j]);
}
// Write out the rest of the stack frames.
for (i = n; i > 0; --i) {
zword *pf = p._stack + frames[i] - 4; // Points to call frame
nvars = (pf[0] & 0x0F00) >> 8;
nargs = pf[0] & 0x00FF;
nstk = frames[i] - frames[i - 1] - nvars - 4;
pc = ((uint)pf[3] << 9) | pf[2];
// Check type of call
switch (pf[0] & 0xF000) {
case 0x0000:
// Function
var = p[pc];
pc = ((pc + 1) << 8) | nvars;
break;
case 0x1000:
// Procedure
var = 0;
pc = (pc << 8) | 0x10 | nvars; // Set procedure flag
break;
default:
p.runtimeError(ERR_SAVE_IN_INTER);
return 0;
}
if (nargs != 0)
nargs = (1 << nargs) - 1; // Make args into bitmap
// Write the main part of the frame...
ws.writeUint32BE(pc);
ws.writeByte(var);
ws.writeByte(nargs);
ws.writeUint16BE(nstk);
// Write the variables and eval stack
for (j = 0, --pf; j<nvars + nstk; ++j, --pf)
ws.writeUint16BE(*pf);
}
}
// Write the save data out
_writer.save(svf, desc, ID_IFZS);
// After all that, still nothing went wrong!
return true;
}
int Quetzal::restore(Common::SeekableReadStream *sv, Processor *proc) {
Processor &p = *proc;
uint tmpl, currlen;
offset_t pc;
zword tmpw;
int fatal = 0; // Set to -1 when errors must be fatal.
zbyte progress = GOT_NONE;
int i, x, y;
// Load the savefile for reading
if (!_reader.open(sv, ID_IFZS)) {
p.print_string("This is not a saved game file!\n");
return 0;
}
// Read each chunk and process it
for (QuetzalReader::Iterator it = _reader.begin(); it != _reader.end(); ++it) {
Common::SeekableReadStream *s = it.getStream();
currlen = (*it)._size;
switch ((*it)._id) {
// `IFhd' header chunk; must be first in file
case ID_IFhd:
if (progress & GOT_HEADER) {
p.print_string("Save file has two IFZS chunks!\n");
return fatal;
}
progress |= GOT_HEADER;
if (currlen < 13)
return fatal;
tmpw = s->readUint16BE();
if (tmpw != p.h_release)
progress = GOT_ERROR;
for (int idx = H_SERIAL; idx < H_SERIAL + 6; ++idx) {
x = s->readByte();
if (x != p[idx])
progress = GOT_ERROR;
}
tmpw = s->readUint16BE();
if (tmpw != p.h_checksum)
progress = GOT_ERROR;
if (progress & GOT_ERROR) {
p.print_string("File was not saved from this story!\n");
return fatal;
}
x = s->readByte();
pc = (uint)x << 16;
x = s->readByte();
pc |= (uint)x << 8;
x = s->readByte();
pc |= (uint)x;
fatal = -1; // Setting PC means errors must be fatal
p.setPC(pc);
break;
// `Stks' stacks chunk; restoring this is quite complex. ;)
case ID_Stks:
if (progress & GOT_STACK) {
p.print_string("File contains two stack chunks!\n");
break;
}
progress |= GOT_STACK;
fatal = -1; // Setting SP means errors must be fatal
p._sp = p._stack + STACK_SIZE;
// All versions other than V6 may use evaluation stack outside any function context.
// As a result a faked function context will be present in the file here. We skip
// this context, but load the associated stack onto the stack proper...
if (p.h_version != V6) {
if (currlen < 8)
return fatal;
s->skip(6);
tmpw = s->readUint16BE();
if (tmpw > STACK_SIZE) {
p.print_string("Save-file has too much stack (and I can't cope).\n");
return fatal;
}
currlen -= 8;
if (currlen < (uint)tmpw * 2)
return fatal;
for (i = 0; i < tmpw; ++i)
*--p._sp = s->readUint16BE();
currlen -= tmpw * 2;
}
// We now proceed to load the main block of stack frames
for (p._fp = p._stack + STACK_SIZE, p._frameCount = 0;
currlen > 0; currlen -= 8, ++p._frameCount) {
if (currlen < 8) return fatal;
if (p._sp - p._stack < 4) {
// No space for frame
p.print_string("Save-file has too much stack (and I can't cope).\n");
return fatal;
}
// Read PC, procedure flag and formal param count
tmpl = s->readUint32BE();
y = (int)(tmpl & 0x0F); // Number of formals
tmpw = y << 8;
// Read result variable
x = s->readByte();
// 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 (p[tmpl] != (zbyte)x) {
p.print_string("Save-file has wrong variable number on stack (possibly wrong game version?)\n");
return fatal;
}
}
*--p._sp = (zword)(tmpl >> 9); // High part of PC
*--p._sp = (zword)(tmpl & 0x1FF); // Low part of PC
*--p._sp = (zword)(p._fp - p._stack - 1); // FP
// Read and process argument mask
x = s->readByte();
++x; // Should now be a power of 2
for (i = 0; i<8; ++i)
if (x & (1 << i))
break;
if (x ^ (1 << i)) {
// Not a power of 2
p.print_string("Save-file uses incomplete argument lists (which I can't handle)\n");
return fatal;
}
*--p._sp = tmpw | i;
p._fp = p._sp; // FP for next frame
// Read amount of eval stack used
tmpw = s->readUint16BE();
tmpw += y; // Amount of stack + number of locals
if (p._sp - p._stack <= tmpw) {
p.print_string("Save-file has too much stack (and I can't cope).\n");
return fatal;
}
if (currlen < (uint)tmpw * 2)
return fatal;
for (i = 0; i < tmpw; ++i)
*--p._sp = s->readUint16BE();
currlen -= tmpw * 2;
}
// End of `Stks' processing...
break;
// `CMem' compressed memory chunk; uncompress it
case ID_CMem:
if (!(progress & GOT_MEMORY)) {
_storyFile->seek(0);
i = 0; // Bytes written to data area
for (; currlen > 0; --currlen) {
x = s->readByte();
if (x == 0) {
// Start of run
// Check for bogus run
if (currlen < 2) {
p.print_string("File contains bogus `CMem' chunk.\n");
s->skip(currlen);
currlen = 1;
i = 0xFFFF;
break; // Keep going; may be a `UMem' too
}
// Copy story file to memory during the run
--currlen;
x = s->readByte();
for (; x >= 0 && i < p.h_dynamic_size; --x, ++i)
p[i] = _storyFile->readByte();
} else {
// Not a run
y = _storyFile->readByte();
p[i] = (zbyte)(x ^ y);
++i;
}
// Make sure we don't load too much
if (i > p.h_dynamic_size) {
p.print_string("warning: `CMem' chunk too long!\n");
s->skip(currlen);
break; // Keep going; there may be a `UMem' too
}
}
// If chunk is short, assume a run
for (; i < p.h_dynamic_size; ++i)
p[i] = _storyFile->readByte();
if (currlen == 0)
progress |= GOT_MEMORY; // Only if succeeded
break;
}
break;
// 'UMem' Uncompressed memory chunk
case ID_UMem:
if (!(progress & GOT_MEMORY)) {
// Must be exactly the right size
if (currlen == p.h_dynamic_size) {
if (s->read(p.zmp, currlen) == currlen) {
progress |= GOT_MEMORY; // Only on success
break;
}
} else {
p.print_string("`UMem' chunk wrong size!\n");
}
// Fall into default action (skip chunk) on errors
}
break;
default:
break;
}
delete s;
}
// We've reached the end of the file. For the restoration to have been a
// success, we must have had one of each of the required chunks.
if (!(progress & GOT_HEADER))
p.print_string("error: no valid header (`IFhd') chunk in file.\n");
if (!(progress & GOT_STACK))
p.print_string("error: no valid stack (`Stks') chunk in file.\n");
if (!(progress & GOT_MEMORY))
p.print_string("error: no valid memory (`CMem' or `UMem') chunk in file.\n");
return (progress == GOT_ALL ? 2 : fatal);
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,68 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_QUETZAL
#define GLK_ZCODE_QUETZAL
#include "glk/glk_types.h"
#include "glk/quetzal.h"
#include "glk/zcode/frotz_types.h"
namespace Glk {
namespace ZCode {
class Processor;
class Quetzal {
private:
Common::SeekableReadStream *_storyFile;
QuetzalReader _reader;
QuetzalWriter _writer;
zword frames[STACK_SIZE / 4 + 1];
public:
/**
* Constructor
*/
Quetzal(Common::SeekableReadStream *storyFile) : _storyFile(storyFile) {}
/*
* Save a game using Quetzal format.
* @param svf Savegame file
* @param proc Pointer to the Frotz processor
* @param desc Savegame description
* @returns Returns true if OK, false if failed
*/
bool save(Common::WriteStream *svf, Processor *proc, const Common::String &desc);
/**
* Restore a saved game using Quetzal format
* @param svf Savegame file
* @param proc Pointer to the Frotz processor
* @returns Return 2 if OK, 0 if an error occurred before any damage was done,
* -1 on a fatal error
*/
int restore(Common::SeekableReadStream *svf, Processor *proc);
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,128 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/screen.h"
#include "glk/zcode/bitmap_font.h"
#include "glk/zcode/zcode.h"
#include "glk/conf.h"
#include "common/file.h"
#include "graphics/fonts/ttf.h"
#include "image/xbm.h"
#include "glk/zcode/infocom6x8.xbm"
#include "glk/zcode/infocom_graphics.xbm"
namespace Glk {
namespace ZCode {
FrotzScreen::FrotzScreen() : Glk::Screen() {
g_conf->_tStyles[style_User1].font = CUSTOM;
g_conf->_gStyles[style_User1].font = CUSTOM;
g_conf->_tStyles[style_User2].font = CUSTOM2;
}
void FrotzScreen::loadFonts(Common::Archive *archive) {
// Get the zmachine version. At this point the header isn't loaded, so we have to do it manually
g_vm->_gameFile.seek(0);
byte version = g_vm->_gameFile.readByte();
if (version == 6) {
loadVersion6Fonts(archive);
} else {
// Load the basic fonts
Screen::loadFonts(archive);
}
// Add character graphics and runic fonts
loadExtraFonts(archive);
}
void FrotzScreen::loadVersion6Fonts(Common::Archive *archive) {
// Set the basic font properties
MonoFontInfo &mi = g_conf->_monoInfo;
PropFontInfo &pi = g_conf->_propInfo;
mi._size = pi._size = 7;
mi._aspect = pi._aspect = 1.0;
pi._quotes = 0;
pi._dashes = 0;
pi._spaces = 0;
pi._morePrompt = "[MORE]";
pi._lineSeparation = 0;
g_vm->_defaultForeground = 0;
g_vm->_defaultBackground = (int)zcolor_Transparent;
g_conf->_tMarginX = 3;
g_conf->_tMarginY = 3;
for (uint idx = 0; idx < style_NUMSTYLES; ++idx) {
g_conf->_tStyles[idx].bg = g_conf->_tStylesDefault[idx].bg = zcolor_Transparent;
g_conf->_gStyles[idx].bg = g_conf->_gStylesDefault[idx].bg = zcolor_Transparent;
}
_fonts.resize(8);
// Load up the 8x8 Infocom font
Image::XBMDecoder decoder;
decoder.loadBits(infocom6x8_bits, infocom6x8_width, infocom6x8_height);
Common::Point fontSize(6, 8);
// Add normal fonts
_fonts[MONOR] = new FixedWidthBitmapFont(*decoder.getSurface(), fontSize, 6, 8);
_fonts[MONOB] = new FixedWidthBitmapFont(*decoder.getSurface(), fontSize, 6, 8);
_fonts[PROPR] = new VariableWidthBitmapFont(*decoder.getSurface(), fontSize, 6, 8);
_fonts[PROPB] = new VariableWidthBitmapFont(*decoder.getSurface(), fontSize, 6, 8);
// Create a new version of the font with every character unlined for the emphasized fonts
const Graphics::Surface &norm = *decoder.getSurface();
Graphics::ManagedSurface emph(norm.w, norm.h);
emph.blitFrom(norm);
for (int y = 8 - 2; y < emph.h; y += 8) {
byte *lineP = (byte *)emph.getBasePtr(0, y);
Common::fill(lineP, lineP + emph.w, 1);
}
// Add them to the font list
_fonts[MONOI] = new FixedWidthBitmapFont(emph, fontSize, 6, 8);
_fonts[MONOZ] = new FixedWidthBitmapFont(emph, fontSize, 6, 8);
_fonts[PROPI] = new VariableWidthBitmapFont(emph, fontSize, 6, 8);
_fonts[PROPZ] = new VariableWidthBitmapFont(emph, fontSize, 6, 8);
}
void FrotzScreen::loadExtraFonts(Common::Archive *archive) {
Image::XBMDecoder decoder;
decoder.loadBits(infocom_graphics_bits, infocom_graphics_width, infocom_graphics_height);
Common::Point fontSize(_fonts[0]->getMaxCharWidth(), _fonts[0]->getFontHeight());
_fonts.push_back(new FixedWidthBitmapFont(*decoder.getSurface(), fontSize));
// Add Runic font. It provides cleaner versions of the runic characters in the
// character graphics font
Common::File *f = new Common::File();
if (!f->open("NotoSansRunic-Regular.ttf", *archive))
error("Could not load font");
_fonts.push_back(Graphics::loadTTFFont(f, DisposeAfterUse::YES, g_conf->_propInfo._size, Graphics::kTTFSizeModeCharacter));
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,59 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_FONTS
#define GLK_ZCODE_FONTS
#include "glk/screen.h"
namespace Glk {
namespace ZCode {
/**
* Derived screen class that adds in the Infocom character graphics font
*/
class FrotzScreen : public Glk::Screen {
private:
/**
* Handles loading fonts for V6 games
*/
void loadVersion6Fonts(Common::Archive *archive);
/**
* Handles loading the character graphics and runic fonts
*/
void loadExtraFonts(Common::Archive *archive);
protected:
/**
* Load the fonts
*/
void loadFonts(Common::Archive *archive) override;
public:
/**
* Constructor
*/
FrotzScreen();
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,146 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/sound_folder.h"
#include "common/file.h"
#include "common/compression/unzip.h"
namespace Glk {
namespace ZCode {
void SoundSubfolder::check(const Common::FSNode &gameDir) {
Common::FSNode sound = gameDir.getChild("sound");
if (sound.isDirectory())
SearchMan.add("sound", new SoundSubfolder(sound));
}
SoundSubfolder::SoundSubfolder(const Common::FSNode &folder) : _folder(folder) {
Common::FSList files;
if (folder.getChildren(files, Common::FSNode::kListFilesOnly)) {
for (uint idx = 0; idx < files.size(); ++idx) {
Common::String filename = files[idx].getName();
if (filename.hasSuffixIgnoreCase(".snd")) {
int fileNum = atoi(filename.c_str() + filename.size() - 6);
Common::Path newName(Common::String::format("sound%d.snd", fileNum));
_filenames[newName] = filename;
}
}
}
}
bool SoundSubfolder::hasFile(const Common::Path &path) const {
return _filenames.contains(path);
}
int SoundSubfolder::listMembers(Common::ArchiveMemberList &list) const {
int total = 0;
for (FileMap::iterator i = _filenames.begin(); i != _filenames.end(); ++i) {
list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember((*i)._key, *this)));
++total;
}
return total;
}
const Common::ArchiveMemberPtr SoundSubfolder::getMember(const Common::Path &path) const {
if (!hasFile(path))
return Common::ArchiveMemberPtr();
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
}
Common::SeekableReadStream *SoundSubfolder::createReadStreamForMember(const Common::Path &path) const {
Common::File *f = new Common::File();
if (_filenames.contains(path) && f->open(_folder.getChild(_filenames[path])))
return f;
delete f;
return nullptr;
}
/*--------------------------------------------------------------------------*/
void SoundZip::check(const Common::FSNode &gameDir, Story story) {
if (story != LURKING_HORROR && story != SHERLOCK)
return;
Common::String zipName = (story == LURKING_HORROR) ? "lhsound.zip" : "shsound.zip";
// Check for the existence of the zip
Common::FSNode zipNode = gameDir.getChild(zipName);
if (!zipNode.exists())
return;
SearchMan.add("sound", new SoundZip(Common::makeZipArchive(zipNode)));
}
SoundZip::SoundZip(Common::Archive *zip) : _zip(zip) {
Common::ArchiveMemberList files;
zip->listMembers(files);
for (Common::ArchiveMemberList::iterator i = files.begin(); i != files.end(); ++i) {
Common::Path filename = (*i)->getPathInArchive();
Common::String basename(filename.baseName());
if (basename.hasSuffixIgnoreCase(".snd")) {
int fileNum = atoi(basename.c_str() + basename.size() - 6);
Common::Path newName(Common::String::format("sound%d.snd", fileNum));
_filenames[newName] = filename;
}
}
}
SoundZip::~SoundZip() {
delete _zip;
}
bool SoundZip::hasFile(const Common::Path &path) const {
return _filenames.contains(path);
}
int SoundZip::listMembers(Common::ArchiveMemberList &list) const {
int total = 0;
for (FileMap::iterator i = _filenames.begin(); i != _filenames.end(); ++i) {
list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember((*i)._key, *this)));
++total;
}
return total;
}
const Common::ArchiveMemberPtr SoundZip::getMember(const Common::Path &path) const {
if (!hasFile(path))
return Common::ArchiveMemberPtr();
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
}
Common::SeekableReadStream *SoundZip::createReadStreamForMember(const Common::Path &path) const {
if (!_filenames.contains(path))
return nullptr;
return _zip->createReadStreamForMember(_filenames[path]);
}
} // End of namespace ZCode
} // End of namespace Glk

View File

@@ -0,0 +1,139 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_SOUND_FOLDER_H
#define GLK_ZCODE_SOUND_FOLDER_H
#include "glk/zcode/frotz_types.h"
#include "common/archive.h"
#include "common/fs.h"
#include "common/hashmap.h"
namespace Glk {
namespace ZCode {
/**
* Acts as an interface to an Infocom sound subfolder for the Lurking Horror or
* Sherlock. Any file which ends with a number and '.snd' will be accessible as
* 'sound<num>.snd' in the outer Glk layer
*/
class SoundSubfolder : public Common::Archive {
private:
Common::FSNode _folder;
typedef Common::HashMap<Common::Path, Common::String, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> FileMap;
FileMap _filenames;
private:
/**
* Constructor
*/
SoundSubfolder(const Common::FSNode &folder);
public:
/**
* Checks for a sound subfolder, and if so, instantiates the class for it
*/
static void check(const Common::FSNode &gameDir);
/**
* Check if a member with the given name is present in the Archive.
* Patterns are not allowed, as this is meant to be a quick File::exists()
* replacement.
*/
bool hasFile(const Common::Path &path) const override;
/**
* Add all members of the Archive to list.
* Must only append to list, and not remove elements from it.
*
* @return the number of names added to list
*/
int listMembers(Common::ArchiveMemberList &list) const override;
/**
* Returns a ArchiveMember representation of the given file.
*/
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
/**
* Create a stream bound to a member with the specified name in the
* archive. If no member with this name exists, 0 is returned.
* @return the newly created input stream
*/
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
};
/**
* Acts as an interface to a Zip file from if-archive for the Lurking Horror or
* Sherlock. Any file which ends with a number and '.snd' will be accessible as
* 'sound<num>.snd' in the outer Glk layer
*/
class SoundZip : public Common::Archive {
private:
Common::Archive *_zip;
typedef Common::HashMap<Common::Path, Common::Path, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> FileMap;
FileMap _filenames;
private:
/**
* Constructor
*/
SoundZip(Common::Archive *zip);
public:
/**
* Checks for a sound subfolder, and if so, instantiates the class for it
*/
static void check(const Common::FSNode &gameDir, Story story);
/**
* Destructor
*/
~SoundZip() override;
/**
* Check if a member with the given name is present in the Archive.
* Patterns are not allowed, as this is meant to be a quick File::exists()
* replacement.
*/
bool hasFile(const Common::Path &path) const override;
/**
* Add all members of the Archive to list.
* Must only append to list, and not remove elements from it.
*
* @return the number of names added to list
*/
int listMembers(Common::ArchiveMemberList &list) const override;
/**
* Returns a ArchiveMember representation of the given file.
*/
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
/**
* Create a stream bound to a member with the specified name in the
* archive. If no member with this name exists, 0 is returned.
* @return the newly created input stream
*/
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,444 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/windows.h"
#include "glk/zcode/zcode.h"
#include "glk/window_pair.h"
#include "glk/window_graphics.h"
#include "glk/window_text_buffer.h"
#include "glk/window_text_grid.h"
#include "glk/conf.h"
namespace Glk {
namespace ZCode {
Windows::Windows() : _lower(_windows[0]), _upper(_windows[1]), _background(nullptr), _cwin(0) {
}
size_t Windows::size() const {
return (g_vm->h_version < 6) ? 2 : 8;
}
Window &Windows::operator[](uint idx) {
assert(idx < size());
return _windows[idx];
}
void Windows::setup(bool isVersion6) {
MonoFontInfo &mi = g_vm->_conf->_monoInfo;
if (isVersion6) {
// For graphic games we have a background window covering the entire screen for greater
// flexibility of wher we draw pictures, and the lower and upper areas sit on top of them
_background = g_vm->glk_window_open(nullptr, 0, 0, wintype_Graphics, 0);
_background->setBackgroundColor(0xffffff);
Window &w = _windows[0];
w[X_SIZE] = g_vm->h_screen_width;
w[Y_SIZE] = g_vm->h_screen_height;
} else {
_lower = g_vm->glk_window_open(nullptr, 0, 0, wintype_TextBuffer, 0);
_upper = g_vm->glk_window_open(_lower, winmethod_Above | winmethod_Fixed, 0, wintype_TextGrid, 0);
_lower.update();
_upper.update();
g_vm->glk_set_window(_lower);
}
for (size_t idx = 0; idx < 8; ++idx) {
Window &w = _windows[idx];
w._windows = this;
w._index = idx;
w[FONT_NUMBER] = TEXT_FONT;
w[FONT_SIZE] = (mi._cellH << 8) | mi._cellW;
PropFontInfo &pi = g_conf->_propInfo;
w._quotes = pi._quotes;
w._dashes = pi._quotes;
w._spaces = pi._spaces;
}
}
void Windows::setWindow(int win) {
_cwin = win;
if (_windows[_cwin]._win)
g_vm->glk_set_window(_windows[_cwin]._win);
}
void Windows::showTextWindows() {
// For v6, drawing graphics brings them to the front (such for title screens). So check for it
const PairWindow *pairWin = dynamic_cast<const PairWindow *>(g_vm->glk_window_get_root());
if (g_vm->h_version == V6 && pairWin && dynamic_cast<GraphicsWindow *>(pairWin->_children.back())) {
// Yep, it's at the forefront. So since we're now drawing text, ensure all text windows are in front of it
for (uint idx = 0; idx < size(); ++idx) {
if (_windows[idx]) {
winid_t win = _windows[idx];
if (dynamic_cast<TextWindow *>(win))
win->bringToFront();
}
}
}
}
/*--------------------------------------------------------------------------*/
Window::Window() : _windows(nullptr), _win(nullptr), _quotes(0), _dashes(0), _spaces(0), _index(-1),
_currFont(TEXT_FONT), _prevFont(TEXT_FONT), _tempFont(TEXT_FONT), _currStyle(0), _oldStyle(0) {
Common::fill(_properties, _properties + TRUE_BG_COLOR + 1, 0);
_properties[Y_POS] = _properties[X_POS] = 1;
_properties[Y_CURSOR] = _properties[X_CURSOR] = 1;
_properties[FONT_NUMBER] = TEXT_FONT;
_properties[FONT_SIZE] = (8 << 8) | 8;
}
void Window::update() {
assert(_win);
int cellW = (g_vm->h_version < V5) ? g_vm->h_font_width : 1;
int cellH = (g_vm->h_version < V5) ? g_vm->h_font_height : 1;
_properties[X_POS] = _win->_bbox.left / cellW + 1;
_properties[Y_POS] = _win->_bbox.top / cellH + 1;
_properties[X_SIZE] = _win->_bbox.width() / cellW;
_properties[Y_SIZE] = _win->_bbox.height() / cellH;
Point pt = _win->getCursor();
_properties[X_CURSOR] = (g_vm->h_version != V6) ? pt.x + 1 : pt.x / cellW + 1;
_properties[Y_CURSOR] = (g_vm->h_version != V6) ? pt.y + 1 : pt.y / cellH + 1;
TextBufferWindow *win = dynamic_cast<TextBufferWindow *>(_win);
_properties[LEFT_MARGIN] = (win ? win->_ladjw : 0) / cellW;
_properties[RIGHT_MARGIN] = (win ? win->_radjw : 0) / cellW;
_properties[FONT_SIZE] = (g_conf->_monoInfo._cellH << 8) | g_conf->_monoInfo._cellW;
}
Window &Window::operator=(winid_t win) {
_win = win;
// Set the screen colors
if (win)
win->_stream->setZColors(g_vm->_defaultForeground, g_vm->_defaultBackground);
return *this;
}
void Window::ensureTextWindow() {
if (_win) {
// There's a window present, so make sure it's textual
if (!dynamic_cast<TextWindow *>(_win)) {
g_vm->glk_window_close(_win);
_win = nullptr;
createGlkWindow();
}
} else {
createGlkWindow();
}
_windows->showTextWindows();
}
void Window::setSize(const Point &newSize) {
checkRepositionLower();
_properties[X_SIZE] = newSize.x;
_properties[Y_SIZE] = newSize.y;
setSize();
}
void Window::setSize() {
if (_win) {
Point newSize(_properties[X_SIZE], _properties[Y_SIZE]);
if (g_vm->h_version < V5) {
newSize.x *= g_conf->_monoInfo._cellW;
newSize.y *= g_conf->_monoInfo._cellH;
}
_win->setSize(newSize);
}
}
void Window::setPosition(const Point &newPos) {
checkRepositionLower();
_properties[X_POS] = newPos.x;
_properties[Y_POS] = newPos.y;
setPosition();
}
void Window::setPosition() {
if (_win) {
Point newPos(_properties[X_POS] - 1, _properties[Y_POS] - 1);
if (g_vm->h_version < V5) {
newPos.x *= g_conf->_monoInfo._cellW;
newPos.y *= g_conf->_monoInfo._cellH;
}
_win->setPosition(newPos);
}
}
void Window::setCursor(const Point &newPos) {
int x = newPos.x, y = newPos.y;
if (y < 0) {
// Cursor on/off
if (y == -2)
g_vm->_events->showMouseCursor(true);
else if (y == -1)
g_vm->_events->showMouseCursor(false);
return;
}
if (!x || !y) {
update();
if (!x)
x = _properties[X_CURSOR];
if (!y)
y = _properties[Y_CURSOR];
}
_properties[X_CURSOR] = x;
_properties[Y_CURSOR] = y;
setCursor();
}
void Window::setCursor() {
if (dynamic_cast<TextGridWindow *>(_win)) {
g_vm->glk_window_move_cursor(_win, (_properties[X_CURSOR] - 1),
(_properties[Y_CURSOR] - 1));
}
}
void Window::clear() {
if (_win)
g_vm->glk_window_clear(_win);
if (_windows->_background) {
Rect r = getBounds();
_windows->_background->fillRect(g_conf->_windowColor, r);
}
}
void Window::updateColors() {
if (_win)
_win->_stream->setZColors(_properties[TRUE_FG_COLOR], _properties[TRUE_BG_COLOR]);
}
void Window::updateColors(uint fore, uint back) {
_properties[TRUE_FG_COLOR] = fore;
_properties[TRUE_BG_COLOR] = back;
updateColors();
}
uint Window::setFont(uint font) {
int result = 0;
switch (font) {
case PREVIOUS_FONT:
// previous font
_tempFont = _currFont;
_currFont = _prevFont;
_prevFont = _tempFont;
setStyle();
result = _currFont;
break;
case TEXT_FONT:
case GRAPHICS_FONT:
case FIXED_WIDTH_FONT:
_prevFont = _currFont;
_currFont = font;
setStyle();
result = _prevFont;
break;
case PICTURE_FONT: // picture font, undefined per 1.1
default: // unavailable
result = 0;
break;
}
PropFontInfo &pi = g_conf->_propInfo;
if (_currFont == GRAPHICS_FONT) {
_quotes = pi._quotes;
_dashes = pi._dashes;
_spaces = pi._spaces;
pi._quotes = 0;
pi._dashes = 0;
pi._spaces = 0;
} else {
pi._quotes = _quotes;
pi._dashes = _dashes;
pi._spaces = _spaces;
}
_properties[FONT_NUMBER] = font;
return result;
}
void Window::setStyle(int style) {
if (style == 0)
_currStyle = 0;
else if (style != -1)
// not tickle time
_currStyle |= style;
if (g_vm->h_flags & FIXED_FONT_FLAG || _currFont == FIXED_WIDTH_FONT || _currFont == GRAPHICS_FONT)
style = _currStyle | FIXED_WIDTH_STYLE;
else
style = _currStyle;
if (g_vm->gos_linepending && _windows->currWin() == g_vm->gos_linewin)
return;
_currStyle = style;
updateStyle();
}
void Window::updateStyle() {
if (!_win)
return;
uint style = _currStyle;
if (style & REVERSE_STYLE)
setReverseVideo(true);
if (style & FIXED_WIDTH_STYLE) {
if (_currFont == GRAPHICS_FONT)
_win->_stream->setStyle(style_User1); // character graphics
else if (style & BOLDFACE_STYLE && style & EMPHASIS_STYLE)
_win->_stream->setStyle(style_BlockQuote); // monoz
else if (style & EMPHASIS_STYLE)
_win->_stream->setStyle(style_Alert); // monoi
else if (style & BOLDFACE_STYLE)
_win->_stream->setStyle(style_Subheader); // monob
else
_win->_stream->setStyle(style_Preformatted); // monor
MonoFontInfo &fi = g_vm->_conf->_monoInfo;
_properties[FONT_SIZE] = (fi._cellH << 8) | fi._cellW;
} else {
if (style & BOLDFACE_STYLE && style & EMPHASIS_STYLE)
_win->_stream->setStyle(style_Note); // propz
else if (style & EMPHASIS_STYLE)
_win->_stream->setStyle(style_Emphasized); // propi
else if (style & BOLDFACE_STYLE)
_win->_stream->setStyle(style_Header); // propb
else
_win->_stream->setStyle(style_Normal); // propr
PropFontInfo &fi = g_vm->_conf->_propInfo;
_properties[FONT_SIZE] = (fi._cellH << 8) | fi._cellW;
}
if (_currStyle == 0)
setReverseVideo(false);
}
Rect Window::getBounds() const {
if (_win)
return _win->_bbox;
if (g_vm->h_version < V5)
return Rect((_properties[X_POS] - 1) * g_vm->h_font_width, (_properties[Y_POS] - 1) * g_vm->h_font_height,
(_properties[X_POS] - 1 + _properties[X_SIZE]) * g_vm->h_font_width,
(_properties[Y_POS] - 1 + _properties[Y_SIZE]) * g_vm->h_font_height);
return Rect(_properties[X_POS] - 1, _properties[Y_POS] - 1, _properties[X_POS] - 1 + _properties[X_SIZE],
_properties[Y_POS] - 1 + _properties[Y_SIZE]);
}
void Window::setReverseVideo(bool reverse) {
_win->_stream->setReverseVideo(reverse);
}
void Window::createGlkWindow() {
if (g_vm->h_version == V6)
_windows->showTextWindows();
// Create a new window
if (_index != 0 || (_currStyle & FIXED_WIDTH_STYLE)) {
// Text grid window
_win = g_vm->glk_window_open(g_vm->glk_window_get_root(),
winmethod_Arbitrary | winmethod_Fixed, 0, wintype_TextGrid, 0);
} else {
// text buffer window
_win = g_vm->glk_window_open(g_vm->glk_window_get_root(),
winmethod_Arbitrary | winmethod_Fixed, 0, wintype_TextBuffer, 0);
}
updateStyle();
setSize();
setPosition();
setCursor();
g_vm->glk_set_window(_win);
}
const uint &Window::getProperty(WindowProperty propType) {
if (_win)
update();
return _properties[propType];
}
void Window::setProperty(WindowProperty propType, uint value) {
switch (propType) {
case TRUE_FG_COLOR:
case TRUE_BG_COLOR:
_properties[propType] = value;
updateColors();
break;
default:
_properties[propType] = value;
}
}
void Window::checkRepositionLower() {
if (&_windows->_lower == this && _win) {
PairWindow *parent = dynamic_cast<PairWindow *>(_win->_parent);
if (!parent)
error("Parent was not a pair window");
// Ensure the parent pair window is flagged as having children at arbitrary positions,
// just in case it isn't already
parent->_dir = winmethod_Arbitrary;
}
}
bool Window::imageDraw(uint image, ImageAlign align, int val) {
ensureTextWindow();
return g_vm->glk_image_draw(_win, image, align, val);
}
bool Window::imageDrawScaled(uint image, int val1, int val2, uint width, uint height) {
ensureTextWindow();
return g_vm->glk_image_draw_scaled(_win, image, val1, val2, width, height);
}
} // End of namespace ZCode
} // End of namespace Glk

297
engines/glk/zcode/windows.h Normal file
View File

@@ -0,0 +1,297 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_WINDOWS
#define GLK_ZCODE_WINDOWS
#include "glk/windows.h"
#include "glk/zcode/frotz_types.h"
namespace Glk {
namespace ZCode {
#include "glk/windows.h"
#include "glk/utils.h"
enum WindowProperty {
Y_POS = 0, X_POS = 1, Y_SIZE = 2, X_SIZE = 3, Y_CURSOR = 4, X_CURSOR = 5,
LEFT_MARGIN = 6, RIGHT_MARGIN = 7, NEWLINE_INTERRUPT = 8, INTERRUPT_COUNTDOWN = 9,
TEXT_STYLE = 10, COLOUR_DATA = 11, FONT_NUMBER = 12, FONT_SIZE = 13, ATTRIBUTES = 14,
LINE_COUNT = 15, TRUE_FG_COLOR = 16, TRUE_BG_COLOR = 17
};
class Windows;
/**
* Represents one of the virtual windows
*/
class Window {
friend class Windows;
/**
* Stub class for accessing window properties via the square brackets operator
*/
class PropertyAccessor {
private:
Window *_owner;
WindowProperty _prop;
public:
/**
* Constructor
*/
PropertyAccessor(Window *owner, WindowProperty prop) : _owner(owner), _prop(prop) {}
/**
* Get
*/
operator uint() const {
return _owner->getProperty(_prop);
}
/**
* Set
*/
PropertyAccessor &operator=(uint val) {
_owner->setProperty(_prop, val);
return *this;
}
};
private:
Windows *_windows;
int _index;
winid_t _win;
uint _properties[TRUE_BG_COLOR + 1];
private:
/**
* Get a property value
*/
const uint &getProperty(WindowProperty propType);
/**
* Set a property value
*/
void setProperty(WindowProperty propType, uint value);
/**
* Called when trying to reposition or resize windows. Does special handling for the lower window
*/
void checkRepositionLower();
/**
* Updates the local window properties based on an attached Glk window
*/
void update();
/**
* Creates a new Glk window to attach to the window
*/
void createGlkWindow();
/**
* Updates the current font/style
*/
void updateStyle();
/**
* Get the bounding area for the window
*/
Rect getBounds() const;
public:
int _currFont;
int _prevFont;
int _tempFont;
int _currStyle;
int _oldStyle;
int _quotes;
int _dashes;
int _spaces;
public:
/**
* Constructor
*/
Window();
/**
* Assignment operator
*/
Window &operator=(winid_t win);
/**
* Cast operator for getting a Glk window
*/
operator winid_t() const {
assert(_win);
return _win;
}
/**
* Equality operator
*/
inline bool operator==(const Window &rhs) { return this == &rhs; }
/**
* Cast operator for testing if the window has a proper Glk window attached to it
*/
operator bool() const { return _win != nullptr; }
/**
* Property accessor
*/
PropertyAccessor operator[](WindowProperty propType) { return PropertyAccessor(this, propType); }
/**
* Ensures that the underlying window is a Glk text window
*/
void ensureTextWindow();
/**
* Set the window size
*/
void setSize(const Point &newSize);
/**
* Copys a window's size to the underlying Glk one, if present
*/
void setSize();
/**
* Set the position of a window
*/
void setPosition(const Point &newPos);
/**
* Copys a window's position to the underlying Glk one, if present
*/
void setPosition();
/**
* Set the cursor position
*/
void setCursor(const Point &newPos);
/**
* Copys a window's position to the underlying Glk one, if present
*/
void setCursor();
/**
* Clear the window
*/
void clear();
/**
* Update colors for the window
*/
void updateColors();
/**
* Update colors for the window
*/
void updateColors(uint fore, uint back);
/**
* Set the font
*/
uint setFont(uint font);
/**
* Set the textstyle
*/
void setStyle(int style = -1);
/**
* Set reverse video
*/
void setReverseVideo(bool reverse);
/**
* Draw an image
*/
bool imageDraw(uint image, ImageAlign align, int val);
/**
* Draw a scaled image
*/
bool imageDrawScaled(uint image, int val1, int val2, uint width, uint height);
};
/**
* Windows manager
*/
class Windows {
private:
Window _windows[8];
public:
winid_t _background;
Window &_lower;
Window &_upper;
int _cwin;
public:
/**
* Constructor
*/
Windows();
/**
* Returns the number of allowable windows
*/
size_t size() const;
/**
* Array access
*/
Window &operator[](uint idx);
/**
* Setup the screen
*/
void setup(bool isVersion6);
/**
* Set current window
*/
void setWindow(int win);
/**
* Get the current window
*/
Window &currWin() {
return _windows[_cwin];
}
/**
* Get the current window pointer
*/
winid_t glkWin() const {
assert(_windows[_cwin]._win);
return _windows[_cwin]._win;
}
/**
* Places any text windows in front of the background in V6 games
*/
void showTextWindows();
};
} // End of namespace ZCode
} // End of namespace Glk
#endif

151
engines/glk/zcode/zcode.cpp Normal file
View File

@@ -0,0 +1,151 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/zcode/zcode.h"
#include "glk/zcode/frotz_types.h"
#include "glk/zcode/screen.h"
#include "glk/zcode/quetzal.h"
#include "engines/util.h"
#include "common/config-manager.h"
#include "common/translation.h"
namespace Glk {
namespace ZCode {
ZCode *g_vm;
ZCode::ZCode(OSystem *syst, const GlkGameDescription &gameDesc) :
Processor(syst, gameDesc) {
g_vm = this;
}
ZCode::~ZCode() {
reset_memory();
}
void ZCode::initGraphicsMode() {
_gameFile.seek(0);
byte version = _gameFile.readByte();
if (version == 6) {
// The V6 games have graphics that expect 320x200 mode
Graphics::PixelFormat pixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
initGraphics(320, 200, &pixelFormat);
} else {
GlkEngine::initGraphicsMode();
}
}
Screen *ZCode::createScreen() {
return new FrotzScreen();
}
void ZCode::runGame() {
story_fp = &_gameFile;
initialize();
// If save was selected from the launcher, handle loading it
int saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
if (saveSlot != -1) {
int loadResult = loadGameState(saveSlot).getCode() == Common::kNoError ? 2 : -1;
if (h_version <= V3)
branch(loadResult);
else
store(loadResult);
}
// Game loop
interpret();
if (!shouldQuit()) {
flush_buffer();
glk_exit();
}
}
void ZCode::initialize() {
// Call process initialization
Processor::initialize();
// Restart the game
z_restart();
}
Common::Error ZCode::loadGameState(int slot) {
FileReference ref(slot, "", fileusage_SavedGame | fileusage_TextMode);
strid_t file = _streams->openFileStream(&ref, filemode_Read);
if (file == nullptr)
return Common::kReadingFailed;
Quetzal q(story_fp);
bool success = q.restore(*file, this) == 2;
if (success) {
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 {
error("Error reading save file");
}
return Common::kNoError;
}
Common::Error ZCode::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
Common::String msg;
FileReference ref(slot, desc, fileusage_BinaryMode | fileusage_SavedGame);
strid_t file = _streams->openFileStream(&ref, filemode_Write);
if (file == nullptr)
return Common::kWritingFailed;
Quetzal q(story_fp);
bool success = q.save(*file, this, desc);
file->close();
if (!success)
print_string_uni(_("Error writing save file\n").u32_str());
return Common::kNoError;
}
} // End of namespace ZCode
} // End of namespace Glk

111
engines/glk/zcode/zcode.h Normal file
View File

@@ -0,0 +1,111 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_ZCODE_ZCODE
#define GLK_ZCODE_ZCODE
#include "glk/zcode/processor.h"
namespace Glk {
namespace ZCode {
class FrotzScreen;
/**
* Frotz interpreter for Z-code games
*/
class ZCode : public Processor {
friend class FrotzScreen;
protected:
/**
* Setup the video mode
*/
void initGraphicsMode() override;
/**
* Create the screen class
*/
Screen *createScreen() override;
public:
/**
* Constructor
*/
ZCode(OSystem *syst, const GlkGameDescription &gameDesc);
/**
* Destructor
*/
~ZCode() override;
/**
* Initialization
*/
void initialize();
/**
* Returns the running interpreter type
*/
InterpreterType getInterpreterType() const override { return INTERPRETER_ZCODE; }
/**
* Execute the game
*/
void runGame() override;
/**
* Indicates whether an autosave can currently be saved.
*/
bool canSaveAutosaveCurrently() override {
/* ZCode saves also include the execution stack.
* So I don't know how to do autosaves in the background
* without ending up with an invalid stack state
*/
return false;
}
/**
* Load a savegame from a given slot
*/
Common::Error loadGameState(int slot) override;
/**
* Save the game to a given slot
*/
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
/**
* Loading method not used for Frotz sub-engine
*/
Common::Error readSaveData(Common::SeekableReadStream *rs) override { return Common::kReadingFailed; }
/**
* Saving method not used for Frotz sub-engine
*/
Common::Error writeGameData(Common::WriteStream *ws) override { return Common::kWritingFailed; }
};
extern ZCode *g_vm;
} // End of namespace ZCode
} // End of namespace Glk
#endif