Files
2026-02-02 04:50:13 +01:00

389 lines
9.9 KiB
C++

/* 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