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