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 "scumm/scumm_v4.h"
24 #include "scumm/object.h"
25 
26 namespace Scumm {
27 
28 #define OPCODE(i, x)	_opcodes[i]._OPCODE(ScummEngine_v4, x)
29 
setupOpcodes()30 void ScummEngine_v4::setupOpcodes() {
31 	ScummEngine_v5::setupOpcodes();
32 
33 	OPCODE(0x25, o5_drawObject);
34 	OPCODE(0x45, o5_drawObject);
35 	OPCODE(0x65, o5_drawObject);
36 	OPCODE(0xa5, o5_drawObject);
37 	OPCODE(0xc5, o5_drawObject);
38 	OPCODE(0xe5, o5_drawObject);
39 
40 	OPCODE(0x50, o4_pickupObject);
41 	OPCODE(0xd0, o4_pickupObject);
42 
43 	OPCODE(0x5c, o4_oldRoomEffect);
44 	OPCODE(0xdc, o4_oldRoomEffect);
45 
46 	OPCODE(0x0f, o4_ifState);
47 	OPCODE(0x4f, o4_ifState);
48 	OPCODE(0x8f, o4_ifState);
49 	OPCODE(0xcf, o4_ifState);
50 
51 	OPCODE(0x2f, o4_ifNotState);
52 	OPCODE(0x6f, o4_ifNotState);
53 	OPCODE(0xaf, o4_ifNotState);
54 	OPCODE(0xef, o4_ifNotState);
55 
56 	OPCODE(0xa7, o4_saveLoadVars);
57 
58 	OPCODE(0x22, o4_saveLoadGame);
59 	OPCODE(0xa2, o4_saveLoadGame);
60 
61 	// Disable some opcodes which are unused in v4.
62 	_opcodes[0x3b].setProc(0, 0);
63 	_opcodes[0x4c].setProc(0, 0);
64 	_opcodes[0xbb].setProc(0, 0);
65 }
66 
o4_ifState()67 void ScummEngine_v4::o4_ifState() {
68 	int a = getVarOrDirectWord(PARAM_1);
69 	int b = getVarOrDirectByte(PARAM_2);
70 
71 	// WORKAROUND bug #3306145 (also occurs in original): Some old versions of
72 	// Indy3 sometimes fail to allocate IQ points correctly. To quote:
73 	// "About the points error leaving Castle Brunwald: It seems to "reversed"!
74 	// When you get caught, free yourself and escape, you get 25 IQ points even
75 	// though you're not supposed to. However if you escape WITHOUT getting
76 	// caught, you get 0 IQ points (supposed to get 25 IQ points)."
77 	// This workaround is meant to address that.
78 	if (_game.id == GID_INDY3 && a == 367 &&
79 	    vm.slot[_currentScript].number == 363 && _currentRoom == 25) {
80 		b = 0;
81 	}
82 
83 	jumpRelative(getState(a) == b);
84 }
85 
o4_ifNotState()86 void ScummEngine_v4::o4_ifNotState() {
87 	int a = getVarOrDirectWord(PARAM_1);
88 	int b = getVarOrDirectByte(PARAM_2);
89 
90 	jumpRelative(getState(a) != b);
91 }
92 
o4_pickupObject()93 void ScummEngine_v4::o4_pickupObject() {
94 	int obj = getVarOrDirectWord(PARAM_1);
95 
96 	if (obj < 1) {
97 		error("pickupObjectOld received invalid index %d (script %d)", obj, vm.slot[_currentScript].number);
98 	}
99 
100 	if (getObjectIndex(obj) == -1)
101 		return;
102 
103 	if (whereIsObject(obj) == WIO_INVENTORY)	// Don't take an object twice
104 		return;
105 
106 	// debug(0, "adding %d from %d to inventoryOld", obj, _currentRoom);
107 	addObjectToInventory(obj, _roomResource);
108 	markObjectRectAsDirty(obj);
109 	putOwner(obj, VAR(VAR_EGO));
110 	putClass(obj, kObjectClassUntouchable, 1);
111 	putState(obj, 1);
112 	clearDrawObjectQueue();
113 	runInventoryScript(1);
114 }
115 
o4_oldRoomEffect()116 void ScummEngine_v4::o4_oldRoomEffect() {
117 	int a;
118 
119 	_opcode = fetchScriptByte();
120 	if ((_opcode & 0x1F) == 3) {
121 		a = getVarOrDirectWord(PARAM_1);
122 
123 		if (_game.platform == Common::kPlatformFMTowns && _game.version == 3) {
124 			if (a == 4) {
125 				_textSurface.fillRect(Common::Rect(0, 0, _textSurface.w * _textSurfaceMultiplier, _textSurface.h * _textSurfaceMultiplier), 0);
126 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
127 				if (_townsScreen)
128 					_townsScreen->clearLayer(1);
129 #endif
130 				return;
131 			}
132 		}
133 
134 		if (a) {
135 			_switchRoomEffect = (byte)(a & 0xFF);
136 			_switchRoomEffect2 = (byte)(a >> 8);
137 		} else {
138 			fadeIn(_newEffect);
139 		}
140 	}
141 }
142 
o4_saveLoadVars()143 void ScummEngine_v4::o4_saveLoadVars() {
144 	if (fetchScriptByte() == 1)
145 		saveVars();
146 	else
147 		loadVars();
148 }
149 
150 enum StringIds {
151 	// The string IDs used by Indy3 to store the episode resp. series IQ points.
152 	// Note that we save the episode IQ points but load the series IQ points,
153 	// which matches the original Indy3 save/load code. See also the notes
154 	// on Feature Request #1666521.
155 	STRINGID_IQ_EPISODE = 7,
156 	STRINGID_IQ_SERIES = 9,
157 	// The string IDs of the first savegame name, used as an offset to determine
158 	// the IDs of all savenames.
159 	// Loom is the only game whose savenames start with a different ID.
160 	STRINGID_SAVENAME1 = 10,
161 	STRINGID_SAVENAME1_LOOM = 9
162 };
163 
saveVars()164 void ScummEngine_v4::saveVars() {
165 	int a, b;
166 
167 	while ((_opcode = fetchScriptByte()) != 0) {
168 		switch (_opcode & 0x1F) {
169 		case 0x01: // write a range of variables
170 			getResultPos();
171 			a = _resultVarNumber;
172 			getResultPos();
173 			b = _resultVarNumber;
174 			debug(0, "stub saveVars: vars %d -> %d", a, b);
175 			break;
176 		case 0x02: // write a range of string variables
177 			a = getVarOrDirectByte(PARAM_1);
178 			b = getVarOrDirectByte(PARAM_2);
179 
180 			if (a == STRINGID_IQ_EPISODE && b == STRINGID_IQ_EPISODE) {
181 				if (_game.id == GID_INDY3) {
182 					saveIQPoints();
183 				}
184 				break;
185 			}
186 			// FIXME: changing savegame-names not supported
187 			break;
188 		case 0x03: // open file
189 			a = resStrLen(_scriptPointer);
190 			strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a);
191 			_saveLoadVarsFilename[a] = '\0';
192 			_scriptPointer += a + 1;
193 			break;
194 		case 0x04:
195 			return;
196 		case 0x1F: // close file
197 			_saveLoadVarsFilename[0] = '\0';
198 			return;
199 		}
200 	}
201 }
202 
loadVars()203 void ScummEngine_v4::loadVars() {
204 	int a, b;
205 
206 	while ((_opcode = fetchScriptByte()) != 0) {
207 		switch (_opcode & 0x1F) {
208 		case 0x01: // read a range of variables
209 			getResultPos();
210 			a = _resultVarNumber;
211 			getResultPos();
212 			b = _resultVarNumber;
213 			debug(0, "stub loadVars: vars %d -> %d", a, b);
214 			break;
215 		case 0x02: // read a range of string variables
216 			a = getVarOrDirectByte(PARAM_1);
217 			b = getVarOrDirectByte(PARAM_2);
218 
219 			int slot;
220 			int slotSize;
221 			byte* slotContent;
222 			int savegameId;
223 			bool avail_saves[100];
224 
225 			if (a == STRINGID_IQ_SERIES && b == STRINGID_IQ_SERIES) {
226 				// Zak256 loads the IQ script-slot but does not use it -> ignore it
227 				if (_game.id == GID_INDY3) {
228 					byte *ptr = getResourceAddress(rtString, STRINGID_IQ_SERIES);
229 					if (ptr) {
230 						int size = getResourceSize(rtString, STRINGID_IQ_SERIES);
231 						loadIQPoints(ptr, size);
232 					}
233 				}
234 				break;
235 			}
236 
237 			listSavegames(avail_saves, ARRAYSIZE(avail_saves));
238 			for (slot = a; slot <= b; ++slot) {
239 				slotSize = getResourceSize(rtString, slot);
240 				slotContent = getResourceAddress(rtString, slot);
241 
242 				// load savegame names
243 				savegameId = slot - a + 1;
244 				Common::String name;
245 				if (avail_saves[savegameId] && getSavegameName(savegameId, name)) {
246 					int pos;
247 					const char *ptr = name.c_str();
248 					// slotContent ends with {'\0','@'} -> max. length = slotSize-2
249 					for (pos = 0; pos < slotSize - 2; ++pos) {
250 						if (!ptr[pos])
251 							break;
252 						// replace special characters
253 						if (ptr[pos] >= 32 && ptr[pos] <= 122 && ptr[pos] != 64)
254 							slotContent[pos] = ptr[pos];
255 						else
256 							slotContent[pos] = '_';
257 					}
258 					slotContent[pos] = '\0';
259 				} else {
260 					slotContent[0] = '\0';
261 				}
262 			}
263 			break;
264 		case 0x03: // open file
265 			a = resStrLen(_scriptPointer);
266 			strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a);
267 			_saveLoadVarsFilename[a] = '\0';
268 			_scriptPointer += a + 1;
269 			break;
270 		case 0x04:
271 			return;
272 		case 0x1F: // close file
273 			_saveLoadVarsFilename[0] = '\0';
274 			return;
275 		}
276 	}
277 }
278 
279 /**
280  * IQ Point calculation for Indy3.
281  * The scripts that perform this task are
282  * - script-9 (save/load dialog initialization, loads room 14),
283  * - room-14-204 (load series IQ string),
284  * - room-14-205 (save series IQ string),
285  * - room-14-206 (calculate series IQ string).
286  * Unfortunately script-9 contains lots of GUI stuff so calling this script
287  * directly is not possible. The other scripts depend on script-9.
288  */
updateIQPoints()289 void ScummEngine_v4::updateIQPoints() {
290 	int seriesIQ;
291 	// IQString[0..72] corresponds to each puzzle's IQ.
292 	// IQString[73] indicates that the IQ-file was loaded successfully and is always 0 when
293 	// the IQ is calculated, hence it will be ignored here.
294 	const int NUM_PUZZLES = 73;
295 	byte seriesIQString[NUM_PUZZLES];
296 	byte *episodeIQString;
297 	int episodeIQStringSize;
298 
299 	// load string with IQ points given per puzzle in any savegame
300 	// IMPORTANT: the resource string STRINGID_IQ_SERIES is only valid while
301 	// the original save/load dialog is executed, so do not use it here.
302 	memset(seriesIQString, 0, sizeof(seriesIQString));
303 	loadIQPoints(seriesIQString, sizeof(seriesIQString));
304 
305 	// string with IQ points given per puzzle in current savegame
306 	episodeIQString = getResourceAddress(rtString, STRINGID_IQ_EPISODE);
307 	if (!episodeIQString)
308 		return;
309 	episodeIQStringSize = getResourceSize(rtString, STRINGID_IQ_EPISODE);
310 	if (episodeIQStringSize < NUM_PUZZLES)
311 		return;
312 
313 	// merge episode and series IQ strings and calculate series IQ
314 	seriesIQ = 0;
315 	// iterate over puzzles
316 	for (int i = 0; i < NUM_PUZZLES; ++i) {
317 		byte puzzleIQ = seriesIQString[i];
318 		// if puzzle is solved copy points to episode string
319 		if (puzzleIQ > 0)
320 			episodeIQString[i] = puzzleIQ;
321 		// add puzzle's IQ-points to series IQ
322 		seriesIQ += episodeIQString[i];
323 	}
324 	_scummVars[245] = seriesIQ;
325 
326 	// save series IQ string
327 	saveIQPoints();
328 }
329 
saveIQPoints()330 void ScummEngine_v4::saveIQPoints() {
331 	// save Indy3 IQ-points
332 	Common::OutSaveFile *file;
333 	Common::String filename = _targetName + ".iq";
334 
335 	file = _saveFileMan->openForSaving(filename);
336 	if (file != NULL) {
337 		byte *ptr = getResourceAddress(rtString, STRINGID_IQ_EPISODE);
338 		if (ptr) {
339 			int size = getResourceSize(rtString, STRINGID_IQ_EPISODE);
340 			file->write(ptr, size);
341 		}
342 		delete file;
343 	}
344 }
345 
loadIQPoints(byte * ptr,int size)346 void ScummEngine_v4::loadIQPoints(byte *ptr, int size) {
347 	// load Indy3 IQ-points
348 	Common::InSaveFile *file;
349 	Common::String filename = _targetName + ".iq";
350 
351 	file = _saveFileMan->openForLoading(filename);
352 	if (file != NULL) {
353 		byte *tmp = (byte *)malloc(size);
354 		int nread = file->read(tmp, size);
355 		if (nread == size) {
356 			memcpy(ptr, tmp, size);
357 		}
358 		free(tmp);
359 		delete file;
360 	}
361 }
362 
o4_saveLoadGame()363 void ScummEngine_v4::o4_saveLoadGame() {
364 	getResultPos();
365 	byte slot;
366 	byte a = getVarOrDirectByte(PARAM_1);
367 	byte result = 0;
368 
369 	if ((_game.id == GID_MANIAC && _game.version <= 1) || (_game.id == GID_ZAK && _game.platform == Common::kPlatformC64)) {
370 		// Convert V0/V1 load/save screen (they support only one savegame per disk)
371 		// 1 Load
372 		// 2 Save
373 		slot = 1;
374 		if (a == 1)
375 			_opcode = 0x40;
376 		else if ((a == 2) || (_game.platform == Common::kPlatformNES))
377 			_opcode = 0x80;
378 	} else {
379 		slot = a & 0x1F;
380 		// Slot numbers in older games start with 0, in newer games with 1
381 		if (_game.version <= 2)
382 			slot++;
383 		_opcode = a & 0xE0;
384 	}
385 
386 	switch (_opcode) {
387 	case 0x00: // num slots available
388 		result = 100;
389 		break;
390 	case 0x20: // drive
391 		if (_game.version <= 3) {
392 			// 0 = ???
393 			// [1,2] = disk drive [A:,B:]
394 			// 3 = hard drive
395 			result = 3;
396 		} else {
397 			// set current drive
398 			result = 1;
399 		}
400 		break;
401 	case 0x40: // load
402 		if (loadState(slot, false))
403 			result = 3; // sucess
404 		else
405 			result = 5; // failed to load
406 		break;
407 	case 0x80: // save
408 		if (_game.version <= 3) {
409 			char name[32];
410 			if (_game.version <= 2) {
411 				// use generic name
412 				sprintf(name, "Game %c", 'A'+slot-1);
413 			} else {
414 				// use name entered by the user
415 				char* ptr;
416 				int firstSlot = (_game.id == GID_LOOM) ? STRINGID_SAVENAME1_LOOM : STRINGID_SAVENAME1;
417 				ptr = (char *)getStringAddress(slot + firstSlot - 1);
418 				Common::strlcpy(name, ptr, sizeof(name));
419 			}
420 
421 			if (savePreparedSavegame(slot, name))
422 				result = 0;
423 			else
424 				result = 2;
425 		} else {
426 			result = 2; // failed to save
427 		}
428 		break;
429 	case 0xC0: // test if save exists
430 		{
431 		Common::InSaveFile *file;
432 		bool avail_saves[100];
433 
434 		listSavegames(avail_saves, ARRAYSIZE(avail_saves));
435 		Common::String filename = makeSavegameName(slot, false);
436 		if (avail_saves[slot] && (file = _saveFileMan->openForLoading(filename))) {
437 			result = 6; // save file exists
438 			delete file;
439 		} else
440 			result = 7; // save file does not exist
441 		}
442 		break;
443 	default:
444 		error("o4_saveLoadGame: unknown subopcode %d", _opcode);
445 	}
446 
447 	setResult(result);
448 }
449 
450 } // End of namespace Scumm
451