GLK: ADVSYS: Adding opcodes and message decoding

This commit is contained in:
Paul Gilbert 2019-06-09 17:10:27 -07:00
parent e8f0e9e998
commit d5d801d1cb
8 changed files with 183 additions and 44 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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<CacheEntry *> _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<byte> _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

View File

@ -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

View File

@ -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

View File

@ -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() {

View File

@ -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];