1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/advsys/game.h"
24 #include "glk/advsys/definitions.h"
25 #include "common/memstream.h"
26 
27 namespace Glk {
28 namespace AdvSys {
29 
decrypt(byte * data,size_t size)30 void Decrypter::decrypt(byte *data, size_t size) {
31 	for (size_t idx = 0; idx < size; ++idx, ++data)
32 		*data = ~(*data + 30);
33 }
34 
35 /*--------------------------------------------------------------------------*/
36 
37 #define HEADER_SIZE 62
38 
init(Common::SeekableReadStream * s)39 bool Header::init(Common::SeekableReadStream *s) {
40 	_valid = false;
41 	byte data[HEADER_SIZE];
42 
43 	// Read in the data
44 	if (s->read(data, HEADER_SIZE) != HEADER_SIZE)
45 		return false;
46 	decrypt(data, HEADER_SIZE);
47 	Common::MemoryReadStream ms(data, HEADER_SIZE, DisposeAfterUse::NO);
48 
49 	// Validate the header
50 	_valid = !strncmp((const char *)data + 2, "ADVSYS", 6);
51 	if (!_valid)
52 		return false;
53 
54 	_size = ms.readUint16LE();
55 	ms.skip(6);
56 	_headerVersion = ms.readUint16LE();
57 	_name = Common::String((const char *)data + 10, (const char *)data + 28);
58 	ms.skip(18);
59 	_version = ms.readUint16LE();
60 	_wordTableOffset = ms.readUint16LE();
61 	_wordTypeTableOffset = ms.readUint16LE();
62 	_objectTableOffset = ms.readUint16LE();
63 	_actionTableOffset = ms.readUint16LE();
64 	_variableTableOffset = ms.readUint16LE();
65 	_dataSpaceOffset = ms.readUint16LE();
66 	_codeSpaceOffset = ms.readUint16LE();
67 	_dataBlockOffset = ms.readUint16LE();
68 	_messageBlockOffset = ms.readUint16LE();
69 	_initCodeOffset = ms.readUint16LE();
70 	_updateCodeOffset = ms.readUint16LE();
71 	_beforeOffset = ms.readUint16LE();
72 	_afterOffset = ms.readUint16LE();
73 	_errorHandlerOffset = ms.readUint16LE();
74 	_saveAreaOffset = ms.readUint16LE();
75 	_saveSize = ms.readUint16LE();
76 
77 	return true;
78 }
79 
80 /*--------------------------------------------------------------------------*/
81 
82 #define MAX_VERSION 102
83 #define WORD_SIZE 6
84 
85 /**
86  * Property flags
87  */
88 enum PropertyFlag {
89 	P_CLASS = 0x8000
90 };
91 
92 /**
93  * Link fields
94  */
95 enum LinkField {
96 	L_DATA = 0,
97 	L_NEXT = 2,
98 	L_SIZE = 4
99 };
100 
Game()101 Game::Game() : Header(), _stream(nullptr), _restartFlag(false), _residentOffset(0), _wordCount(0),
102 		_objectCount(0), _actionCount(0), _variableCount(0), _wordTable(nullptr), _wordTypeTable(nullptr),
103 		_objectTable(nullptr), _actionTable(nullptr), _variableTable(nullptr), _saveArea(nullptr),
104 		_msgBlockNum(-1), _msgBlockOffset(0) {
105 	_msgCache.resize(MESSAGE_CACHE_SIZE);
106 	for (int idx = 0; idx < MESSAGE_CACHE_SIZE; ++idx)
107 		_msgCache[idx] = new CacheEntry();
108 }
109 
~Game()110 Game::~Game() {
111 	for (int idx = 0; idx < MESSAGE_CACHE_SIZE; ++idx)
112 		delete _msgCache[idx];
113 }
114 
init(Common::SeekableReadStream * s)115 bool Game::init(Common::SeekableReadStream *s) {
116 	// Store a copy of the game file stream
117 	_stream = s;
118 
119 	// Load the header
120 	s->seek(0);
121 	if (!Header::init(s))
122 		return false;
123 
124 	if (_headerVersion < 101 || _headerVersion > MAX_VERSION)
125 		error("Wrong version number");
126 
127 	// Load the needed resident game data and decrypt it
128 	_residentOffset = _dataBlockOffset * 512;
129 	s->seek(_residentOffset);
130 
131 	_data.resize(_size);
132 	if (!s->read(&_data[0], _size))
133 		return false;
134 	decrypt(&_data[0], _size);
135 
136 	_wordTable = &_data[_wordTableOffset];
137 	_wordTypeTable = &_data[_wordTypeTableOffset - 1];
138 	_objectTable = &_data[_objectTableOffset];
139 	_actionTable = &_data[_actionTableOffset];
140 	_variableTable = &_data[_variableTableOffset];
141 	_saveArea = &_data[_saveAreaOffset];
142 	_dataSpace = &_data[_dataSpaceOffset];
143 	_codeSpace = &_data[_codeSpaceOffset];
144 
145 	_wordCount = READ_LE_UINT16(_wordTable);
146 	_objectCount = READ_LE_UINT16(_objectTable);
147 	_actionCount = READ_LE_UINT16(_actionTable);
148 	_variableCount = READ_LE_UINT16(_variableTable);
149 
150 	setVariable(V_OCOUNT, _objectCount);
151 
152 	return true;
153 }
154 
restart()155 void Game::restart() {
156 	_stream->seek(_residentOffset + _saveAreaOffset);
157 	_stream->read(_saveArea, _saveSize);
158 	decrypt(_saveArea, _saveSize);
159 
160 	setVariable(V_OCOUNT, _objectCount);
161 	_restartFlag = true;
162 }
163 
shouldRestart()164 bool Game::shouldRestart() {
165 	bool result = _restartFlag;
166 	_restartFlag = false;
167 	return result;
168 }
169 
saveGameData(Common::WriteStream & ws)170 void Game::saveGameData(Common::WriteStream &ws) {
171 	ws.write(_saveArea, _saveSize);
172 }
173 
loadGameData(Common::ReadStream & rs)174 void Game::loadGameData(Common::ReadStream &rs) {
175 	rs.read(_saveArea, _saveSize);
176 }
177 
findWord(const Common::String & word) const178 int Game::findWord(const Common::String &word) const {
179 	// Limit the word to the maximum allowable size
180 	Common::String w(word.c_str(), word.c_str() + WORD_SIZE);
181 
182 	// Iterate over the dictionary for the word
183 	for (int idx = 1; idx <= _wordCount; ++idx) {
184 		int wordOffset = READ_LE_UINT16(_wordTable + idx * 2);
185 		if (w == (const char *)_dataSpace + wordOffset + 2)
186 			return readWord(wordOffset);
187 	}
188 
189 	return NIL;
190 }
191 
checkVerb(const Common::Array<int> & verbs)192 int Game::checkVerb(const Common::Array<int> &verbs) {
193 	// Iterate through the actions
194 	for (int idx = 1; idx <= _actionCount; ++idx) {
195 		if (hasVerb(idx, verbs))
196 			return idx;
197 	}
198 
199 	return NIL;
200 }
201 
findAction(const Common::Array<int> & verbs,int preposition,int flag)202 int Game::findAction(const Common::Array<int> &verbs, int preposition, int flag) {
203 	// Iterate through the actions
204 	for (int idx = 1; idx <= _actionCount; ++idx) {
205 		if ((preposition && !hasPreposition(idx, preposition)) || !hasVerb(idx, verbs))
206 			continue;
207 
208 		int mask = ~getActionByte(idx, A_MASK);
209 		if ((flag & mask) == (getActionByte(idx, A_FLAG) & mask))
210 			return idx;
211 	}
212 
213 	return NIL;
214 }
215 
getObjectProperty(int obj,int prop)216 int Game::getObjectProperty(int obj, int prop) {
217 	int field;
218 
219 	for (; obj; obj = getObjectField(obj, O_CLASS)) {
220 		if ((field = findProperty(obj, prop)) != 0)
221 			return getObjectField(obj, field);
222 	}
223 
224 	return NIL;
225 }
226 
setObjectProperty(int obj,int prop,int val)227 int Game::setObjectProperty(int obj, int prop, int val) {
228 	int field;
229 
230 	for (; obj; obj = getObjectField(obj, O_CLASS)) {
231 		if ((field = findProperty(obj, prop)) != 0)
232 			return setObjectField(obj, field, val);
233 	}
234 
235 	return NIL;
236 }
237 
getObjectLocation(int obj) const238 int Game::getObjectLocation(int obj) const {
239 	if (obj < 1 || obj > _objectCount)
240 		error("Invalid object number %d", obj);
241 
242 	return READ_LE_UINT16(_objectTable + obj * 2);
243 }
244 
getActionLocation(int action) const245 int Game::getActionLocation(int action) const {
246 	if (action < 1 || action > _actionCount)
247 		error("Invalid action number %d", action);
248 
249 	return READ_LE_UINT16(_actionTable + action * 2);
250 }
251 
getVariable(int variableNum)252 int Game::getVariable(int variableNum) {
253 	if (variableNum < 1 || variableNum > _variableCount)
254 		error("Invalid ariable number %d", variableNum);
255 
256 	return READ_LE_UINT16(_variableTable + variableNum * 2);
257 }
258 
setVariable(int variableNum,int value)259 void Game::setVariable(int variableNum, int value) {
260 	if (variableNum < 1 || variableNum > _variableCount)
261 		error("Invalid ariable number %d", variableNum);
262 
263 	WRITE_LE_UINT16(_variableTable + variableNum * 2, value);
264 }
265 
findProperty(int obj,int prop) const266 int Game::findProperty(int obj, int prop) const {
267 	int nProp = getObjectField(obj, O_NPROPERTIES);
268 
269 	for (int idx = 0, p = 0; idx < nProp; ++idx, p += 4) {
270 		if ((getObjectField(obj, O_PROPERTIES + p) & ~P_CLASS) == prop)
271 			return O_PROPERTIES + p + 2;
272 	}
273 
274 	return NIL;
275 }
276 
hasNoun(int obj,int noun) const277 bool Game::hasNoun(int obj, int noun) const {
278 	for (; obj; obj = getObjectField(obj, O_CLASS)) {
279 		if (inList(getObjectField(obj, O_NOUNS), noun))
280 			return true;
281 	}
282 
283 	return false;
284 }
285 
hasAdjective(int obj,int adjective) const286 bool Game::hasAdjective(int obj, int adjective) const {
287 	for (; obj; obj = getObjectField(obj, O_CLASS)) {
288 		if (inList(getObjectField(obj, O_ADJECTIVES), adjective))
289 			return true;
290 	}
291 
292 	return false;
293 }
294 
hasVerb(int act,const Common::Array<int> & verbs) const295 bool Game::hasVerb(int act, const Common::Array<int> &verbs) const {
296 	// Get the list of verbs
297 	int link = getActionField(act, A_VERBS);
298 
299 	// Look for the verb
300 	for (; link; link = readWord(link + L_NEXT)) {
301 		Common::Array<int>::const_iterator verb = verbs.begin();
302 		int word = readWord(link + L_DATA);
303 
304 		for (; verb < verbs.end() && word; ++verb, word = readWord(word + L_NEXT)) {
305 			if (*verb != readWord(word + L_DATA))
306 				break;
307 		}
308 
309 		if (verb == verbs.end() && !word)
310 			return true;
311 	}
312 
313 	return false;
314 }
315 
inList(int link,int word) const316 bool Game::inList(int link, int word) const {
317 	for (; link; link = readWord(link + L_NEXT)) {
318 		if (word == readWord(link + L_DATA))
319 			return true;
320 	}
321 
322 	return false;
323 }
324 
readString(int msg)325 Common::String Game::readString(int msg) {
326 	// Get the block to use, and ensure it's loaded
327 	_msgBlockNum = msg >> 7;
328 	_msgBlockOffset = (msg & 0x7f) << 2;
329 	readMsgBlock();
330 
331 	// Read the string
332 	Common::String result;
333 	char c;
334 
335 	while ((c = readMsgChar()) != '\0')
336 		result += c;
337 
338 	return result;
339 }
340 
readMsgChar()341 char Game::readMsgChar() {
342 	if (_msgBlockOffset >= MESSAGE_BLOCK_SIZE) {
343 		// Move to the next block
344 		++_msgBlockNum;
345 		_msgBlockOffset = 0;
346 		readMsgBlock();
347 	}
348 
349 	// Return next character
350 	return _msgCache[0]->_data[_msgBlockOffset++];
351 }
352 
readMsgBlock()353 void Game::readMsgBlock() {
354 	CacheEntry *ce;
355 
356 	// Check to see if the specified block is in the cache
357 	for (int idx = 0; idx < MESSAGE_CACHE_SIZE; ++idx) {
358 		if (_msgCache[idx]->_blockNum == _msgBlockNum) {
359 			// If it's not already at the top of the list, move it there to ensure
360 			// it'll be last to be unloaded as new blocks are loaded in
361 			if (idx != 0) {
362 				ce = _msgCache[idx];
363 				_msgCache.remove_at(idx);
364 				_msgCache.insert_at(0, ce);
365 			}
366 
367 			return;
368 		}
369 	}
370 
371 	// At this point we need to load a new block in. Discard the block at the end
372 	// and move it to the start for storing the new block to load
373 	ce = _msgCache.back();
374 	_msgCache.remove_at(_msgCache.size() - 1);
375 	_msgCache.insert_at(0, ce);
376 
377 	// Load the new block
378 	ce->_blockNum = _msgBlockNum;
379 	_stream->seek((_messageBlockOffset + _msgBlockNum) << 9);
380 	if (_stream->read(&ce->_data[0], MESSAGE_BLOCK_SIZE) != MESSAGE_BLOCK_SIZE)
381 		error("Error reading message block");
382 
383 	// Decode the loaded block
384 	for (int idx = 0; idx < MESSAGE_BLOCK_SIZE; ++idx)
385 		ce->_data[idx] = (ce->_data[idx] + 30) & 0xff;
386 }
387 
388 
389 } // End of namespace AdvSys
390 } // End of namespace Glk
391