1 /* ResidualVM - A 3D game interpreter
2 *
3 * ResidualVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the AUTHORS
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 "engines/myst3/database.h"
24
25 #include "common/archive.h"
26 #include "common/debug.h"
27 #include "common/substream.h"
28
29 namespace Myst3 {
30
31 /**
32 * An abstract node transformation.
33 *
34 * Subclasses can read data related to a node from a stream in read,
35 * and transform a node using that data in apply.
36 */
37 class NodeTransform {
38 public :
~NodeTransform()39 virtual ~NodeTransform() {};
40
41 virtual void read(Common::SeekableReadStream *file) = 0;
42 virtual void apply(NodePtr &node) = 0;
43 };
44
45 /**
46 * A node transformation that reads hotspots and scripts,
47 * and adds them to a node.
48 */
49 class NodeTransformAddHotspots : public NodeTransform {
50 public :
51 NodeTransformAddHotspots();
52
53 void read(Common::SeekableReadStream *file);
54 void apply(NodePtr &node);
55
56 private:
57 int32 _zipBitIndex;
58 Common::Array<CondScript> _scripts;
59 Common::Array<HotSpot> _hotspots;
60 };
61
62 /**
63 * A node transformation that reads scripts, and adds them to a node's
64 * sound scripts.
65 */
66 class NodeTransformAddSoundScripts : public NodeTransform {
67 public :
68 void read(Common::SeekableReadStream *file) override;
69 void apply(NodePtr &node) override;
70
71 private:
72 Common::Array<CondScript> _scripts;
73 };
74
75 /**
76 * A node transformation that reads scripts, and adds them to a node's
77 * background sound scripts.
78 */
79 class NodeTransformAddBackgroundSoundScripts : public NodeTransform {
80 public :
81 void read(Common::SeekableReadStream *file) override;
82 void apply(NodePtr &node) override;
83
84 private:
85 Common::Array<CondScript> _scripts;
86 };
87
88 /**
89 * Walks through a stream of nodes. For each encountered node, a NodeTransformer is called.
90 */
91 class NodeWalker {
92 public :
93 NodeWalker(NodeTransform *transform);
94 ~NodeWalker();
95
96 void read(Common::SeekableReadStream *file, Common::Array<NodePtr> &allNodes, bool createMissingSharedNodes);
97
98 private:
99 NodeTransform *_transform;
100 };
101
102 /**
103 * A collection of functions used to read script related data
104 */
105 class ScriptData {
106 public:
107 static Common::Array<CondScript> readCondScripts(Common::SeekableReadStream &s);
108 static Common::Array<Opcode> readOpcodes(Common::ReadStream &s);
109 static Common::Array<HotSpot> readHotspots(Common::ReadStream &s);
110 static Common::Array<PolarRect> readRects(Common::ReadStream &s);
111 static CondScript readCondScript(Common::SeekableReadStream &s);
112 static HotSpot readHotspot(Common::ReadStream &s);
113
114 private:
ScriptData()115 ScriptData() {};
116 };
117
readRects(Common::ReadStream & s)118 Common::Array<PolarRect> ScriptData::readRects(Common::ReadStream &s) {
119 Common::Array<PolarRect> rects;
120
121 bool lastRect = false;
122 do {
123 PolarRect rect;
124 rect.centerPitch = s.readUint16LE();
125 rect.centerHeading = s.readUint16LE();
126 rect.width = s.readUint16LE();
127 rect.height = s.readUint16LE();
128
129 if (rect.width < 0) {
130 rect.width = -rect.width;
131 } else {
132 lastRect = true;
133 }
134
135 rects.push_back(rect);
136 } while (!lastRect && !s.eos());
137
138 return rects;
139 }
140
readOpcodes(Common::ReadStream & s)141 Common::Array<Opcode> ScriptData::readOpcodes(Common::ReadStream &s) {
142 Common::Array<Opcode> script;
143
144 while (!s.eos()) {
145 Opcode opcode;
146 uint16 code = s.readUint16LE();
147
148 opcode.op = code & 0xff;
149 uint8 count = code >> 8;
150 if (count == 0 && opcode.op == 0)
151 break;
152
153 for (int i = 0; i < count; i++) {
154 int16 value = s.readSint16LE();
155 opcode.args.push_back(value);
156 }
157
158 script.push_back(opcode);
159 }
160
161 return script;
162 }
163
readCondScript(Common::SeekableReadStream & s)164 CondScript ScriptData::readCondScript(Common::SeekableReadStream &s) {
165 CondScript script;
166 script.condition = s.readUint16LE();
167 if(!script.condition)
168 return script;
169
170 // WORKAROUND: Original data bug in MATO 32765
171 // The script data for node MATO 32765 is missing its first two bytes
172 // of data, resulting in incorrect opcodes being read
173
174 // Original disassembly:
175 // init 0 > c[v565 != 0]
176 // op 115, ifVarInRange ( )
177 // op 45, inventoryAddBack ( )
178 // op 53, varSetValue ( vSunspotColor 4090 )
179 // op 53, varSetValue ( vSunspotRadius 40 )
180 // op 33, waterEffectSetWave ( 100 80 )
181 // op 32, waterEffectSetAttenuation ( 359 )
182 // op 31, waterEffectSetSpeed ( 15 )
183
184 // Fixed disassembly
185 // init 0 > c[v1 != 0]
186 // op 53, varSetValue ( vSunspotIntensity 45 )
187 // op 53, varSetValue ( vSunspotColor 4090 )
188 // op 53, varSetValue ( vSunspotRadius 40 )
189 // op 33, waterEffectSetWave ( 100 80 )
190 // op 32, waterEffectSetAttenuation ( 359 )
191 // op 31, waterEffectSetSpeed ( 15 )
192
193 if (script.condition == 565) {
194 script.condition = 1;
195 s.seek(-2, SEEK_CUR);
196 }
197 // END WORKAROUND
198
199 script.script = readOpcodes(s);
200
201 return script;
202 }
203
readCondScripts(Common::SeekableReadStream & s)204 Common::Array<CondScript> ScriptData::readCondScripts(Common::SeekableReadStream &s) {
205 Common::Array<CondScript> scripts;
206
207 while (!s.eos()) {
208 CondScript script = readCondScript(s);
209
210 if (!script.condition)
211 break;
212
213 scripts.push_back(script);
214 }
215
216 return scripts;
217 }
218
readHotspot(Common::ReadStream & s)219 HotSpot ScriptData::readHotspot(Common::ReadStream &s) {
220 HotSpot hotspot;
221
222 hotspot.condition = s.readUint16LE();
223
224 if (hotspot.condition == 0)
225 return hotspot;
226
227 if (hotspot.condition != -1) {
228 hotspot.rects = readRects(s);
229 hotspot.cursor = s.readUint16LE();
230 }
231
232 hotspot.script = readOpcodes(s);
233
234 return hotspot;
235 }
236
readHotspots(Common::ReadStream & s)237 Common::Array<HotSpot> ScriptData::readHotspots(Common::ReadStream &s) {
238 Common::Array<HotSpot> scripts;
239
240 while (!s.eos()) {
241 HotSpot hotspot = readHotspot(s);
242
243 if (!hotspot.condition)
244 break;
245
246 scripts.push_back(hotspot);
247 }
248
249 return scripts;
250 }
251
NodeTransformAddHotspots()252 NodeTransformAddHotspots::NodeTransformAddHotspots() : _zipBitIndex(-1) {
253 }
254
read(Common::SeekableReadStream * file)255 void NodeTransformAddHotspots::read(Common::SeekableReadStream *file) {
256 _zipBitIndex++;
257 _scripts = ScriptData::readCondScripts(*file);
258 _hotspots = ScriptData::readHotspots(*file);
259 }
260
apply(NodePtr & node)261 void NodeTransformAddHotspots::apply(NodePtr &node) {
262 node->zipBitIndex = _zipBitIndex;
263 node->scripts.push_back(_scripts);
264 node->hotspots.push_back(_hotspots);
265 }
266
read(Common::SeekableReadStream * file)267 void NodeTransformAddSoundScripts::read(Common::SeekableReadStream *file) {
268 _scripts = ScriptData::readCondScripts(*file);
269 }
270
apply(NodePtr & node)271 void NodeTransformAddSoundScripts::apply(NodePtr &node) {
272 node->soundScripts.push_back(_scripts);
273 }
274
read(Common::SeekableReadStream * file)275 void NodeTransformAddBackgroundSoundScripts::read(Common::SeekableReadStream *file) {
276 _scripts = ScriptData::readCondScripts(*file);
277 }
278
apply(NodePtr & node)279 void NodeTransformAddBackgroundSoundScripts::apply(NodePtr &node) {
280 node->backgroundSoundScripts.push_back(_scripts);
281 }
282
NodeWalker(NodeTransform * transform)283 NodeWalker::NodeWalker(NodeTransform *transform) : _transform(transform) {
284 }
285
read(Common::SeekableReadStream * file,Common::Array<NodePtr> & allNodes,bool createMissingSharedNodes)286 void NodeWalker::read(Common::SeekableReadStream *file, Common::Array<NodePtr> &allNodes, bool createMissingSharedNodes) {
287 while (!file->eos()) {
288 int16 id = file->readUint16LE();
289
290 // End of list
291 if (id == 0)
292 break;
293
294 if (id < -10)
295 error("Unimplemented node list command");
296
297 if (id > 0) {
298 // Normal node, find the node if existing
299 NodePtr node;
300 for (uint i = 0; i < allNodes.size(); i++)
301 if (allNodes[i]->id == id) {
302 node = allNodes[i];
303 break;
304 }
305
306 // Node not found, create a new one
307 if (!node) {
308 node = NodePtr(new NodeData());
309 node->id = id;
310 allNodes.push_back(node);
311 }
312
313 _transform->read(file);
314 _transform->apply(node);
315 } else {
316 // Several nodes sharing the same scripts
317 // Find the node ids the script applies to
318 Common::Array<int16> scriptNodeIds;
319
320 if (id == -10)
321 do {
322 id = file->readUint16LE();
323 if (id < 0) {
324 uint16 end = file->readUint16LE();
325 for (int i = -id; i <= end; i++)
326 scriptNodeIds.push_back(i);
327
328 } else if (id > 0) {
329 scriptNodeIds.push_back(id);
330 }
331 } while (id);
332 else
333 for (int i = 0; i < -id; i++) {
334 scriptNodeIds.push_back(file->readUint16LE());
335 }
336
337 // Load the script
338 _transform->read(file);
339
340 // Add the script to each matching node
341 for (uint i = 0; i < scriptNodeIds.size(); i++) {
342 NodePtr node;
343
344 // Find the current node if existing
345 for (uint j = 0; j < allNodes.size(); j++) {
346 if (allNodes[j]->id == scriptNodeIds[i]) {
347 node = allNodes[j];
348 break;
349 }
350 }
351
352 if (!node) {
353 if (createMissingSharedNodes) {
354 // Node not found, create a new one
355 node = NodePtr(new NodeData());
356 node->id = scriptNodeIds[i];
357 allNodes.push_back(node);
358 } else {
359 // Node not found, skip it
360 continue;
361 }
362 }
363
364 _transform->apply(node);
365 }
366 }
367 }
368 }
369
~NodeWalker()370 NodeWalker::~NodeWalker() {
371 delete _transform;
372 }
373
374 static const RoomData roomsXXXX[] = {
375 { 101, "XXXX" }
376 };
377
378 static const RoomData roomsINTR[] = {
379 { 201, "INTR" }
380 };
381
382 static const RoomData roomsTOHO[] = {
383 { 301, "TOHO" }
384 };
385
386 static const RoomData roomsTOHB[] = {
387 { 401, "TOHB" }
388 };
389
390 static const RoomData roomsLE[] = {
391 { 501, "LEIS" },
392 { 502, "LEOS" },
393 { 503, "LEET" },
394 { 504, "LELT" },
395 { 505, "LEMT" },
396 { 506, "LEOF" }
397 };
398
399 static const RoomData roomsLI[] = {
400 { 601, "LIDR" },
401 { 602, "LISW" },
402 { 603, "LIFO" },
403 { 604, "LISP" },
404 { 605, "LINE" }
405 };
406
407 static const RoomData roomsEN[] = {
408 { 701, "ENSI" },
409 { 703, "ENPP" },
410 { 704, "ENEM" },
411 { 705, "ENLC" },
412 { 706, "ENDD" },
413 { 707, "ENCH" },
414 { 708, "ENLI" }
415 };
416
417 static const RoomData roomsNA[] = {
418 { 801, "NACH" }
419 };
420
421 static const RoomData roomsMENU[] = {
422 { 901, "MENU" },
423 { 902, "JRNL" },
424 { 903, "DEMO" },
425 { 904, "ATIX" }
426 };
427
428 static const RoomData roomsMA[] = {
429 { 1001, "MACA" },
430 { 1002, "MAIS" },
431 { 1003, "MALL" },
432 { 1004, "MASS" },
433 { 1005, "MAWW" },
434 { 1006, "MATO" }
435 };
436
437 static const RoomData roomsLOGO[] = {
438 { 1101, "LOGO" }
439 };
440
441 const AgeData Database::_ages[] = {
442 { 1, 0, 1, roomsXXXX, 0 },
443 { 2, 1, 1, roomsINTR, 0 },
444 { 3, 2, 1, roomsTOHO, 0 },
445 { 4, 4, 1, roomsTOHB, 0 },
446 { 5, 2, 6, roomsLE, 1 },
447 { 6, 4, 5, roomsLI, 2 },
448 { 7, 3, 7, roomsEN, 3 },
449 { 8, 3, 1, roomsNA, 4 },
450 { 9, 0, 4, roomsMENU, 0 },
451 { 10, 1, 6, roomsMA, 5 },
452 { 11, 0, 1, roomsLOGO, 0 }
453 };
454
Database(const Common::Platform platform,const Common::Language language,const uint32 localizationType)455 Database::Database(const Common::Platform platform, const Common::Language language, const uint32 localizationType) :
456 _platform(platform),
457 _language(language),
458 _localizationType(localizationType) {
459
460 _datFile = SearchMan.createReadStreamForMember("myst3.dat");
461 if (!_datFile) {
462 error("Unable to find 'myst3.dat'");
463 }
464
465 uint magic = _datFile->readUint32LE();
466 if (magic != MKTAG('M', 'Y', 'S', 'T')) {
467 error("'myst3.dat' is invalid");
468 }
469
470 uint version = _datFile->readUint32LE();
471 if (version != kDatVersion) {
472 error("Incorrect 'myst3.dat' version. Expected '%d', found '%d'", kDatVersion, version);
473 }
474
475 bool isWindowMacVersion = _platform == Common::kPlatformWindows || _platform == Common::kPlatformMacintosh;
476 bool isXboxVersion = _platform == Common::kPlatformXbox;
477
478 readScriptIndex(_datFile, isWindowMacVersion); // Main scripts
479 readScriptIndex(_datFile, isWindowMacVersion && _localizationType == kLocMulti6); // Menu scripts 6 languages version
480 readScriptIndex(_datFile, isWindowMacVersion && _localizationType == kLocMulti2); // Menu scripts 2 languages CD version
481 readScriptIndex(_datFile, isWindowMacVersion && _localizationType == kLocMonolingual); // Menu scripts english CD version
482 readScriptIndex(_datFile, isXboxVersion); // Main scripts Xbox version
483 readScriptIndex(_datFile, isXboxVersion && _localizationType != kLocMonolingual); // Menu scripts PAL Xbox version
484 readScriptIndex(_datFile, isXboxVersion && _localizationType == kLocMonolingual); // Menu scripts NTSC Xbox version
485 readSoundNames(_datFile, isWindowMacVersion); // Sound names
486 readSoundNames(_datFile, isXboxVersion); // Sound names Xbox
487
488 _roomScriptsStartOffset = _datFile->pos();
489
490 Common::SeekableReadStream *initScriptStream = getRoomScriptStream("INIT", kScriptTypeNodeInit);
491 _nodeInitScript = ScriptData::readOpcodes(*initScriptStream);
492 delete initScriptStream;
493
494 Common::SeekableReadStream *cuesStream = getRoomScriptStream("INIT", kScriptTypeAmbientCue);
495 loadAmbientCues(cuesStream);
496 delete cuesStream;
497
498 preloadCommonRooms();
499 initializeZipBitIndexTable();
500
501 if (isWindowMacVersion && _localizationType == kLocMulti2) {
502 patchLanguageMenu();
503 }
504 }
505
~Database()506 Database::~Database() {
507 delete _datFile;
508 }
509
preloadCommonRooms()510 void Database::preloadCommonRooms() {
511 for (uint i = 0; i < ARRAYSIZE(_ages); i++) {
512 const AgeData &age = _ages[i];
513
514 for (uint j = 0; j < age.roomCount; j++) {
515 const RoomData &room = age.rooms[j];
516
517 if (isCommonRoom(room.id, age.id)) {
518 Common::Array<NodePtr> nodes = readRoomScripts(&room);
519 _roomNodesCache.setVal(RoomKey(room.id, age.id), nodes);
520 }
521 }
522 }
523 }
524
getRoomNodes(uint32 roomID,uint32 ageID) const525 Common::Array<NodePtr> Database::getRoomNodes(uint32 roomID, uint32 ageID) const {
526 Common::Array<NodePtr> nodes;
527
528 if (_roomNodesCache.contains(RoomKey(roomID, ageID))) {
529 nodes = _roomNodesCache.getVal(RoomKey(roomID, ageID));
530 } else {
531 const RoomData *data = findRoomData(roomID, ageID);
532 nodes = readRoomScripts(data);
533 }
534
535 return nodes;
536 }
537
listRoomNodes(uint32 roomID,uint32 ageID)538 Common::Array<uint16> Database::listRoomNodes(uint32 roomID, uint32 ageID) {
539 Common::Array<NodePtr> nodes;
540 Common::Array<uint16> list;
541
542 nodes = getRoomNodes(roomID, ageID);
543
544 for (uint i = 0; i < nodes.size(); i++) {
545 list.push_back(nodes[i]->id);
546 }
547
548 return list;
549 }
550
getNodeData(uint16 nodeID,uint32 roomID,uint32 ageID)551 NodePtr Database::getNodeData(uint16 nodeID, uint32 roomID, uint32 ageID) {
552 Common::Array<NodePtr> nodes = getRoomNodes(roomID, ageID);
553
554 for (uint i = 0; i < nodes.size(); i++) {
555 if (nodes[i]->id == nodeID)
556 return nodes[i];
557 }
558
559 return NodePtr();
560 }
561
initializeZipBitIndexTable()562 void Database::initializeZipBitIndexTable() {
563 int16 zipBit = 0;
564 for (uint i = 0; i < ARRAYSIZE(_ages); i++) {
565 for (uint j = 0; j < _ages[i].roomCount; j++) {
566 _roomZipBitIndex.setVal(_ages[i].rooms[j].id, zipBit);
567
568 // Add the highest zip-bit index for the current room
569 // to get the zip-bit index for the next room
570 int16 maxZipBitForRoom = 0;
571 Common::Array<NodePtr> nodes = readRoomScripts(&_ages[i].rooms[j]);
572 for (uint k = 0; k < nodes.size(); k++) {
573 maxZipBitForRoom = MAX(maxZipBitForRoom, nodes[k]->zipBitIndex);
574 }
575
576 zipBit += maxZipBitForRoom + 1;
577 }
578 }
579 }
580
getNodeZipBitIndex(uint16 nodeID,uint32 roomID,uint32 ageID)581 int32 Database::getNodeZipBitIndex(uint16 nodeID, uint32 roomID, uint32 ageID) {
582 if (!_roomZipBitIndex.contains(roomID)) {
583 error("Unable to find zip-bit index for room %d", roomID);
584 }
585
586 Common::Array<NodePtr> nodes = getRoomNodes(roomID, ageID);
587
588 for (uint i = 0; i < nodes.size(); i++) {
589 if (nodes[i]->id == nodeID) {
590 return _roomZipBitIndex[roomID] + nodes[i]->zipBitIndex;
591 }
592 }
593
594 error("Unable to find zip-bit index for node (%d, %d)", nodeID, roomID);
595 }
596
findRoomData(uint32 roomID,uint32 ageID) const597 const RoomData *Database::findRoomData(uint32 roomID, uint32 ageID) const {
598 for (uint i = 0; i < ARRAYSIZE(_ages); i++) {
599 if (_ages[i].id == ageID) {
600 for (uint j = 0; j < _ages[i].roomCount; j++) {
601 if (_ages[i].rooms[j].id == roomID) {
602 return &_ages[i].rooms[j];
603 }
604 }
605 }
606 }
607
608 error("No room with ID %d", roomID);
609 }
610
readRoomScripts(const RoomData * room) const611 Common::Array<NodePtr> Database::readRoomScripts(const RoomData *room) const {
612 Common::Array<NodePtr> nodes;
613
614 // Load the node scripts
615 Common::SeekableReadStream *scriptsStream = getRoomScriptStream(room->name, kScriptTypeNode);
616 if (scriptsStream) {
617 NodeWalker scriptWalker = NodeWalker(new NodeTransformAddHotspots());
618 scriptWalker.read(scriptsStream, nodes, true);
619
620 delete scriptsStream;
621 }
622
623 // Load the ambient sound scripts, if any
624 Common::SeekableReadStream *ambientSoundsStream = getRoomScriptStream(room->name, kScriptTypeAmbientSound);
625 if (ambientSoundsStream) {
626 NodeWalker scriptWalker = NodeWalker(new NodeTransformAddSoundScripts());
627 scriptWalker.read(ambientSoundsStream, nodes, false);
628
629 delete ambientSoundsStream;
630 }
631
632 Common::SeekableReadStream *backgroundSoundsStream = getRoomScriptStream(room->name, kScriptTypeBackgroundSound);
633 if (backgroundSoundsStream) {
634 NodeWalker scriptWalker = NodeWalker(new NodeTransformAddBackgroundSoundScripts());
635 scriptWalker.read(backgroundSoundsStream, nodes, false);
636
637 delete backgroundSoundsStream;
638 }
639
640 patchNodeScripts(room, nodes);
641
642 return nodes;
643 }
644
patchNodeScripts(const RoomData * room,Common::Array<NodePtr> & nodes) const645 void Database::patchNodeScripts(const RoomData *room, Common::Array<NodePtr> &nodes) const {
646 if (strcmp(room->name, "LEOF") == 0) {
647 // The room LEOF does not have a script to set default water effect
648 // parameters when entering a node. As a result, the pool of water
649 // mainly visible in LEOF 23 uses the last set water effect parameters.
650 // If the player comes from the top of the tower, the water effect is
651 // barely visible.
652 // As a workaround we insert default water effect settings in node
653 // 32765 which get applied for each node in the room.
654 // The new script disassembles as follow:
655
656 // node: LEOF 32765
657 // init 0 > c[v1 != 0] (true)
658 // op 33, waterEffectSetWave ( 100 100 )
659 // op 32, waterEffectSetAttenuation ( 360 )
660 // op 31, waterEffectSetSpeed ( 12 )
661
662 Opcode waterEffectSetWave;
663 waterEffectSetWave.op = 33;
664 waterEffectSetWave.args.push_back(100);
665 waterEffectSetWave.args.push_back(100);
666
667 Opcode waterEffectSetAttenuation;
668 waterEffectSetAttenuation.op = 32;
669 waterEffectSetAttenuation.args.push_back(360);
670
671 Opcode waterEffectSetSpeed;
672 waterEffectSetSpeed.op = 31;
673 waterEffectSetSpeed.args.push_back(12);
674
675 CondScript waterEffectScript;
676 waterEffectScript.condition = 1;
677 waterEffectScript.script.push_back(waterEffectSetWave);
678 waterEffectScript.script.push_back(waterEffectSetAttenuation);
679 waterEffectScript.script.push_back(waterEffectSetSpeed);
680
681 NodePtr node32765 = NodePtr(new NodeData());
682 node32765->id = 32765;
683 node32765->scripts.push_back(waterEffectScript);
684
685 nodes.push_back(node32765);
686 }
687 }
688
isCommonRoom(uint32 roomID,uint32 ageID) const689 bool Database::isCommonRoom(uint32 roomID, uint32 ageID) const {
690 return roomID == 101 || roomID == 901 || roomID == 902;
691 }
692
cacheRoom(uint32 roomID,uint32 ageID)693 void Database::cacheRoom(uint32 roomID, uint32 ageID) {
694 if (_roomNodesCache.contains(RoomKey(roomID, ageID))) {
695 return;
696 }
697
698 // Remove old rooms from cache and add the new one
699 for (NodesCache::iterator it = _roomNodesCache.begin(); it != _roomNodesCache.end(); it++) {
700 if (!isCommonRoom(it->_key.roomID, it->_key.ageID)) {
701 _roomNodesCache.erase(it);
702 }
703 }
704
705 const RoomData *currentRoomData = findRoomData(roomID, ageID);
706
707 if (!currentRoomData)
708 return;
709
710 _roomNodesCache.setVal(RoomKey(roomID, ageID), readRoomScripts(currentRoomData));
711 }
712
getRoomName(uint32 roomID,uint32 ageID) const713 Common::String Database::getRoomName(uint32 roomID, uint32 ageID) const {
714 const RoomData *data = findRoomData(roomID, ageID);
715 return data->name;
716 }
717
getRoomKey(const char * name)718 RoomKey Database::getRoomKey(const char *name) {
719 for (uint i = 0; i < ARRAYSIZE(_ages); i++)
720 for (uint j = 0; j < _ages[i].roomCount; j++) {
721 if (scumm_stricmp(_ages[i].rooms[j].name, name) == 0) {
722 return RoomKey(_ages[i].rooms[j].id, _ages[i].id);
723 }
724 }
725
726 return RoomKey(0, 0);
727 }
728
getAgeLabelId(uint32 ageID)729 uint32 Database::getAgeLabelId(uint32 ageID) {
730 for (uint i = 0; i < ARRAYSIZE(_ages); i++)
731 if (_ages[i].id == ageID)
732 return _ages[i].labelId;
733
734 return 0;
735 }
736
getSoundName(uint32 id)737 Common::String Database::getSoundName(uint32 id) {
738 const Common::String result = _soundNames.getVal(id, "");
739 if (result.empty())
740 error("Unable to find a sound with id %d", id);
741
742 return result;
743 }
744
loadAmbientCues(Common::ReadStream * s)745 void Database::loadAmbientCues(Common::ReadStream *s) {
746 _ambientCues.clear();
747
748 while (!s->eos()) {
749 uint16 id = s->readUint16LE();
750
751 if (!id)
752 break;
753
754 AmbientCue cue;
755 cue.id = id;
756 cue.minFrames = s->readUint16LE();
757 cue.maxFrames = s->readUint16LE();
758
759 while (1) {
760 uint16 track = s->readUint16LE();
761
762 if (!track)
763 break;
764
765 cue.tracks.push_back(track);
766 }
767
768 _ambientCues[id] = cue;
769 }
770 }
771
getAmbientCue(uint16 id)772 const AmbientCue &Database::getAmbientCue(uint16 id) {
773 if (!_ambientCues.contains(id))
774 error("Unable to find an ambient cue with id %d", id);
775
776 return _ambientCues.getVal(id);
777 }
778
readScriptIndex(Common::SeekableReadStream * stream,bool load)779 void Database::readScriptIndex(Common::SeekableReadStream *stream, bool load) {
780 uint count = stream->readUint32LE();
781 for (uint i = 0; i < count; i++) {
782
783 RoomScripts roomScripts;
784
785 char roomName[5];
786 stream->read(roomName, sizeof(roomName));
787 roomName[4] = '\0';
788
789 roomScripts.room = Common::String(roomName);
790 roomScripts.type = (ScriptType) stream->readUint32LE();
791 roomScripts.offset = stream->readUint32LE();
792 roomScripts.size = stream->readUint32LE();
793
794 if (load) {
795 _roomScriptsIndex.push_back(roomScripts);
796 }
797 }
798 }
799
readSoundNames(Common::SeekableReadStream * stream,bool load)800 void Database::readSoundNames(Common::SeekableReadStream *stream, bool load) {
801 uint count = stream->readUint32LE();
802 for (uint i = 0; i < count; i++) {
803
804 uint id = stream->readUint32LE();
805
806 char soundName[32];
807 stream->read(soundName, sizeof(soundName));
808 soundName[31] = '\0';
809
810 if (load) {
811 _soundNames[id] = Common::String(soundName);
812 }
813 }
814 }
815
getRoomScriptStream(const char * room,ScriptType scriptType) const816 Common::SeekableReadStream *Database::getRoomScriptStream(const char *room, ScriptType scriptType) const {
817 for (uint i = 0; i < _roomScriptsIndex.size(); i++) {
818 if (_roomScriptsIndex[i].room.equalsIgnoreCase(room)
819 && _roomScriptsIndex[i].type == scriptType) {
820 uint32 startOffset = _roomScriptsStartOffset + _roomScriptsIndex[i].offset;
821 uint32 size = _roomScriptsIndex[i].size;
822
823 return new Common::SeekableSubReadStream(_datFile, startOffset, startOffset + size);
824 }
825 }
826
827 return nullptr;
828 }
829
getGameLanguageCode() const830 int16 Database::getGameLanguageCode() const {
831 // The monolingual versions of the game always use 0 as the language code
832 if (_localizationType == kLocMonolingual) {
833 return kEnglish;
834 }
835
836 switch (_language) {
837 case Common::FR_FRA:
838 return kFrench;
839 case Common::DE_DEU:
840 return kGerman;
841 case Common::IT_ITA:
842 return kItalian;
843 case Common::ES_ESP:
844 return kSpanish;
845 case Common::EN_ANY:
846 return kEnglish;
847 default:
848 return kOther;
849 }
850 }
851
patchLanguageMenu()852 void Database::patchLanguageMenu() {
853 // The menu scripts in 'myst3.dat" for the non English CD versions come from the French version
854 // The scripts for the other languages only differ by the value set for AudioLanguage variable
855 // when the language selection is not English.
856 // This function patches the language selection script to set the appropriate value based
857 // on the detected game langage.
858
859 // Script disassembly:
860 // hotspot 5 > c[v1 != 0] (true)
861 // rect > pitch: 373 heading: 114 width: 209 height: 28
862 // op 206, soundPlayVolume ( 795 5 )
863 // op 53, varSetValue ( vLanguageAudio 2 ) // <= The second argument of this opcode is patched
864 // op 194, runPuzzle1 ( 18 )
865 // op 194, runPuzzle1 ( 19 )
866
867 NodePtr languageMenu = getNodeData(530, 901, 9);
868 languageMenu->hotspots[5].script[1].args[1] = getGameLanguageCode();
869 }
870
871 } // End of namespace Myst3
872