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 "common/config-manager.h"
24 #include "common/substream.h"
25 
26 #include "engines/util.h"
27 
28 #include "graphics/macgui/macwindowmanager.h"
29 
30 #include "director/director.h"
31 #include "director/archive.h"
32 #include "director/cast.h"
33 #include "director/movie.h"
34 #include "director/score.h"
35 #include "director/window.h"
36 #include "director/lingo/lingo.h"
37 #include "director/lingo/lingo-object.h"
38 
39 namespace Director {
40 
Movie(Window * window)41 Movie::Movie(Window *window) {
42 	_window = window;
43 	_vm = _window->getVM();
44 	_lingo = _vm->getLingo();
45 
46 	_flags = 0;
47 	_stageColor = _window->_wm->_colorWhite;
48 
49 	_currentClickOnSpriteId = 0;
50 	_currentEditableTextChannel = 0;
51 	_lastEventTime = _vm->getMacTicks();
52 	_lastKeyTime = _lastEventTime;
53 	_lastClickTime = _lastEventTime;
54 	_lastRollTime = _lastEventTime;
55 	_lastTimerReset = _lastEventTime;
56 	_nextEventId = 0;
57 
58 	_videoPlayback = false;
59 
60 	_key = 0;
61 	_keyCode = 0;
62 	_keyFlags = 0;
63 
64 	_currentDraggedChannel = nullptr;
65 	_currentHiliteChannelId = 0;
66 	_mouseDownWasInButton = false;
67 
68 	_version = 0;
69 	_platform = Common::kPlatformMacintosh;
70 	_allowOutdatedLingo = false;
71 
72 	_movieArchive = nullptr;
73 
74 	_cast = new Cast(this, 0);
75 	_sharedCast = nullptr;
76 	_score = new Score(this);
77 
78 	_selEnd = -1;
79 	_selStart = -1;
80 
81 	_checkBoxType = 0;
82 	_checkBoxAccess = 0;
83 
84 	_lastTimeOut = _lastEventTime;
85 	_timeOutLength = 10800;	// D4 dictionary p297, default value is 3minutes
86 	// default value of keydown and mouse is true, for timeOutPlay is false. check D4 dictionary p297
87 	_timeOutKeyDown = true;
88 	_timeOutMouse = true;
89 	_timeOutPlay = false;
90 }
91 
~Movie()92 Movie::~Movie() {
93 	// _movieArchive is shared with the cast, so the cast will free it
94 	delete _cast;
95 	delete _sharedCast;
96 	delete _score;
97 }
98 
setArchive(Archive * archive)99 void Movie::setArchive(Archive *archive) {
100 	_movieArchive = archive;
101 
102 	if (archive->hasResource(MKTAG('M', 'C', 'N', 'M'), 0)) {
103 		_macName = archive->getName(MKTAG('M', 'C', 'N', 'M'), 0).c_str();
104 	} else {
105 		_macName = archive->getFileName();
106 	}
107 
108 	_cast->setArchive(archive);
109 
110 	// Frame Labels
111 	if (archive->hasResource(MKTAG('V', 'W', 'L', 'B'), -1)) {
112 		Common::SeekableReadStreamEndian *r;
113 		_score->loadLabels(*(r = archive->getFirstResource(MKTAG('V', 'W', 'L', 'B'))));
114 		delete r;
115 	}
116 }
117 
loadArchive()118 bool Movie::loadArchive() {
119 	Common::SeekableReadStreamEndian *r = nullptr;
120 
121 	// Config
122 	if (!_cast->loadConfig())
123 		return false;
124 
125 	_version = _cast->_version;
126 	_platform = _cast->_platform;
127 	_movieRect = _cast->_movieRect;
128 	// Wait to handle _stageColor until palette is loaded in loadCast...
129 
130 	// File Info
131 	if (_movieArchive->hasResource(MKTAG('V', 'W', 'F', 'I'), -1)) {
132 		loadFileInfo(*(r = _movieArchive->getFirstResource(MKTAG('V', 'W', 'F', 'I'))));
133 		delete r;
134 	}
135 
136 	// Cast
137 	_cast->loadCast();
138 	_stageColor = _vm->transformColor(_cast->_stageColor);
139 
140 	bool recenter = false;
141 	// If the stage dimensions are different, delete it and start again.
142 	// Otherwise, do not clear it so there can be a nice transition.
143 	if (_window->getSurface()->w != _movieRect.width() || _window->getSurface()->h != _movieRect.height()) {
144 		_window->resize(_movieRect.width(), _movieRect.height(), true);
145 		recenter = true;
146 	}
147 
148 	// TODO: Add more options for desktop dimensions
149 	if (_window == _vm->getStage()) {
150 		uint16 windowWidth = debugChannelSet(-1, kDebugDesktop) ? 1024 : _movieRect.width();
151 		uint16 windowHeight = debugChannelSet(-1, kDebugDesktop) ? 768 : _movieRect.height();
152 		if (_vm->_wm->_screenDims.width() != windowWidth || _vm->_wm->_screenDims.height() != windowHeight) {
153 			_vm->_wm->resizeScreen(windowWidth, windowHeight);
154 			recenter = true;
155 
156 			initGraphics(windowWidth, windowHeight, &_vm->_pixelformat);
157 		}
158 	}
159 
160 	if (recenter && debugChannelSet(-1, kDebugDesktop))
161 		_window->center(g_director->_centerStage);
162 
163 	_window->setStageColor(_stageColor, true);
164 
165 	// Score
166 	if (!_movieArchive->hasResource(MKTAG('V', 'W', 'S', 'C'), -1)) {
167 		warning("Movie::loadArchive(): Wrong movie format. VWSC resource missing");
168 		return false;
169 	}
170 	_score->loadFrames(*(r = _movieArchive->getFirstResource(MKTAG('V', 'W', 'S', 'C'))), _version);
171 	delete r;
172 
173 	// Action list
174 	if (_movieArchive->hasResource(MKTAG('V', 'W', 'A', 'C'), -1)) {
175 		_score->loadActions(*(r = _movieArchive->getFirstResource(MKTAG('V', 'W', 'A', 'C'))));
176 		delete r;
177 	}
178 
179 	_score->setSpriteCasts();
180 
181 	return true;
182 }
183 
readRect(Common::ReadStreamEndian & stream)184 Common::Rect Movie::readRect(Common::ReadStreamEndian &stream) {
185 	Common::Rect rect;
186 	rect.top = stream.readSint16();
187 	rect.left = stream.readSint16();
188 	rect.bottom = stream.readSint16();
189 	rect.right = stream.readSint16();
190 
191 	return rect;
192 }
193 
loadInfoEntries(Common::SeekableReadStreamEndian & stream,uint16 version)194 InfoEntries Movie::loadInfoEntries(Common::SeekableReadStreamEndian &stream, uint16 version) {
195 	uint32 offset = stream.pos();
196 	offset += stream.readUint32();
197 
198 	InfoEntries res;
199 	res.unk1 = stream.readUint32();
200 	res.unk2 = stream.readUint32();
201 	res.flags = stream.readUint32();
202 
203 	if (version >= kFileVer400)
204 		res.scriptId = stream.readUint32();
205 
206 	stream.seek(offset);
207 	uint16 count = stream.readUint16() + 1;
208 
209 	debugC(3, kDebugLoading, "Movie::loadInfoEntries(): InfoEntry: %d entries", count - 1);
210 
211 	if (count == 1)
212 		return res;
213 
214 	uint32 *entries = (uint32 *)calloc(count, sizeof(uint32));
215 
216 	for (uint i = 0; i < count; i++)
217 		entries[i] = stream.readUint32();
218 
219 	res.strings.resize(count - 1);
220 
221 	for (uint16 i = 0; i < count - 1; i++) {
222 		res.strings[i].len = entries[i + 1] - entries[i];
223 		res.strings[i].data = (byte *)malloc(res.strings[i].len);
224 		stream.read(res.strings[i].data, res.strings[i].len);
225 
226 		debugC(6, kDebugLoading, "InfoEntry %d: %d bytes", i, res.strings[i].len);
227 	}
228 
229 	free(entries);
230 
231 	return res;
232 }
233 
loadFileInfo(Common::SeekableReadStreamEndian & stream)234 void Movie::loadFileInfo(Common::SeekableReadStreamEndian &stream) {
235 	debugC(2, kDebugLoading, "****** Loading FileInfo VWFI");
236 
237 	InfoEntries fileInfo = Movie::loadInfoEntries(stream, _version);
238 
239 	_allowOutdatedLingo = (fileInfo.flags & kMovieFlagAllowOutdatedLingo) != 0;
240 
241 	_script = fileInfo.strings[0].readString(false);
242 
243 	if (!_script.empty() && ConfMan.getBool("dump_scripts"))
244 		_cast->dumpScript(_script.c_str(), kMovieScript, 0);
245 
246 	if (!_script.empty())
247 		_cast->_lingoArchive->addCode(_script, kMovieScript, 0);
248 
249 	_changedBy = fileInfo.strings[1].readString();
250 	_createdBy = fileInfo.strings[2].readString();
251 	_createdBy = fileInfo.strings[3].readString();
252 
253 	uint16 preload = 0;
254 	if (fileInfo.strings[4].len) {
255 		if (stream.isBE())
256 			preload = READ_BE_INT16(fileInfo.strings[4].data);
257 		else
258 			preload = READ_LE_INT16(fileInfo.strings[4].data);
259 	}
260 
261 	if (debugChannelSet(3, kDebugLoading)) {
262 		debug("VWFI: flags: %d", fileInfo.flags);
263 		debug("VWFI: allow outdated lingo: %d", _allowOutdatedLingo);
264 		debug("VWFI: script: '%s'", _script.c_str());
265 		debug("VWFI: changed by: '%s'", _changedBy.c_str());
266 		debug("VWFI: created by: '%s'", _createdBy.c_str());
267 		debug("VWFI: directory: '%s'", _createdBy.c_str());
268 		debug("VWFI: preload: %d (0x%x)", preload, preload);
269 
270 		for (uint i = 5; i < fileInfo.strings.size(); i++) {
271 			debug("VWFI: entry %d (%d bytes)", i, fileInfo.strings[i].len);
272 			Common::hexdump(fileInfo.strings[i].data, fileInfo.strings[i].len);
273 		}
274 	}
275 }
276 
clearSharedCast()277 void Movie::clearSharedCast() {
278 	if (!_sharedCast)
279 		return;
280 
281 	delete _sharedCast;
282 
283 	_sharedCast = nullptr;
284 }
285 
loadSharedCastsFrom(Common::String filename)286 void Movie::loadSharedCastsFrom(Common::String filename) {
287 	clearSharedCast();
288 
289 	Archive *sharedCast = _vm->createArchive();
290 
291 	if (!sharedCast->openFile(filename)) {
292 		warning("loadSharedCastsFrom(): No shared cast %s", filename.c_str());
293 
294 		delete sharedCast;
295 
296 		return;
297 	}
298 	sharedCast->setPathName(filename);
299 
300 	debug(0, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
301 	debug(0, "@@@@   Loading shared cast '%s'", filename.c_str());
302 	debug(0, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
303 
304 	_sharedCast = new Cast(this, 0, true);
305 	_sharedCast->setArchive(sharedCast);
306 	_sharedCast->loadArchive();
307 }
308 
getCastMember(CastMemberID memberID)309 CastMember *Movie::getCastMember(CastMemberID memberID) {
310 	CastMember *result = nullptr;
311 	if (memberID.castLib == 0) {
312 		result = _cast->getCastMember(memberID.member);
313 		if (result == nullptr && _sharedCast) {
314 			result = _sharedCast->getCastMember(memberID.member);
315 		}
316 	} else {
317 		warning("Movie::getCastMember: Unknown castLib %d", memberID.castLib);
318 	}
319 	return result;
320 }
321 
getCastMemberByName(const Common::String & name,int castLib)322 CastMember *Movie::getCastMemberByName(const Common::String &name, int castLib) {
323 	CastMember *result = nullptr;
324 	if (castLib == 0) {
325 		result = _cast->getCastMemberByName(name);
326 		if (result == nullptr && _sharedCast) {
327 			result = _sharedCast->getCastMemberByName(name);
328 		}
329 	} else {
330 		warning("Movie::getCastMemberByName: Unknown castLib %d", castLib);
331 	}
332 	return result;
333 }
334 
getCastMemberInfo(CastMemberID memberID)335 CastMemberInfo *Movie::getCastMemberInfo(CastMemberID memberID) {
336 	CastMemberInfo *result = nullptr;
337 	if (memberID.castLib == 0) {
338 		result = _cast->getCastMemberInfo(memberID.member);
339 		if (result == nullptr && _sharedCast) {
340 			result = _sharedCast->getCastMemberInfo(memberID.member);
341 		}
342 	} else {
343 		warning("Movie::getCastMemberInfo: Unknown castLib %d", memberID.castLib);
344 	}
345 	return result;
346 }
347 
getStxt(CastMemberID memberID)348 const Stxt *Movie::getStxt(CastMemberID memberID) {
349 	const Stxt *result = nullptr;
350 	if (memberID.castLib == 0) {
351 		result = _cast->getStxt(memberID.member);
352 		if (result == nullptr && _sharedCast) {
353 			result = _sharedCast->getStxt(memberID.member);
354 		}
355 	} else {
356 		warning("Movie::getStxt: Unknown castLib %d", memberID.castLib);
357 	}
358 	return result;
359 }
360 
getMainLingoArch()361 LingoArchive *Movie::getMainLingoArch() {
362 	return _cast->_lingoArchive;
363 }
364 
getSharedLingoArch()365 LingoArchive *Movie::getSharedLingoArch() {
366 	return _sharedCast ? _sharedCast->_lingoArchive : nullptr;
367 }
368 
getScriptContext(ScriptType type,CastMemberID id)369 ScriptContext *Movie::getScriptContext(ScriptType type, CastMemberID id) {
370 	ScriptContext *result = nullptr;
371 	if (id.castLib == 0) {
372 		result = _cast->_lingoArchive->getScriptContext(type, id.member);
373 		if (result == nullptr && _sharedCast) {
374 			result = _sharedCast->_lingoArchive->getScriptContext(type, id.member);
375 		}
376 	} else {
377 		warning("Movie::getScriptContext: Unknown castLib %d", id.castLib);
378 	}
379 	return result;
380 }
381 
getHandler(const Common::String & name)382 Symbol Movie::getHandler(const Common::String &name) {
383 	if (_cast->_lingoArchive->functionHandlers.contains(name))
384 		return _cast->_lingoArchive->functionHandlers[name];
385 
386 	if (_sharedCast && _sharedCast->_lingoArchive->functionHandlers.contains(name))
387 		return _sharedCast->_lingoArchive->functionHandlers[name];
388 
389 	return Symbol();
390 }
391 
readString(bool pascal)392 Common::String InfoEntry::readString(bool pascal) {
393 	Common::String res;
394 
395 	if (len == 0)
396 		return res;
397 
398 	uint start = pascal ? 1 : 0; // skip length for Pascal string
399 
400 	Common::String encodedStr;
401 	for (uint i = start; i < len; i++) {
402 		if (!Common::isCntrl(data[i]) || Common::isSpace(data[i]))
403 			encodedStr += data[i];
404 	}
405 
406 	// FIXME: Use the case which contains this string, not the main cast.
407 	return g_director->getCurrentMovie()->getCast()->decodeString(encodedStr).encode(Common::kUtf8);
408 }
409 
410 } // End of namespace Director
411