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,123 @@
/* 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/advsys/advsys.h"
#include "common/translation.h"
#include "common/config-manager.h"
namespace Glk {
namespace AdvSys {
void AdvSys::runGame() {
// Check for savegame
_saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
if (!initialize()) {
GUIErrorMessage(_("Could not start AdvSys game"));
return;
}
// Outer play loop - this loop re-iterates if a game is restarted
while (!shouldQuit()) {
// Run game startup
execute(_initCodeOffset);
if (_saveSlot != -1) {
Common::ErrorCode err = loadGameState(_saveSlot).getCode();
_saveSlot = -1;
if (err != Common::kNoError)
print(_("Sorry, the savegame couldn't be restored"));
else
_pendingLine = "look"; // Do a look action after loading the savegame
}
// Gameplay loop
while (!shouldQuit() && !shouldRestart()) {
// Run update code
execute(_updateCodeOffset);
// Get and parse a single line
if (getInput()) {
if (singleAction()) {
while (!shouldQuit() && nextCommand() && singleAction()) {}
}
}
}
}
deinitialize();
}
bool AdvSys::initialize() {
// Create a Glk window for the game
if (!GlkInterface::initialize())
return false;
// Load the game's header
if (!Game::init(&_gameFile))
return false;
return true;
}
void AdvSys::deinitialize() {
}
bool AdvSys::singleAction() {
// Do the before code
switch (execute(_beforeOffset)) {
case ABORT:
// Script aborted
return false;
case CHAIN:
// Execute the action handler
if (execute(getActionField(getVariable(V_ACTION), A_CODE)) == ABORT)
return false;
// fall through
case FINISH:
// Do the after code
if (execute(_afterOffset) == ABORT)
return false;
break;
default:
break;
}
return true;
}
Common::Error AdvSys::readSaveData(Common::SeekableReadStream *rs) {
if (rs->size() != (int)_saveSize)
return Common::kReadingFailed;
rs->read(_saveArea, rs->size());
return Common::kNoError;
}
Common::Error AdvSys::writeGameData(Common::WriteStream *ws) {
ws->write(_saveArea, _saveSize);
return Common::kNoError;
}
} // End of namespace AdvSys
} // End of namespace Glk

View File

@@ -0,0 +1,85 @@
/* 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/>.
*
*/
/* Based on AdvSys interpreter 1.2 */
#ifndef GLK_ADVSYS_ADVSYS
#define GLK_ADVSYS_ADVSYS
#include "common/scummsys.h"
#include "glk/advsys/vm.h"
namespace Glk {
namespace AdvSys {
/**
* AdvSys game interpreter
*/
class AdvSys : public VM {
private:
/**
* Engine initialization
*/
bool initialize();
/**
* Engine cleanup
*/
void deinitialize();
/**
* Handle a single action
*/
bool singleAction();
public:
/**
* Constructor
*/
AdvSys(OSystem *syst, const GlkGameDescription &gameDesc) : VM(syst, gameDesc) {}
/**
* Run the game
*/
void runGame() override;
/**
* Returns the running interpreter type
*/
InterpreterType getInterpreterType() const override {
return INTERPRETER_ADVSYS;
}
/**
* Load a savegame from the passed Quetzal file chunk stream
*/
Common::Error readSaveData(Common::SeekableReadStream *rs) override;
/**
* Save the game. The passed write stream represents access to the UMem chunk
* in the Quetzal save file that will be created
*/
Common::Error writeGameData(Common::WriteStream *ws) override;
};
} // End of namespace AdvSys
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,32 @@
/* 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_ADVSYS_DEFINITIONS
#define GLK_ADVSYS_DEFINITIONS
namespace Glk {
namespace AdvSys {
} // End of namespace AdvSys
} // End of namespace Glk
#endif

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/>.
*
*/
#include "glk/advsys/detection.h"
#include "glk/advsys/detection_tables.h"
#include "glk/advsys/game.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/md5.h"
#include "engines/game.h"
namespace Glk {
namespace AdvSys {
void AdvSysMetaEngine::getSupportedGames(PlainGameList &games) {
for (const PlainGameDescriptor *pd = ADVSYS_GAME_LIST; pd->gameId; ++pd)
games.push_back(*pd);
}
const GlkDetectionEntry* AdvSysMetaEngine::getDetectionEntries() {
return ADVSYS_GAMES;
}
GameDescriptor AdvSysMetaEngine::findGame(const char *gameId) {
for (const PlainGameDescriptor *pd = ADVSYS_GAME_LIST; pd->gameId; ++pd) {
if (!strcmp(gameId, pd->gameId))
return *pd;
}
return GameDescriptor::empty();
}
bool AdvSysMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
const char *const EXTENSIONS[] = { ".dat", 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 = false;
for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext)
hasExt = filename.hasSuffixIgnoreCase(*ext);
if (!hasExt)
continue;
Common::File gameFile;
if (!gameFile.open(*file))
continue;
Header hdr(&gameFile);
if (!hdr._valid)
continue;
gameFile.seek(0);
Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
uint32 filesize = gameFile.size();
// Scan through the AdvSys game list for a match
const GlkDetectionEntry *p = ADVSYS_GAMES;
while (p->_md5 && p->_filesize != filesize && md5 != p->_md5)
++p;
if (!p->_gameId) {
const PlainGameDescriptor &desc = ADVSYS_GAME_LIST[0];
gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
} else {
// Found a match
PlainGameDescriptor gameDesc = findGame(p->_gameId);
gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, filename));
}
}
return !gameList.empty();
}
void AdvSysMetaEngine::detectClashes(Common::StringMap &map) {
for (const PlainGameDescriptor *pd = ADVSYS_GAME_LIST; pd->gameId; ++pd) {
if (map.contains(pd->gameId))
error("Duplicate game Id found - %s", pd->gameId);
map[pd->gameId] = "";
}
}
} // End of namespace AdvSys
} // End of namespace Glk

View File

@@ -0,0 +1,64 @@
/* 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_ADVSYS_DETECTION
#define GLK_ADVSYS_DETECTION
#include "common/fs.h"
#include "common/hash-str.h"
#include "engines/game.h"
#include "glk/detection.h"
namespace Glk {
namespace AdvSys {
class AdvSysMetaEngine {
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 AdvSys
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,52 @@
/* 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 "engines/game.h"
namespace Glk {
namespace AdvSys {
const PlainGameDescriptor ADVSYS_GAME_LIST[] = {
{ "advsys", "AdvSys Game" },
{ "bustedadvsys", "Busted!" },
{ "starshipcolumbus", "Starship Columbus" },
{ "elves87", "Elves '87" },
{ "keytotime", "The Key to Time" },
{ "onehand", "The Sound of One Hand Clapping" },
{ "pirating", "Pirating" },
{ nullptr, nullptr }
};
const GlkDetectionEntry ADVSYS_GAMES[] = {
DT_ENTRY0("bustedadvsys", "2246a2686a07c714868680eaf980ece9", 79091),
DT_ENTRY0("starshipcolumbus", "120d7041dfa000c9a313a8b0ae9cef33", 76032),
DT_ENTRY0("elves87", "746963e82552f95b5e743fe24ecd1ec3", 77947),
DT_ENTRY0("keytotime", "892217ab8d902a732e82c55efd22931d", 24941),
DT_ENTRY0("onehand", "3a2a3cc24709ff3272f3a15d09b5e63e", 95762),
DT_ENTRY0("pirating", "e55fff2ac51a8a16b979541e8d3210d8", 29529),
DT_END_MARKER
};
} // End of namespace AdvSys
} // End of namespace Glk

388
engines/glk/advsys/game.cpp Normal file
View File

@@ -0,0 +1,388 @@
/* 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/advsys/game.h"
#include "glk/advsys/definitions.h"
#include "common/memstream.h"
namespace Glk {
namespace AdvSys {
void Decrypter::decrypt(byte *data, size_t size) {
for (size_t idx = 0; idx < size; ++idx, ++data)
*data = ~(*data + 30);
}
/*--------------------------------------------------------------------------*/
#define HEADER_SIZE 62
bool Header::init(Common::SeekableReadStream *s) {
_valid = false;
byte data[HEADER_SIZE];
// Read in the data
if (s->read(data, HEADER_SIZE) != HEADER_SIZE)
return false;
decrypt(data, HEADER_SIZE);
Common::MemoryReadStream ms(data, HEADER_SIZE, DisposeAfterUse::NO);
// Validate the header
_valid = !strncmp((const char *)data + 2, "ADVSYS", 6);
if (!_valid)
return false;
_size = ms.readUint16LE();
ms.skip(6);
_headerVersion = ms.readUint16LE();
_name = Common::String((const char *)data + 10, (const char *)data + 28);
ms.skip(18);
_version = ms.readUint16LE();
_wordTableOffset = ms.readUint16LE();
_wordTypeTableOffset = ms.readUint16LE();
_objectTableOffset = ms.readUint16LE();
_actionTableOffset = ms.readUint16LE();
_variableTableOffset = ms.readUint16LE();
_dataSpaceOffset = ms.readUint16LE();
_codeSpaceOffset = ms.readUint16LE();
_dataBlockOffset = ms.readUint16LE();
_messageBlockOffset = ms.readUint16LE();
_initCodeOffset = ms.readUint16LE();
_updateCodeOffset = ms.readUint16LE();
_beforeOffset = ms.readUint16LE();
_afterOffset = ms.readUint16LE();
_errorHandlerOffset = ms.readUint16LE();
_saveAreaOffset = ms.readUint16LE();
_saveSize = ms.readUint16LE();
return true;
}
/*--------------------------------------------------------------------------*/
#define MAX_VERSION 102
#define WORD_SIZE 6
/**
* Property flags
*/
enum PropertyFlag {
P_CLASS = 0x8000
};
/**
* Link fields
*/
enum LinkField {
L_DATA = 0,
L_NEXT = 2,
L_SIZE = 4
};
Game::Game() : Header(), _stream(nullptr), _restartFlag(false), _residentOffset(0), _wordCount(0),
_objectCount(0), _actionCount(0), _variableCount(0), _wordTable(nullptr), _wordTypeTable(nullptr),
_objectTable(nullptr), _actionTable(nullptr), _variableTable(nullptr), _saveArea(nullptr),
_msgBlockNum(-1), _msgBlockOffset(0) {
_msgCache.resize(MESSAGE_CACHE_SIZE);
for (int idx = 0; idx < MESSAGE_CACHE_SIZE; ++idx)
_msgCache[idx] = new CacheEntry();
}
Game::~Game() {
for (int idx = 0; idx < MESSAGE_CACHE_SIZE; ++idx)
delete _msgCache[idx];
}
bool Game::init(Common::SeekableReadStream *s) {
// Store a copy of the game file stream
_stream = s;
// Load the header
s->seek(0);
if (!Header::init(s))
return false;
if (_headerVersion < 101 || _headerVersion > MAX_VERSION)
error("Wrong version number");
// Load the needed resident game data and decrypt it
_residentOffset = _dataBlockOffset * 512;
s->seek(_residentOffset);
_data.resize(_size);
if (!s->read(&_data[0], _size))
return false;
decrypt(&_data[0], _size);
_wordTable = &_data[_wordTableOffset];
_wordTypeTable = &_data[_wordTypeTableOffset - 1];
_objectTable = &_data[_objectTableOffset];
_actionTable = &_data[_actionTableOffset];
_variableTable = &_data[_variableTableOffset];
_saveArea = &_data[_saveAreaOffset];
_dataSpace = &_data[_dataSpaceOffset];
_codeSpace = &_data[_codeSpaceOffset];
_wordCount = READ_LE_UINT16(_wordTable);
_objectCount = READ_LE_UINT16(_objectTable);
_actionCount = READ_LE_UINT16(_actionTable);
_variableCount = READ_LE_UINT16(_variableTable);
setVariable(V_OCOUNT, _objectCount);
return true;
}
void Game::restart() {
_stream->seek(_residentOffset + _saveAreaOffset);
_stream->read(_saveArea, _saveSize);
decrypt(_saveArea, _saveSize);
setVariable(V_OCOUNT, _objectCount);
_restartFlag = true;
}
bool Game::shouldRestart() {
bool result = _restartFlag;
_restartFlag = false;
return result;
}
void Game::saveGameData(Common::WriteStream &ws) {
ws.write(_saveArea, _saveSize);
}
void Game::loadGameData(Common::ReadStream &rs) {
rs.read(_saveArea, _saveSize);
}
int Game::findWord(const Common::String &word) const {
// Limit the word to the maximum allowable size
Common::String w(word.c_str(), MIN(word.size(), (uint)WORD_SIZE));
// Iterate over the dictionary for the word
for (int idx = 1; idx <= _wordCount; ++idx) {
int wordOffset = READ_LE_UINT16(_wordTable + idx * 2);
if (w == (const char *)_dataSpace + wordOffset + 2)
return readWord(wordOffset);
}
return NIL;
}
int Game::checkVerb(const Common::Array<int> &verbs) {
// Iterate through the actions
for (int idx = 1; idx <= _actionCount; ++idx) {
if (hasVerb(idx, verbs))
return idx;
}
return NIL;
}
int Game::findAction(const Common::Array<int> &verbs, int preposition, int flag) {
// Iterate through the actions
for (int idx = 1; idx <= _actionCount; ++idx) {
if ((preposition && !hasPreposition(idx, preposition)) || !hasVerb(idx, verbs))
continue;
int mask = ~getActionByte(idx, A_MASK);
if ((flag & mask) == (getActionByte(idx, A_FLAG) & mask))
return idx;
}
return NIL;
}
int Game::getObjectProperty(int obj, int prop) {
int field;
for (; obj; obj = getObjectField(obj, O_CLASS)) {
if ((field = findProperty(obj, prop)) != 0)
return getObjectField(obj, field);
}
return NIL;
}
int Game::setObjectProperty(int obj, int prop, int val) {
int field;
for (; obj; obj = getObjectField(obj, O_CLASS)) {
if ((field = findProperty(obj, prop)) != 0)
return setObjectField(obj, field, val);
}
return NIL;
}
int Game::getObjectLocation(int obj) const {
if (obj < 1 || obj > _objectCount)
error("Invalid object number %d", obj);
return READ_LE_UINT16(_objectTable + obj * 2);
}
int Game::getActionLocation(int action) const {
if (action < 1 || action > _actionCount)
error("Invalid action number %d", action);
return READ_LE_UINT16(_actionTable + action * 2);
}
int Game::getVariable(int variableNum) {
if (variableNum < 1 || variableNum > _variableCount)
error("Invalid ariable number %d", variableNum);
return READ_LE_UINT16(_variableTable + variableNum * 2);
}
void Game::setVariable(int variableNum, int value) {
if (variableNum < 1 || variableNum > _variableCount)
error("Invalid ariable number %d", variableNum);
WRITE_LE_UINT16(_variableTable + variableNum * 2, value);
}
int Game::findProperty(int obj, int prop) const {
int nProp = getObjectField(obj, O_NPROPERTIES);
for (int idx = 0, p = 0; idx < nProp; ++idx, p += 4) {
if ((getObjectField(obj, O_PROPERTIES + p) & ~P_CLASS) == prop)
return O_PROPERTIES + p + 2;
}
return NIL;
}
bool Game::hasNoun(int obj, int noun) const {
for (; obj; obj = getObjectField(obj, O_CLASS)) {
if (inList(getObjectField(obj, O_NOUNS), noun))
return true;
}
return false;
}
bool Game::hasAdjective(int obj, int adjective) const {
for (; obj; obj = getObjectField(obj, O_CLASS)) {
if (inList(getObjectField(obj, O_ADJECTIVES), adjective))
return true;
}
return false;
}
bool Game::hasVerb(int act, const Common::Array<int> &verbs) const {
// Get the list of verbs
int link = getActionField(act, A_VERBS);
// Look for the verb
for (; link; link = readWord(link + L_NEXT)) {
Common::Array<int>::const_iterator verb = verbs.begin();
int word = readWord(link + L_DATA);
for (; verb < verbs.end() && word; ++verb, word = readWord(word + L_NEXT)) {
if (*verb != readWord(word + L_DATA))
break;
}
if (verb == verbs.end() && !word)
return true;
}
return false;
}
bool Game::inList(int link, int word) const {
for (; link; link = readWord(link + L_NEXT)) {
if (word == readWord(link + L_DATA))
return true;
}
return false;
}
Common::String Game::readString(int msg) {
// Get the block to use, and ensure it's loaded
_msgBlockNum = msg >> 7;
_msgBlockOffset = (msg & 0x7f) << 2;
readMsgBlock();
// Read the string
Common::String result;
char c;
while ((c = readMsgChar()) != '\0')
result += c;
return result;
}
char Game::readMsgChar() {
if (_msgBlockOffset >= MESSAGE_BLOCK_SIZE) {
// Move to the next block
++_msgBlockNum;
_msgBlockOffset = 0;
readMsgBlock();
}
// Return next character
return _msgCache[0]->_data[_msgBlockOffset++];
}
void Game::readMsgBlock() {
CacheEntry *ce;
// Check to see if the specified block is in the cache
for (int idx = 0; idx < MESSAGE_CACHE_SIZE; ++idx) {
if (_msgCache[idx]->_blockNum == _msgBlockNum) {
// If it's not already at the top of the list, move it there to ensure
// it'll be last to be unloaded as new blocks are loaded in
if (idx != 0) {
ce = _msgCache[idx];
_msgCache.remove_at(idx);
_msgCache.insert_at(0, ce);
}
return;
}
}
// At this point we need to load a new block in. Discard the block at the end
// and move it to the start for storing the new block to load
ce = _msgCache.back();
_msgCache.remove_at(_msgCache.size() - 1);
_msgCache.insert_at(0, ce);
// Load the new block
ce->_blockNum = _msgBlockNum;
_stream->seek((_messageBlockOffset + _msgBlockNum) << 9);
if (_stream->read(&ce->_data[0], MESSAGE_BLOCK_SIZE) != MESSAGE_BLOCK_SIZE)
error("Error reading message block");
// Decode the loaded block
for (int idx = 0; idx < MESSAGE_BLOCK_SIZE; ++idx)
ce->_data[idx] = (ce->_data[idx] + 30) & 0xff;
}
} // End of namespace AdvSys
} // End of namespace Glk

377
engines/glk/advsys/game.h Normal file
View File

@@ -0,0 +1,377 @@
/* 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_ADVSYS_GAME
#define GLK_ADVSYS_GAME
#include "common/array.h"
#include "common/stream.h"
namespace Glk {
namespace AdvSys {
#define NIL 0
#define MESSAGE_CACHE_SIZE 8
#define MESSAGE_BLOCK_SIZE 512
/**
* Actions
*/
enum Action {
A_VERBS = 0,
A_PREPOSITIONS = 2,
A_FLAG = 4,
A_MASK = 5,
A_CODE = 6,
A_SIZE = 8
};
/**
* Word types
*/
enum WordType {
WT_UNKNOWN = 0,
WT_VERB = 1,
WT_NOUN = 2,
WT_ADJECTIVE = 3,
WT_PREPOSITION = 4,
WT_CONJUNCTION = 5,
WT_ARTICLE = 6
};
/**
* Object fields
*/
enum ObjectField {
O_CLASS = 0,
O_NOUNS = 2,
O_ADJECTIVES = 4,
O_NPROPERTIES = 6,
O_PROPERTIES = 8,
O_SIZE = 8
};
/**
* Built-in variables
*/
enum Variable {
V_ACTOR = 1, ///< Actor noun phrase number
V_ACTION = 2, ///< Action from phrase
V_DOBJECT = 3, ///< First direct object noun phrase number
V_NDOBJECTS = 4, ///< Number of direct object noun phrases
V_IOBJECT = 5, ///< Indirect object noun phrase number
V_OCOUNT = 6 ///< Total object count
};
/**
* Data decryption
*/
class Decrypter {
public:
/**
* Decrypt a data block
*/
static void decrypt(byte *data, size_t size);
};
/**
* AdvSys game header
*/
class Header : public Decrypter {
public:
bool _valid; ///< Signals whether header is valid
size_t _size; ///< Resident size in bytes
uint _headerVersion; ///< Header structure version
Common::String _name; ///< Adventure name
uint _version; ///< Adventure version
uint _wordTableOffset; ///< Word table offset
uint _wordTypeTableOffset; ///< Word type table offset
uint _objectTableOffset; ///< Object table offset
uint _actionTableOffset; ///< Action table offset
uint _variableTableOffset; ///< Variable table offset
uint _dataSpaceOffset; ///< Data space offset
uint _codeSpaceOffset; ///< Code space offset
uint _dataBlockOffset; ///< First data block offset
uint _messageBlockOffset; ///< First message block offset
uint _initCodeOffset; ///< Initialization code offset
uint _updateCodeOffset; ///< Update code offset
uint _beforeOffset; ///< Code offset before verb handler
uint _afterOffset; ///< Code offset after verb handler
uint _errorHandlerOffset; ///< Error handler code offset
uint _saveAreaOffset; ///< Save area offset
uint _saveSize; ///< Save area size
public:
/**
* Constructor
*/
Header() : _valid(false), _size(0), _headerVersion(0), _version(0), _wordTableOffset(0),
_wordTypeTableOffset(0), _objectTableOffset(0), _actionTableOffset(0), _variableTableOffset(0),
_dataSpaceOffset(0), _codeSpaceOffset(0), _dataBlockOffset(0), _messageBlockOffset(0),
_initCodeOffset(0), _updateCodeOffset(0), _beforeOffset(0), _afterOffset(0),
_errorHandlerOffset(0), _saveAreaOffset(0), _saveSize(0) {
}
/**
* Constructor
*/
Header(Common::SeekableReadStream *s) {
init(s);
}
/**
* init the header
*/
bool init(Common::SeekableReadStream *s);
};
/**
* Game abstraction class
*/
class Game : public Header {
struct CacheEntry {
int _blockNum;
char _data[MESSAGE_BLOCK_SIZE];
/**
* Constructor
*/
CacheEntry() : _blockNum(-1) {
Common::fill(&_data[0], &_data[MESSAGE_BLOCK_SIZE], '\0');
}
};
private:
bool _restartFlag;
Common::SeekableReadStream *_stream;
Common::Array<CacheEntry *> _msgCache;
int _msgBlockNum, _msgBlockOffset;
private:
/**
* Find an object property field
*/
int findProperty(int obj, int prop) const;
/**
* Returns true if an action has a given verb
*/
bool hasVerb(int act, const Common::Array<int> &verbs) const;
/**
* Returns true if an action is in a given list
*/
bool hasPreposition(int act, int preposition) const {
return inList(getActionField(act, A_PREPOSITIONS), preposition);
}
/**
* Check if a word is in an element of a given list
*/
bool inList(int link, int word) const;
/**
* Reads in a message block from the game file
*/
void readMsgBlock();
/**
* Read the next character for a string
*/
char readMsgChar();
protected:
/**
* Returns true if an object has a given noun
*/
bool hasNoun(int obj, int noun) const;
/**
* Returns true if an object has a given adjective
*/
bool hasAdjective(int obj, int adjective) const;
public:
Common::Array<byte> _data;
int _residentOffset;
int _wordCount;
int _objectCount;
int _actionCount;
int _variableCount;
byte *_wordTable;
byte *_wordTypeTable;
byte *_objectTable;
byte *_actionTable;
byte *_variableTable;
byte *_saveArea;
byte *_dataSpace;
byte *_codeSpace;
public:
/**
* Constructor
*/
Game();
/**
* Destructor
*/
~Game();
/**
* init data for the game
*/
bool init(Common::SeekableReadStream *s);
/**
* Restore savegame data from the game to it's initial state
*/
void restart();
/**
* Returns true if the game is restarting, and resets the flag
*/
bool shouldRestart();
/**
* Save the game data to a savegame
*/
void saveGameData(Common::WriteStream &ws);
/**
* Restore the game data from a savegame
*/
void loadGameData(Common::ReadStream &rs);
/**
* Find a word in the dictionary
*/
int findWord(const Common::String &word) const;
/**
* Return a word's type
*/
WordType getWordType(int word) const {
return (WordType)_wordTypeTable[word];
}
/**
* Check to see if this is a valid verb
*/
int checkVerb(const Common::Array<int> &verbs);
/**
* Find an action matching a given description
*/
int findAction(const Common::Array<int> &verbs, int preposition, int flag);
/**
* Get an object property
*/
int getObjectProperty(int obj, int prop);
/**
* Sets an object property
*/
int setObjectProperty(int obj, int prop, int val);
/**
* Gets a field from an object
*/
int getObjectField(int obj, int offset) const {
return READ_LE_UINT16(_dataSpace + getObjectLocation(obj) + offset);
}
/**
* Sets a field in an object
*/
int setObjectField(int obj, int offset, int val) {
WRITE_LE_UINT16(_dataSpace + getObjectLocation(obj) + offset, val);
return val;
}
/**
* Gets a field from an action
*/
int getActionField(int action, int offset) const {
return READ_LE_UINT16(_dataSpace + getActionLocation(action) + offset);
}
/**
* Gets a byte field from an action
*/
int getActionByte(int action, int offset) const {
return _dataSpace[getActionLocation(action) + offset];
}
/**
* Gets the offset of an object from the object table
*/
int getObjectLocation(int obj) const;
/**
* Gets the offset of an action from the action table
*/
int getActionLocation(int action) const;
/**
* Get a variable value
*/
int getVariable(int variableNum);
/**
* Set a variable value
*/
void setVariable(int variableNum, int value);
/**
* Gets a code byte
*/
int getCodeByte(int offset) const {
return _codeSpace[offset];
}
/**
* Gets a code byte
*/
int getCodeWord(int offset) const {
return READ_LE_UINT16(_codeSpace + offset);
}
/**
* Read a word
*/
int readWord(int offset) const {
return READ_LE_UINT16(_dataSpace + offset);
}
/**
* Write a word
*/
void writeWord(int offset, int val) {
WRITE_LE_UINT16(_dataSpace + offset, val);
}
/**
* Read a string from the messages section
*/
Common::String readString(int msg);
};
} // End of namespace AdvSys
} // End of namespace Glk
#endif

View File

@@ -0,0 +1,83 @@
/* 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/advsys/glk_interface.h"
namespace Glk {
namespace AdvSys {
bool GlkInterface::initialize() {
_window = glk_window_open(nullptr, 0, 0, wintype_TextBuffer, 1);
return _window != nullptr;
}
void GlkInterface::print(const Common::String &msg) {
// Don't print out text if loading a savegame directly from the launcher, since we don't
// want any of the intro text displayed by the startup code to show
if (_saveSlot == -1)
glk_put_string_stream(glk_window_get_stream(_window), msg.c_str());
}
void GlkInterface::print(const Common::U32String &msg) {
// Don't print out text if loading a savegame directly from the launcher, since we don't
// want any of the intro text displayed by the startup code to show
if (_saveSlot == -1)
glk_put_string_stream_uni(glk_window_get_stream(_window), msg.u32_str());
}
void GlkInterface::print(int number) {
Common::String s = Common::String::format("%d", number);
print(s);
}
Common::String GlkInterface::readLine() {
event_t ev;
char line[200];
print(": ");
if (!_pendingLine.empty()) {
// The next input line has been manually provided, so return it
print(_pendingLine);
print("\n");
Common::String l = _pendingLine;
_pendingLine = "";
return l;
}
glk_request_line_event(_window, line, 199, 0);
do {
glk_select(&ev);
if (ev.type == evtype_Quit)
return "";
else if (ev.type == evtype_LineInput) {
line[ev.val1] = '\0';
return Common::String(line);
}
} while (!shouldQuit() && ev.type != evtype_Quit);
return "";
}
} // End of namespace AdvSys
} // End of namespace Glk

View File

@@ -0,0 +1,79 @@
/* 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_ADVSYS_GLK_INTERFACE
#define GLK_ADVSYS_GLK_INTERFACE
#include "glk/glk_api.h"
namespace Glk {
namespace AdvSys {
/**
* Interface class that sits between AdvSys and the GLK base, providing methods for
* input and output
*/
class GlkInterface : public GlkAPI {
private:
winid_t _window;
protected:
int _saveSlot;
Common::String _pendingLine;
protected:
/**
* GLK initialization
*/
bool initialize();
/**
* Print a string
* @param msg String
*/
void print(const Common::String &msg);
/**
* Print a unicode string
* @param msg U32String
*/
void print(const Common::U32String &msg);
/**
* Print a number
* @param number Number to print
*/
void print(int number);
/**
* Get an input line
*/
Common::String readLine();
public:
/**
* Constructor
*/
GlkInterface(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
_window(nullptr), _saveSlot(-1) {}
};
} // End of namespace AdvSys
} // End of namespace Glk
#endif

701
engines/glk/advsys/vm.cpp Normal file
View File

@@ -0,0 +1,701 @@
/* 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/advsys/vm.h"
#include "common/translation.h"
#include "common/ustr.h"
namespace Glk {
namespace AdvSys {
#define TRUE -1
OpcodeMethod VM::_METHODS[0x34] = {
&VM::opBRT,
&VM::opBRF,
&VM::opBR,
&VM::opT,
&VM::opNIL,
&VM::opPUSH,
&VM::opNOT,
&VM::opADD,
&VM::opSUB,
&VM::opMUL,
&VM::opDIV,
&VM::opREM,
&VM::opBAND,
&VM::opBOR,
&VM::opBNOT,
&VM::opLT,
&VM::opEQ,
&VM::opGT,
&VM::opLIT,
&VM::opVAR,
&VM::opGETP,
&VM::opSETP,
&VM::opSET,
&VM::opPRINT,
&VM::opTERPRI,
&VM::opPNUMBER,
&VM::opFINISH,
&VM::opCHAIN,
&VM::opABORT,
&VM::opEXIT,
&VM::opRETURN,
&VM::opCALL,
&VM::opSVAR,
&VM::opSSET,
&VM::opSPLIT,
&VM::opSNLIT,
&VM::opYORN,
&VM::opSAVE,
&VM::opRESTORE,
&VM::opARG,
&VM::opASET,
&VM::opTMP,
&VM::opTSET,
&VM::opTSPACE,
&VM::opCLASS,
&VM::opMATCH,
&VM::opPNOUN,
&VM::opRESTART,
&VM::opRAND,
&VM::opRNDMIZE,
&VM::opSEND,
&VM::opVOWEL
};
VM::VM(OSystem *syst, const GlkGameDescription &gameDesc) : GlkInterface(syst, gameDesc), Game(),
_fp(_stack), _pc(0), _status(IN_PROGRESS), _actor(-1), _action(-1), _dObject(-1),
_ndObjects(-1), _iObject(-1), _wordPtr(nullptr) {
}
ExecutionResult VM::execute(int offset) {
// Set the code pointer
_pc = offset;
// Clear the stack
_fp.clear();
_stack.clear();
// Iterate through the script
for (_status = IN_PROGRESS; !shouldQuit() && _status == IN_PROGRESS;)
executeOpcode();
return _status;
}
void VM::executeOpcode() {
// Get next opcode
uint opcode = readCodeByte();
if (gDebugLevel > 0) {
Common::String s;
for (int idx = (int)_stack.size() - 1; idx >= 0; --idx) s += Common::String::format(" %d", _stack[idx]);
debugC(kDebugScripts, "%.4x - %.2x - %d%s", _pc - 1, opcode, _stack.size(), s.c_str());
}
if (opcode >= OP_BRT && opcode <= OP_VOWEL) {
(this->*_METHODS[(int)opcode - 1])();
} else if (opcode >= OP_XVAR && opcode < OP_XSET) {
_stack.top() = getVariable((int)opcode - OP_XVAR);
} else if (opcode >= OP_XSET && opcode < OP_XPLIT) {
setVariable((int)opcode - OP_XSET, _stack.top());
} else if (opcode >= OP_XPLIT && opcode < OP_XNLIT) {
_stack.top() = (int)opcode - OP_XPLIT;
} else if (opcode >= OP_XNLIT && (int)opcode < 256) {
_stack.top() = OP_XNLIT - opcode;
} else {
error("Unknown opcode %x at offset %d", opcode, _pc);
}
}
void VM::opBRT() {
_pc = _stack.top() ? readCodeWord() : _pc + 2;
}
void VM::opBRF() {
_pc = !_stack.top() ? readCodeWord() : _pc + 2;
}
void VM::opBR() {
_pc = readCodeWord();
}
void VM::opT() {
_stack.top() = TRUE;
}
void VM::opNIL() {
_stack.top() = NIL;
}
void VM::opPUSH() {
_stack.push(NIL);
}
void VM::opNOT() {
_stack.top() = _stack.top() ? NIL : TRUE;
}
void VM::opADD() {
int v = _stack.pop();
_stack.top() += v;
}
void VM::opSUB() {
int v = _stack.pop();
_stack.top() -= v;
}
void VM::opMUL() {
int v = _stack.pop();
_stack.top() *= v;
}
void VM::opDIV() {
int v = _stack.pop();
_stack.top() = (v == 0) ? 0 : _stack.top() / v;
}
void VM::opREM() {
int v = _stack.pop();
_stack.top() = (v == 0) ? 0 : _stack.top() % v;
}
void VM::opBAND() {
int v = _stack.pop();
_stack.top() &= v;
}
void VM::opBOR() {
int v = _stack.pop();
_stack.top() |= v;
}
void VM::opBNOT() {
_stack.top() = ~_stack.top();
}
void VM::opLT() {
int v = _stack.pop();
_stack.top() = (_stack.top() < v) ? TRUE : NIL;
}
void VM::opEQ() {
int v = _stack.pop();
_stack.top() = (_stack.top() == v) ? TRUE : NIL;
}
void VM::opGT() {
int v = _stack.pop();
_stack.top() = (_stack.top() > v) ? TRUE : NIL;
}
void VM::opLIT() {
_stack.top() = readCodeWord();
}
void VM::opVAR() {
_stack.top() = getVariable(readCodeWord());
}
void VM::opGETP() {
int v = _stack.pop();
_stack.top() = getObjectProperty(_stack.top(), v);
}
void VM::opSETP() {
int v3 = _stack.pop();
int v2 = _stack.pop();
_stack.top() = setObjectProperty(_stack.top(), v2, v3);
}
void VM::opSET() {
setVariable(readCodeWord(), _stack.top());
}
void VM::opPRINT() {
Common::String msg = readString(_stack.top());
print(msg);
}
void VM::opTERPRI() {
print("\n");
}
void VM::opPNUMBER() {
print(_stack.top());
}
void VM::opFINISH() {
_status = FINISH;
}
void VM::opCHAIN() {
_status = CHAIN;
}
void VM::opABORT() {
_status = ABORT;
}
void VM::opEXIT() {
quitGame();
_status = ABORT;
}
void VM::opRETURN() {
if (_fp == 0) {
_status = CHAIN;
} else {
int val = _stack.top();
_stack.resize(_fp);
_fp = _stack.pop();
_pc = _stack.pop();
int argsSize = _stack.pop();
_stack.resize(_stack.size() - argsSize);
_stack.top() = val;
}
}
void VM::opCALL() {
int argsSize = readCodeByte();
_stack.push(argsSize);
_stack.push(_pc);
_stack.push(_fp);
_fp.set();
_pc = getActionField(_fp[_fp[FP_ARGS_SIZE] + FP_ARGS], A_CODE);
}
void VM::opSVAR() {
_stack.top() = getVariable(readCodeByte());
}
void VM::opSSET() {
setVariable(readCodeByte(), _stack.top());
}
void VM::opSPLIT() {
_stack.top() = readCodeByte();
}
void VM::opSNLIT() {
_stack.top() = readCodeByte();
}
void VM::opYORN() {
Common::String line = readLine();
_stack.top() = !line.empty() && (line[0] == 'Y' || line[0] == 'y') ? TRUE : NIL;
}
void VM::opSAVE() {
if (saveGame().getCode() != Common::kNoError)
print(_("Sorry, the savegame couldn't be created"));
}
void VM::opRESTORE() {
if (loadGame().getCode() != Common::kNoError)
print(_("Sorry, the savegame couldn't be restored"));
}
void VM::opARG() {
int argNum = readCodeByte();
if (argNum >= _fp[FP_ARGS_SIZE])
error("Invalid argument number");
_stack.top() = _fp[argNum + FP_ARGS];
}
void VM::opASET() {
int argNum = readCodeByte();
if (argNum >= _fp[FP_ARGS_SIZE])
error("Invalid argument number");
_fp[argNum + FP_ARGS] = _stack.top();
}
void VM::opTMP() {
int val = readCodeByte();
_stack.top() = _fp[-val - 1];
}
void VM::opTSET() {
int val = readCodeByte();
_fp[-val - 1] = _stack.top();
}
void VM::opTSPACE() {
_stack.allocate(readCodeByte());
}
void VM::opCLASS() {
_stack.top() = getObjectField(_stack.top(), O_CLASS);
}
void VM::opMATCH() {
int idx = _stack.pop() - 1;
if (idx < 0 || _nouns.size() == 0) {
_stack.top() = NIL;
} else {
_stack.top() = match(_stack.top(), _nouns[idx]._noun, _nouns[idx]._adjective) ? TRUE : NIL;
}
}
void VM::opPNOUN() {
int noun = _stack.top();
Common::String str;
// Add the adjectives
bool space = false;
for (const AdjectiveEntry *aPtr = &_adjectiveList[noun - 1]; aPtr->_list; ++aPtr, space = true) {
if (space)
str += " ";
str += _words[aPtr->_word]._text;
}
// Add the noun
if (space)
str += " ";
str += _words[_nouns[noun - 1]._num]._text;
print(str);
}
void VM::opRESTART() {
restart();
}
void VM::opRAND() {
_stack.top() = getRandomNumber(_stack.top());
}
void VM::opRNDMIZE() {
// No implementation
}
void VM::opSEND() {
int argsSize = readCodeByte();
_stack.push(argsSize);
_stack.push(_pc);
_stack.push(_fp);
_fp.set();
int val = _fp[_fp[FP_ARGS_SIZE] + FP_ARGS];
if (val)
val = getObjectField(val, O_CLASS);
else
val = _fp[_fp[FP_ARGS_SIZE] + FP_ARGS - 1];
if (val && (val = getObjectProperty(val, _fp[_fp[FP_ARGS_SIZE] + FP_ARGS - 2])) != 0) {
_pc = getActionField(val, A_CODE);
} else {
// Return NIL if there's no action for the given message
opRETURN();
}
}
void VM::opVOWEL() {
// No implementation
}
bool VM::getInput() {
if (!parseInput())
return false;
setVariable(V_ACTOR, _actor);
setVariable(V_ACTION, _action);
setVariable(V_DOBJECT, _dObject);
setVariable(V_NDOBJECTS, _ndObjects);
setVariable(V_IOBJECT, _iObject);
return true;
}
bool VM::nextCommand() {
if (getVariable(V_NDOBJECTS) > 1) {
setVariable(V_ACTOR, _actor);
setVariable(V_ACTION, _action);
setVariable(V_DOBJECT, getVariable(V_DOBJECT) + 1);
setVariable(V_NDOBJECTS, getVariable(V_NDOBJECTS) - 1);
setVariable(V_IOBJECT, _iObject);
return true;
} else {
return false;
}
}
bool VM::parseInput() {
int noun1 = 0, cnt1 = 0, noun2 = 0, cnt2 = 0;
int preposition = 0, flags = 0;
// Initialize the parser result fields
_actor = _action = _dObject = _iObject = 0;
_ndObjects = 0;
_nouns.clear();
_adjectiveList.clear();
_adjectiveList.reserve(20);
// Get the input line
if (!getLine())
return false;
// Check for actor
WordType wordType = getWordType(*_wordPtr);
if (wordType == WT_ADJECTIVE || wordType == WT_NOUN) {
if (!(_actor = getNoun()))
return false;
flags |= A_ACTOR;
}
// Check for a verb
if (!getVerb())
return false;
// Get direct object, preposition, and/or indirect object
if (_wordPtr != _words.end()) {
// Get any direct objects
noun1 = _adjectiveList.size() + 1;
for (;;) {
// Get the next direct object
if (!getNoun())
return false;
++cnt1;
// Check for more direct objects
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_CONJUNCTION)
break;
++_wordPtr;
}
// Get any reposition and indirect object
if (_wordPtr != _words.end()) {
// Get the preposition
if (getWordType(*_wordPtr) == WT_PREPOSITION)
preposition = *_wordPtr++;
// Get the indirect object
noun2 = _adjectiveList.size() + 1;
for (;;) {
// Get the indirect object
if (!getNoun())
return false;
++cnt2;
// Check for more objects
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_CONJUNCTION)
break;
++_wordPtr;
}
}
// Ensure we're at the end of the input line
if (_wordPtr != _words.end()) {
parseError();
return false;
}
}
// Setup resulting properties
if (preposition) {
if (cnt2 > 1) {
parseError();
return false;
}
_dObject = noun1;
_ndObjects = cnt1;
_iObject = noun2;
} else if (noun2) {
if (cnt1 > 1) {
parseError();
return false;
}
preposition = findWord("to");
_dObject = noun2;
_ndObjects = cnt2;
_iObject = noun1;
} else {
_dObject = noun1;
_ndObjects = cnt1;
}
// Setup the flags for the action lookup
if (_dObject)
flags |= A_DOBJECT;
if (_iObject)
flags |= A_IOBJECT;
// Find the action
if (!(_action = findAction(_verbs, preposition, flags))) {
parseError();
return false;
}
return true;
}
bool VM::getLine() {
// Let the user type in an input line
Common::String line = readLine();
if (shouldQuit())
return false;
skipSpaces(line);
if (line.empty()) {
print(_("Speak up! I can't hear you!\n"));
return false;
}
// Get the words of the line
_words.clear();
while (!line.empty()) {
if (!getWord(line))
return false;
}
_wordPtr = _words.begin();
return true;
}
bool VM::getWord(Common::String &line) {
// Find the end of the word
const char *wordP = line.c_str();
for (; *wordP && !isWhitespace(*wordP); ++wordP) {}
// Copy out the next word
InputWord iw;
iw._text = Common::String(line.c_str(), wordP);
iw._text.toLowercase();
// Remove the word from the line
line = Common::String(wordP);
skipSpaces(line);
// Look up the word
iw._number = findWord(iw._text);
if (iw._number) {
_words.push_back(iw);
return true;
} else {
Common::U32String msg = Common::U32String::format(_("I don't know the word \"%s\".\n"), iw._text.c_str());
print(msg);
return false;
}
}
uint VM::getNoun() {
// Skip over optional article if present
if (_wordPtr != _words.end() && getWordType(*_wordPtr) == WT_ARTICLE)
++_wordPtr;
// Get optional adjectives
uint alStart = _adjectiveList.size();
while (_wordPtr != _words.end() && getWordType(*_wordPtr) == WT_ADJECTIVE) {
AdjectiveEntry ae;
ae._list = *_wordPtr++;
ae._word = _wordPtr - _words.begin() - 1;
_adjectiveList.push_back(ae);
}
_adjectiveList.push_back(AdjectiveEntry());
assert(_adjectiveList.size() <= 20);
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_NOUN) {
parseError();
return NIL;
}
// Add a noun entry to the list
Noun n;
n._adjective = &_adjectiveList[alStart];
n._noun = *_wordPtr++;
n._num = _wordPtr - _words.begin() - 1;
_nouns.push_back(n);
return _nouns.size();
}
bool VM::getVerb() {
_verbs.clear();
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_VERB) {
parseError();
return false;
}
_verbs.push_back(*_wordPtr++);
// Check for a word following the verb
if (_wordPtr < _words.end()) {
_verbs.push_back(*_wordPtr);
if (checkVerb(_verbs)) {
++_wordPtr;
} else {
_verbs.pop_back();
_verbs.push_back(_words.back());
if (checkVerb(_verbs)) {
_words.pop_back();
} else {
_verbs.pop_back();
if (!checkVerb(_verbs)) {
parseError();
return false;
}
}
}
}
return true;
}
bool VM::match(int obj, int noun, const VM::AdjectiveEntry *adjectives) {
if (!hasNoun(obj, noun))
return false;
for (const VM::AdjectiveEntry *adjPtr = adjectives; adjPtr->_list; ++adjPtr) {
if (!hasAdjective(obj, adjPtr->_list))
return false;
}
return true;
}
void VM::parseError() {
print(_("I don't understand.\n"));
}
bool VM::isWhitespace(char c) {
return c == ' ' || c == ',' || c == '.';
}
bool VM::skipSpaces(Common::String &str) {
while (!str.empty() && isWhitespace(str[0]))
str.deleteChar(0);
return !str.empty();
}
} // End of namespace AdvSys
} // End of namespace Glk

391
engines/glk/advsys/vm.h Normal file
View File

@@ -0,0 +1,391 @@
/* 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_ADVSYS_VM
#define GLK_ADVSYS_VM
#include "glk/advsys/glk_interface.h"
#include "glk/advsys/game.h"
#include "common/stack.h"
namespace Glk {
namespace AdvSys {
/**
* Execution result states
*/
enum ExecutionResult {
IN_PROGRESS = 0, ///< Default state whilst script is in progress
FINISH = 1, ///< Script was finished
CHAIN = 2, ///< Another script is being chained to
ABORT = 3 ///< Script was aborted
};
/**
* Opcode list
*/
enum Opcode {
OP_BRT = 0x01, ///< Branch on true
OP_BRF = 0x02, ///< Branch on false
OP_BR = 0x03, ///< Branch unconditionally
OP_T = 0x04, ///< Load top of stack with t
OP_NIL = 0x05, ///< Load top of stack with nil
OP_PUSH = 0x06, ///< Push nil onto stack
OP_NOT = 0x07, ///< Logical negate top of stack
OP_ADD = 0x08, ///< Add two numeric expressions
OP_SUB = 0x09, ///< Subtract two numeric expressions
OP_MUL = 0x0A, ///< Multiply two numeric expressions
OP_DIV = 0x0B, ///< Divide two numeric expressions
OP_REM = 0x0C, ///< Remainder of two numeric expressions
OP_BAND = 0x0D, ///< Bitwise and of two numeric expressions
OP_BOR = 0x0E, ///< Bitwise or of two numeric expressions
OP_BNOT = 0x0F, ///< Bitwise not
OP_LT = 0x10, ///< Less than
OP_EQ = 0x11, ///< Equal to
OP_GT = 0x12, ///< Greater than
OP_LIT = 0x13, ///< Load literal
OP_VAR = 0x14, ///< Load a variable value
OP_GETP = 0x15, ///< Get the value of an object property
OP_SETP = 0x16, ///< Set the value of an object property
OP_SET = 0x17, ///< Set the value of a variable
OP_PRINT = 0x18, ///< Print messages
OP_TERPRI = 0x19, ///< Terminate the print line
OP_PNUMBER = 0x1A, ///< Print a number
OP_FINISH = 0x1B, ///< Finish handling this command
OP_CHAIN = 0x1C, ///< Chain to the next handler
OP_ABORT = 0x1D, ///< Abort this command
OP_EXIT = 0x1E, ///< Exit the game
OP_RETURN = 0x1F, ///< Return from function
OP_CALL = 0x20, ///< Call subroutine
OP_SVAR = 0x21, ///< Short load a variable
OP_SSET = 0x22, ///< Short set a variable
OP_SPLIT = 0x23, ///< Short load a positive literal
OP_SNLIT = 0x24, ///< Short load a negative literal
OP_YORN = 0x25, ///< Yes or No predicate
OP_SAVE = 0x26, ///< Save data structures
OP_RESTORE = 0x27, ///< Restore data structures
OP_ARG = 0x28, ///< Load an argument value
OP_ASET = 0x29, ///< Set an argument value
OP_TMP = 0x2A, ///< Load a temporary variable value
OP_TSET = 0x2B, ///< Set a temporary variable
OP_TSPACE = 0x2C, ///< Allocate temporary variable space
OP_CLASS = 0x2D, ///< Get the class of an object
OP_MATCH = 0x2E, ///< Match a noun phrase with an object
OP_PNOUN = 0x2F, ///< Print a noun phrase
OP_RESTART = 0x30, ///< Restart the current game
OP_RAND = 0x31, ///< Generate a random number
OP_RNDMIZE = 0x32, ///< Seed the random number generator
OP_SEND = 0x33, ///< Send a message to an object
OP_VOWEL = 0x34, ///< Check for vowel beginning string
OP_XVAR = 0x40, ///< Extra short load a variable
OP_XSET = 0x60, ///< Extra short set a variable
OP_XPLIT = 0x80, ///< Extra short load a positive literal
OP_XNLIT = 0xC0 ///< Extra short load a negative literal
};
/**
* Indexes useable in function pointer array offsets
*/
enum FPOffset {
FP_FP = 0,
FP_PC = 1,
FP_ARGS_SIZE = 2,
FP_ARGS = 3
};
/**
* Action flags
*/
enum ActionFlag {
A_ACTOR = 1, ///< Actor
A_DOBJECT = 2, ///< Direct object
A_IOBJECT = 4 ///< Indirect object
};
class VM;
typedef void (VM::*OpcodeMethod)();
/**
* Fixed stack
*/
class FixedStack : public Common::FixedStack<int, 500> {
public:
/**
* Resize the stack
*/
void resize(size_t newSize) {
assert(newSize <= 500);
_size = newSize;
}
/**
* Allocate extra space on the stack
*/
void allocate(size_t amount) {
uint oldSize = _size;
resize(_size + amount);
Common::fill(&_stack[oldSize], &_stack[oldSize + amount], 0);
}
};
/**
* Implements a function pointer reference into the stack. It also allows
* positive array indexing to reference the following:
* 0 = Previous function pointer
* 1 = Return PC
* 2 = Size of argument block
* 3+ = Any function call arguments
*/
class FunctionPointer {
private:
FixedStack &_stack;
int _index;
public:
/**
* Constructor
*/
FunctionPointer(FixedStack &s) : _stack(s), _index(0) {}
/**
* Array indexing
*/
int &operator[](int idx) { return _stack[_index - idx - 1]; }
/**
* Sets the index in the stack of the function pointer
*/
FunctionPointer &operator=(int index) {
_index = index;
return *this;
}
/**
* Clear the function pointer
*/
void clear() { _index = 0; }
/**
* Returns the index in the stack of the function pointer
*/
operator int() const { return _index; }
/**
* Sets the function pointer to the top of the stack
*/
void set() {
_index = _stack.size();
}
};
/**
* Main VM for AdvSys
*/
class VM : public GlkInterface, public Game {
struct InputWord {
Common::String _text;
int _number;
InputWord() : _number(0) {}
operator int() const { return _number; }
};
struct AdjectiveEntry {
int _list;
int _word;
AdjectiveEntry() : _list(0), _word(0) {}
};
struct Noun {
int _noun;
int _num;
AdjectiveEntry *_adjective;
Noun() : _noun(0), _num(0), _adjective(nullptr) {}
};
private:
// Execution fields
static OpcodeMethod _METHODS[0x34];
int _pc;
ExecutionResult _status;
FixedStack _stack;
FunctionPointer _fp;
// Parser fields
int _actor;
int _action;
int _dObject;
int _ndObjects;
int _iObject;
Common::Array<InputWord> _words;
Common::Array<InputWord>::iterator _wordPtr;
Common::Array<int> _verbs;
Common::Array<AdjectiveEntry> _adjectiveList;
Common::Array<Noun> _nouns;
private:
/**
* Execute a single opcode within the script
*/
void executeOpcode();
/**
* Get the next code byte and increments the PC counter
*/
int readCodeByte() {
return getCodeByte(_pc++);
}
/**
* Gets the next code word and increases the PC counter to after it
*/
int readCodeWord() {
int v = getCodeWord(_pc);
_pc += 2;
return v;
}
/**
* Gets an input line and parse it
*/
bool parseInput();
/**
* Gets an input line and splits it up into the words array
*/
bool getLine();
/**
* Get the next word of a passed input line
* @param line Input line
* @returns True if a valid word was extracted
*/
bool getWord(Common::String &line);
/**
* Get a noun phrase and return the object it refers to
*/
uint getNoun();
/**
* Get a verb phrase and return the action it refers to
*/
bool getVerb();
/**
* Match an object against a name and list of adjectives
*/
bool match(int obj, int noun, const AdjectiveEntry *adjectives);
/**
* Called when a parsing error occurs
*/
void parseError();
/**
* Returns true if a passed character is a skippable whitespace
*/
static bool isWhitespace(char c);
/**
* Skips over spaces in a passed string
*/
static bool skipSpaces(Common::String &str);
private:
void opBRT();
void opBRF();
void opBR();
void opT();
void opNIL();
void opPUSH();
void opNOT();
void opADD();
void opSUB();
void opMUL();
void opDIV();
void opREM();
void opBAND();
void opBOR();
void opBNOT();
void opLT();
void opEQ();
void opGT();
void opLIT();
void opVAR();
void opGETP();
void opSETP();
void opSET();
void opPRINT();
void opTERPRI();
void opPNUMBER();
void opFINISH();
void opCHAIN();
void opABORT();
void opEXIT();
void opRETURN();
void opCALL();
void opSVAR();
void opSSET();
void opSPLIT();
void opSNLIT();
void opYORN();
void opSAVE();
void opRESTORE();
void opARG();
void opASET();
void opTMP();
void opTSET();
void opTSPACE();
void opCLASS();
void opMATCH();
void opPNOUN();
void opRESTART();
void opRAND();
void opRNDMIZE();
void opSEND();
void opVOWEL();
public:
/**
* Constructor
*/
VM(OSystem *syst, const GlkGameDescription &gameDesc);
/**
* Exeecute a script
* @param offset Script offset
* @returns Script result code
*/
ExecutionResult execute(int offset);
/**
* Get an input line and parse it
*/
bool getInput();
/**
* Get the next command (next direct object)
*/
bool nextCommand();
};
} // End of namespace AdvSys
} // End of namespace Glk
#endif