From d5d801d1cba3ee95a9be48bd2a505f2e2906ead8 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sun, 9 Jun 2019 17:10:27 -0700 Subject: GLK: ADVSYS: Adding opcodes and message decoding --- engines/glk/advsys/advsys.cpp | 2 +- engines/glk/advsys/detection.cpp | 2 +- engines/glk/advsys/game.cpp | 99 ++++++++++++++++++++++++++++++++---- engines/glk/advsys/game.h | 49 +++++++++++++++--- engines/glk/advsys/glk_interface.cpp | 7 +++ engines/glk/advsys/glk_interface.h | 12 +++++ engines/glk/advsys/vm.cpp | 51 ++++++++++--------- engines/glk/advsys/vm.h | 5 ++ 8 files changed, 183 insertions(+), 44 deletions(-) (limited to 'engines/glk/advsys') diff --git a/engines/glk/advsys/advsys.cpp b/engines/glk/advsys/advsys.cpp index b745222ea1..fe02e25181 100644 --- a/engines/glk/advsys/advsys.cpp +++ b/engines/glk/advsys/advsys.cpp @@ -80,7 +80,7 @@ bool AdvSys::initialize() { return false; // Load the game's header - if (!Game::init(_gameFile)) + if (!Game::init(&_gameFile)) return false; return true; diff --git a/engines/glk/advsys/detection.cpp b/engines/glk/advsys/detection.cpp index 921be4eef8..d3b376f0b6 100644 --- a/engines/glk/advsys/detection.cpp +++ b/engines/glk/advsys/detection.cpp @@ -65,7 +65,7 @@ bool AdvSysMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames & if (!gameFile.open(*file)) continue; - Header hdr(gameFile); + Header hdr(&gameFile); if (!hdr._valid) continue; diff --git a/engines/glk/advsys/game.cpp b/engines/glk/advsys/game.cpp index 17a4104404..fe1221baf7 100644 --- a/engines/glk/advsys/game.cpp +++ b/engines/glk/advsys/game.cpp @@ -36,12 +36,12 @@ void Decrypter::decrypt(byte *data, size_t size) { #define HEADER_SIZE 62 -bool Header::init(Common::ReadStream &s) { +bool Header::init(Common::SeekableReadStream *s) { _valid = false; byte data[HEADER_SIZE]; // Read in the data - if (s.read(data, HEADER_SIZE) != HEADER_SIZE) + if (s->read(data, HEADER_SIZE) != HEADER_SIZE) return false; decrypt(data, HEADER_SIZE); Common::MemoryReadStream ms(data, HEADER_SIZE, DisposeAfterUse::NO); @@ -98,9 +98,26 @@ enum LinkField { L_SIZE = 4 }; -bool Game::init(Common::SeekableReadStream &s) { +Game::Game() : Header(), _stream(nullptr), _restartFlag(false), _residentOffset(0), _wordCount(0), + _objectCount(0), _actionCount(0), _variableCount(0), _residentBase(nullptr), + _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); + s->seek(0); if (!Header::init(s)) return false; @@ -109,10 +126,10 @@ bool Game::init(Common::SeekableReadStream &s) { // Load the needed resident game data and decrypt it _residentOffset = _dataBlockOffset * 512; - s.seek(_residentOffset); + s->seek(_residentOffset); _data.resize(_size); - if (!s.read(&_data[0], _size)) + if (!s->read(&_data[0], _size)) return false; decrypt(&_data[0], _size); @@ -136,9 +153,9 @@ bool Game::init(Common::SeekableReadStream &s) { return true; } -void Game::restart(Common::SeekableReadStream &s) { - s.seek(_residentOffset + _saveAreaOffset); - s.read(_saveArea, _saveSize); +void Game::restart() { + _stream->seek(_residentOffset + _saveAreaOffset); + _stream->read(_saveArea, _saveSize); decrypt(_saveArea, _saveSize); setVariable(V_OCOUNT, _objectCount); @@ -316,5 +333,69 @@ bool Game::inList(int link, int word) const { 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 diff --git a/engines/glk/advsys/game.h b/engines/glk/advsys/game.h index a5c7f41f55..99cfe26068 100644 --- a/engines/glk/advsys/game.h +++ b/engines/glk/advsys/game.h @@ -30,6 +30,8 @@ namespace Glk { namespace AdvSys { #define NIL 0 +#define MESSAGE_CACHE_SIZE 8 +#define MESSAGE_BLOCK_SIZE 512 /** * Actions @@ -118,22 +120,36 @@ public: /** * Constructor */ - Header(Common::ReadStream &s) { + Header(Common::SeekableReadStream *s) { init(s); } /** * init the header */ - bool init(Common::ReadStream &s); + 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 _msgCache; + int _msgBlockNum, _msgBlockOffset; private: /** * Find an object property field @@ -166,6 +182,16 @@ private: * 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(); public: Common::Array _data; int _residentOffset; @@ -187,20 +213,22 @@ public: /** * Constructor */ - Game() : Header(), _restartFlag(false), _residentOffset(0), _wordCount(0), _objectCount(0), - _actionCount(0), _variableCount(0), _residentBase(nullptr), _wordTable(nullptr), - _wordTypeTable(nullptr), _objectTable(nullptr), _actionTable(nullptr), - _variableTable(nullptr), _saveArea(nullptr) {} + Game(); + + /** + * Destructor + */ + ~Game(); /** * init data for the game */ - bool init(Common::SeekableReadStream &s); + bool init(Common::SeekableReadStream *s); /** * Restore savegame data from the game to it's initial state */ - void restart(Common::SeekableReadStream &s); + void restart(); /** * Returns true if the game is restarting, and resets the flag @@ -330,6 +358,11 @@ public: void writeWord(int offset, int val) { WRITE_LE_UINT16(_residentBase + offset, val); } + + /** + * Read a string from the messages section + */ + Common::String readString(int msg); }; } // End of namespace AdvSys diff --git a/engines/glk/advsys/glk_interface.cpp b/engines/glk/advsys/glk_interface.cpp index 3bc6800d0c..fff6bd12ed 100644 --- a/engines/glk/advsys/glk_interface.cpp +++ b/engines/glk/advsys/glk_interface.cpp @@ -25,6 +25,13 @@ namespace Glk { namespace AdvSys { +void GlkInterface::printString(int offset) { + // TODO +} + +void GlkInterface::printNumber(int number) { + // TODO +} } // End of namespace AdvSys } // End of namespace Glk diff --git a/engines/glk/advsys/glk_interface.h b/engines/glk/advsys/glk_interface.h index 58ff831509..b54a35b250 100644 --- a/engines/glk/advsys/glk_interface.h +++ b/engines/glk/advsys/glk_interface.h @@ -33,6 +33,18 @@ namespace AdvSys { * input and output */ class GlkInterface : public GlkAPI { +protected: + /** + * Print a string + * @param offset String offset + */ + void printString(int offset); + + /** + * Print a number + * @param number Number to print + */ + void printNumber(int number); public: /** * Constructor diff --git a/engines/glk/advsys/vm.cpp b/engines/glk/advsys/vm.cpp index 754a86a530..be2fed9ee7 100644 --- a/engines/glk/advsys/vm.cpp +++ b/engines/glk/advsys/vm.cpp @@ -108,23 +108,23 @@ void VM::executeOpcode() { if (opcode >= OP_BRT && opcode <= OP_VOWEL) { (this->*_METHODS[(int)opcode - 1])(); } else if (opcode >= OP_XVAR && opcode < OP_XSET) { - _stack.back() = getVariable((int)opcode - OP_XVAR); + _stack.top() = getVariable((int)opcode - OP_XVAR); } else if (opcode >= OP_XSET && opcode < OP_XPLIT) { - setVariable((int)opcode - OP_XSET, _stack.back()); + setVariable((int)opcode - OP_XSET, _stack.top()); } else if (opcode >= OP_XPLIT && opcode < OP_XNLIT) { - _stack.back() = (int)opcode - OP_XPLIT; + _stack.top() = (int)opcode - OP_XPLIT; } else if (opcode >= OP_XNLIT && (int)opcode < 256) { - _stack.back() = OP_XNLIT - opcode; + _stack.top() = OP_XNLIT - opcode; } else { error("Unknown opcode %x at offset %d", opcode, _pc); } } void VM::opBRT() { - _pc = _stack.back() ? readCodeWord() : _pc + 2; + _pc = _stack.top() ? readCodeWord() : _pc + 2; } void VM::opBRF() { - _pc = !_stack.back() ? readCodeWord() : _pc + 2; + _pc = !_stack.top() ? readCodeWord() : _pc + 2; } void VM::opBR() { @@ -132,11 +132,11 @@ void VM::opBR() { } void VM::opT() { - _stack.back() = TRUE; + _stack.top() = TRUE; } void VM::opNIL() { - _stack.back() = NIL; + _stack.top() = NIL; } void VM::opPUSH() { @@ -144,83 +144,84 @@ void VM::opPUSH() { } void VM::opNOT() { - _stack.back() = _stack.back() ? NIL : TRUE; + _stack.top() = _stack.top() ? NIL : TRUE; } void VM::opADD() { int v = _stack.pop(); - _stack.back() += v; + _stack.top() += v; } void VM::opSUB() { int v = _stack.pop(); - _stack.back() -= v; + _stack.top() -= v; } void VM::opMUL() { int v = _stack.pop(); - _stack.back() *= v; + _stack.top() *= v; } void VM::opDIV() { int v = _stack.pop(); - _stack.back() = (v == 0) ? 0 : _stack.back() / v; + _stack.top() = (v == 0) ? 0 : _stack.top() / v; } void VM::opREM() { int v = _stack.pop(); - _stack.back() = (v == 0) ? 0 : _stack.back() % v; + _stack.top() = (v == 0) ? 0 : _stack.top() % v; } void VM::opBAND() { int v = _stack.pop(); - _stack.back() &= v; + _stack.top() &= v; } void VM::opBOR() { int v = _stack.pop(); - _stack.back() |= v; + _stack.top() |= v; } void VM::opBNOT() { - _stack.back() = ~_stack.back(); + _stack.top() = ~_stack.top(); } void VM::opLT() { int v = _stack.pop(); - _stack.back() = (_stack.back() < v) ? TRUE : NIL; + _stack.top() = (_stack.top() < v) ? TRUE : NIL; } void VM::opEQ() { int v = _stack.pop(); - _stack.back() = (_stack.back() == v) ? TRUE : NIL; + _stack.top() = (_stack.top() == v) ? TRUE : NIL; } void VM::opGT() { int v = _stack.pop(); - _stack.back() = (_stack.back() > v) ? TRUE : NIL; + _stack.top() = (_stack.top() > v) ? TRUE : NIL; } void VM::opLIT() { - _stack.back() = readCodeWord(); + _stack.top() = readCodeWord(); } void VM::opVAR() { - _stack.back() = getVariable(readCodeWord()); + _stack.top() = getVariable(readCodeWord()); } void VM::opGETP() { int v = _stack.pop(); - _stack.back() = getObjectProperty(_stack.back(), v); + _stack.top() = getObjectProperty(_stack.top(), v); } void VM::opSETP() { int v3 = _stack.pop(); int v2 = _stack.pop(); - _stack.back() = setObjectProperty(_stack.back(), v2, v3); + _stack.top() = setObjectProperty(_stack.top(), v2, v3); } void VM::opSET() { + setVariable(readCodeWord(), _stack.top()); } void VM::opPRINT() { @@ -251,7 +252,7 @@ void VM::opCALL() { } void VM::opSVAR() { - _stack.back() = getVariable(readCodeByte()); + _stack.top() = getVariable(readCodeByte()); } void VM::opSSET() { diff --git a/engines/glk/advsys/vm.h b/engines/glk/advsys/vm.h index e1b217fd31..9718b044f4 100644 --- a/engines/glk/advsys/vm.h +++ b/engines/glk/advsys/vm.h @@ -127,6 +127,11 @@ class VM : public GlkInterface, public Game { pop_back(); return v; } + + /** + * Returns the top of the stack (the most recently added value + */ + int &top() { return back(); } }; private: static OpcodeMethod _METHODS[0x34]; -- cgit v1.2.3