Initial commit
This commit is contained in:
388
engines/glk/advsys/game.cpp
Normal file
388
engines/glk/advsys/game.cpp
Normal 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
|
||||
Reference in New Issue
Block a user