2009-02-17 15:02:16 +00:00
|
|
|
/* ScummVM - Graphic Adventure Engine
|
|
|
|
*
|
|
|
|
* ScummVM is the legal property of its developers, whose names
|
|
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
|
|
* file distributed with this source distribution.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
2014-02-18 02:34:24 +01:00
|
|
|
*
|
2009-02-17 15:02:16 +00:00
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
2014-02-18 02:34:24 +01:00
|
|
|
*
|
2009-02-17 15:02:16 +00:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
*/
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2010-05-18 04:17:58 +00:00
|
|
|
|
2009-02-15 08:34:13 +00:00
|
|
|
#include "sci/engine/message.h"
|
2009-10-10 02:16:23 +00:00
|
|
|
#include "sci/engine/kernel.h"
|
|
|
|
#include "sci/engine/seg_manager.h"
|
2018-09-30 19:59:36 -07:00
|
|
|
#include "sci/engine/state.h"
|
2010-05-18 04:17:58 +00:00
|
|
|
#include "sci/util.h"
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2009-02-21 10:23:36 +00:00
|
|
|
namespace Sci {
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
struct MessageRecord {
|
|
|
|
MessageTuple tuple;
|
|
|
|
MessageTuple refTuple;
|
|
|
|
const char *string;
|
2017-01-05 21:05:22 -06:00
|
|
|
uint32 length;
|
2009-10-10 02:16:23 +00:00
|
|
|
byte talker;
|
|
|
|
};
|
2009-05-13 19:03:12 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
class MessageReader {
|
|
|
|
public:
|
|
|
|
bool init() {
|
2016-12-31 20:39:57 -06:00
|
|
|
if (_headerSize > _data.size())
|
2009-10-10 02:16:23 +00:00
|
|
|
return false;
|
2009-05-13 19:03:12 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
// Read message count from last word in header
|
2016-12-31 20:39:57 -06:00
|
|
|
_messageCount = _data.getUint16SEAt(_headerSize - 2);
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2016-12-31 20:39:57 -06:00
|
|
|
if (_messageCount * _recordSize + _headerSize > _data.size())
|
2009-10-10 02:16:23 +00:00
|
|
|
return false;
|
2009-05-13 19:03:12 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
return true;
|
2009-05-12 11:28:15 +00:00
|
|
|
}
|
2009-05-13 19:03:12 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
virtual bool findRecord(const MessageTuple &tuple, MessageRecord &record) = 0;
|
2009-05-12 11:28:15 +00:00
|
|
|
|
2010-03-22 20:28:08 +00:00
|
|
|
virtual ~MessageReader() { }
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
protected:
|
2016-12-31 20:39:57 -06:00
|
|
|
MessageReader(const SciSpan<const byte> &data, uint headerSize, uint recordSize)
|
|
|
|
: _data(data), _headerSize(headerSize), _recordSize(recordSize), _messageCount(0) { }
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2016-12-31 20:39:57 -06:00
|
|
|
const SciSpan<const byte> _data;
|
2009-10-10 02:16:23 +00:00
|
|
|
const uint _headerSize;
|
|
|
|
const uint _recordSize;
|
|
|
|
uint _messageCount;
|
|
|
|
};
|
2009-05-12 11:28:15 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
class MessageReaderV2 : public MessageReader {
|
|
|
|
public:
|
2016-12-31 20:39:57 -06:00
|
|
|
MessageReaderV2(const SciSpan<const byte> &data) : MessageReader(data, 6, 4) { }
|
2009-05-13 19:03:12 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
bool findRecord(const MessageTuple &tuple, MessageRecord &record) {
|
2016-12-31 20:39:57 -06:00
|
|
|
SciSpan<const byte> recordPtr = _data.subspan(_headerSize);
|
2009-05-12 11:28:15 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
for (uint i = 0; i < _messageCount; i++) {
|
|
|
|
if ((recordPtr[0] == tuple.noun) && (recordPtr[1] == tuple.verb)) {
|
|
|
|
record.tuple = tuple;
|
|
|
|
record.refTuple = MessageTuple();
|
|
|
|
record.talker = 0;
|
2016-12-31 20:39:57 -06:00
|
|
|
const uint16 stringOffset = recordPtr.getUint16LEAt(2);
|
|
|
|
const uint32 maxSize = _data.size() - stringOffset;
|
|
|
|
record.string = (const char *)_data.getUnsafeDataAt(stringOffset, maxSize);
|
2017-01-05 21:05:22 -06:00
|
|
|
record.length = Common::strnlen(record.string, maxSize);
|
|
|
|
if (record.length == maxSize) {
|
2017-03-30 14:02:27 -05:00
|
|
|
warning("Message %s from %s appears truncated at %d", tuple.toString().c_str(), _data.name().c_str(), recordPtr - _data);
|
2017-01-05 21:05:22 -06:00
|
|
|
}
|
2009-10-10 02:16:23 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
recordPtr += _recordSize;
|
|
|
|
}
|
2009-05-12 11:28:15 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class MessageReaderV3 : public MessageReader {
|
|
|
|
public:
|
2016-12-31 20:39:57 -06:00
|
|
|
MessageReaderV3(const SciSpan<const byte> &data) : MessageReader(data, 8, 10) { }
|
2009-10-10 02:16:23 +00:00
|
|
|
|
|
|
|
bool findRecord(const MessageTuple &tuple, MessageRecord &record) {
|
2016-12-31 20:39:57 -06:00
|
|
|
SciSpan<const byte> recordPtr = _data.subspan(_headerSize);
|
2009-10-10 02:16:23 +00:00
|
|
|
for (uint i = 0; i < _messageCount; i++) {
|
|
|
|
if ((recordPtr[0] == tuple.noun) && (recordPtr[1] == tuple.verb)
|
|
|
|
&& (recordPtr[2] == tuple.cond) && (recordPtr[3] == tuple.seq)) {
|
|
|
|
record.tuple = tuple;
|
|
|
|
record.refTuple = MessageTuple();
|
|
|
|
record.talker = recordPtr[4];
|
2016-12-31 20:39:57 -06:00
|
|
|
const uint16 stringOffset = recordPtr.getUint16LEAt(5);
|
|
|
|
const uint32 maxSize = _data.size() - stringOffset;
|
|
|
|
record.string = (const char *)_data.getUnsafeDataAt(stringOffset, maxSize);
|
2017-01-05 21:05:22 -06:00
|
|
|
record.length = Common::strnlen(record.string, maxSize);
|
|
|
|
if (record.length == maxSize) {
|
2017-03-30 14:02:27 -05:00
|
|
|
warning("Message %s from %s appears truncated at %d", tuple.toString().c_str(), _data.name().c_str(), recordPtr - _data);
|
2017-01-05 21:05:22 -06:00
|
|
|
}
|
2009-10-10 02:16:23 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
recordPtr += _recordSize;
|
|
|
|
}
|
2009-05-10 22:25:43 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class MessageReaderV4 : public MessageReader {
|
|
|
|
public:
|
2016-12-31 20:39:57 -06:00
|
|
|
MessageReaderV4(const SciSpan<const byte> &data) : MessageReader(data, 10, 11) { }
|
2009-10-10 02:16:23 +00:00
|
|
|
|
|
|
|
bool findRecord(const MessageTuple &tuple, MessageRecord &record) {
|
2016-12-31 20:39:57 -06:00
|
|
|
SciSpan<const byte> recordPtr = _data.subspan(_headerSize);
|
2009-10-10 02:16:23 +00:00
|
|
|
for (uint i = 0; i < _messageCount; i++) {
|
|
|
|
if ((recordPtr[0] == tuple.noun) && (recordPtr[1] == tuple.verb)
|
|
|
|
&& (recordPtr[2] == tuple.cond) && (recordPtr[3] == tuple.seq)) {
|
|
|
|
record.tuple = tuple;
|
|
|
|
record.refTuple = MessageTuple(recordPtr[7], recordPtr[8], recordPtr[9]);
|
|
|
|
record.talker = recordPtr[4];
|
2016-12-31 20:39:57 -06:00
|
|
|
const uint16 stringOffset = recordPtr.getUint16SEAt(5);
|
|
|
|
const uint32 maxSize = _data.size() - stringOffset;
|
|
|
|
record.string = (const char *)_data.getUnsafeDataAt(stringOffset, maxSize);
|
2017-01-05 21:05:22 -06:00
|
|
|
record.length = Common::strnlen(record.string, maxSize);
|
|
|
|
if (record.length == maxSize) {
|
2017-03-30 14:02:27 -05:00
|
|
|
warning("Message %s from %s appears truncated at %d", tuple.toString().c_str(), _data.name().c_str(), recordPtr - _data);
|
2017-01-05 21:05:22 -06:00
|
|
|
}
|
2009-10-10 02:16:23 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
recordPtr += _recordSize;
|
|
|
|
}
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
return false;
|
2009-05-12 11:28:15 +00:00
|
|
|
}
|
2009-10-10 02:16:23 +00:00
|
|
|
};
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2017-09-24 22:27:02 -05:00
|
|
|
#ifdef ENABLE_SCI32_MAC
|
2011-02-03 05:10:24 +00:00
|
|
|
// SCI32 Mac decided to add an extra byte (currently unknown in meaning) between
|
|
|
|
// the talker and the string...
|
|
|
|
class MessageReaderV4_MacSCI32 : public MessageReader {
|
|
|
|
public:
|
2016-12-31 20:39:57 -06:00
|
|
|
MessageReaderV4_MacSCI32(const SciSpan<const byte> &data) : MessageReader(data, 10, 12) { }
|
2011-02-03 05:10:24 +00:00
|
|
|
|
|
|
|
bool findRecord(const MessageTuple &tuple, MessageRecord &record) {
|
2016-12-31 20:39:57 -06:00
|
|
|
SciSpan<const byte> recordPtr = _data.subspan(_headerSize);
|
2011-02-03 05:10:24 +00:00
|
|
|
for (uint i = 0; i < _messageCount; i++) {
|
|
|
|
if ((recordPtr[0] == tuple.noun) && (recordPtr[1] == tuple.verb)
|
|
|
|
&& (recordPtr[2] == tuple.cond) && (recordPtr[3] == tuple.seq)) {
|
|
|
|
record.tuple = tuple;
|
|
|
|
record.refTuple = MessageTuple(recordPtr[8], recordPtr[9], recordPtr[10]);
|
|
|
|
record.talker = recordPtr[4];
|
2016-12-31 20:39:57 -06:00
|
|
|
const uint16 stringOffset = recordPtr.getUint16BEAt(6);
|
|
|
|
const uint32 maxSize = _data.size() - stringOffset;
|
|
|
|
record.string = (const char *)_data.getUnsafeDataAt(stringOffset, maxSize);
|
2017-01-05 21:05:22 -06:00
|
|
|
record.length = Common::strnlen(record.string, maxSize);
|
|
|
|
if (record.length == maxSize) {
|
2017-03-30 14:02:27 -05:00
|
|
|
warning("Message %s from %s appears truncated at %d", tuple.toString().c_str(), _data.name().c_str(), recordPtr - _data);
|
2017-01-05 21:05:22 -06:00
|
|
|
}
|
2011-02-03 05:10:24 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
recordPtr += _recordSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
bool MessageState::getRecord(CursorStack &stack, bool recurse, MessageRecord &record) {
|
2017-01-05 21:05:22 -06:00
|
|
|
Resource *res = g_sci->getResMan()->findResource(ResourceId(kResourceTypeMessage, stack.getModule()), false);
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
if (!res) {
|
|
|
|
warning("Failed to open message resource %d", stack.getModule());
|
2009-10-10 12:13:34 +00:00
|
|
|
return false;
|
2009-10-10 02:16:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MessageReader *reader;
|
2016-12-31 20:39:57 -06:00
|
|
|
int version = res->getUint32SEAt(0) / 1000;
|
2009-10-10 02:16:23 +00:00
|
|
|
|
|
|
|
switch (version) {
|
|
|
|
case 2:
|
2016-12-31 20:39:57 -06:00
|
|
|
reader = new MessageReaderV2(*res);
|
2009-10-10 02:16:23 +00:00
|
|
|
break;
|
|
|
|
case 3:
|
2016-12-31 20:39:57 -06:00
|
|
|
reader = new MessageReaderV3(*res);
|
2009-10-10 02:16:23 +00:00
|
|
|
break;
|
|
|
|
case 4:
|
2009-12-21 15:22:42 +00:00
|
|
|
#ifdef ENABLE_SCI32
|
2009-12-23 05:10:16 +00:00
|
|
|
case 5: // v5 seems to be compatible with v4
|
2017-09-24 22:27:02 -05:00
|
|
|
#endif
|
|
|
|
#ifdef ENABLE_SCI32_MAC
|
2011-02-03 05:10:24 +00:00
|
|
|
// SCI32 Mac is different than SCI32 DOS/Win here
|
2015-12-29 01:44:11 +01:00
|
|
|
if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1_EARLY)
|
2016-12-31 20:39:57 -06:00
|
|
|
reader = new MessageReaderV4_MacSCI32(*res);
|
2011-02-03 05:10:24 +00:00
|
|
|
else
|
2009-12-21 15:22:42 +00:00
|
|
|
#endif
|
2016-12-31 20:39:57 -06:00
|
|
|
reader = new MessageReaderV4(*res);
|
2009-12-23 05:10:16 +00:00
|
|
|
break;
|
2009-10-10 02:16:23 +00:00
|
|
|
default:
|
2010-06-17 23:45:38 +00:00
|
|
|
error("Message: unsupported resource version %d", version);
|
2009-10-10 12:13:34 +00:00
|
|
|
return false;
|
2009-10-10 02:16:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!reader->init()) {
|
2010-08-02 22:35:29 +00:00
|
|
|
delete reader;
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
warning("Message: failed to read resource header");
|
2009-10-10 12:13:34 +00:00
|
|
|
return false;
|
2009-10-10 02:16:23 +00:00
|
|
|
}
|
2009-05-13 19:03:12 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
while (1) {
|
|
|
|
MessageTuple &t = stack.top();
|
2009-05-12 11:28:15 +00:00
|
|
|
|
2013-02-17 20:44:31 +02:00
|
|
|
// Fix known incorrect message tuples
|
2018-12-02 23:58:44 +02:00
|
|
|
// TODO: Add a more generic mechanism, like the one we have for
|
|
|
|
// script workarounds, for cases with incorrect sync resources,
|
|
|
|
// like the ones below.
|
2013-02-17 20:44:31 +02:00
|
|
|
if (g_sci->getGameId() == GID_QFG1VGA && stack.getModule() == 322 &&
|
|
|
|
t.noun == 14 && t.verb == 1 && t.cond == 19 && t.seq == 1) {
|
|
|
|
// Talking to Kaspar the shopkeeper - bug #3604944
|
|
|
|
t.verb = 2;
|
|
|
|
}
|
|
|
|
|
2013-03-30 13:58:53 +02:00
|
|
|
if (g_sci->getGameId() == GID_PQ1 && stack.getModule() == 38 &&
|
|
|
|
t.noun == 10 && t.verb == 4 && t.cond == 8 && t.seq == 1) {
|
|
|
|
// Using the hand icon on Keith in the Blue Room - bug #3605654
|
|
|
|
t.cond = 9;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (g_sci->getGameId() == GID_PQ1 && stack.getModule() == 38 &&
|
|
|
|
t.noun == 10 && t.verb == 1 && t.cond == 0 && t.seq == 1) {
|
|
|
|
// Using the eye icon on Keith in the Blue Room - bug #3605654
|
|
|
|
t.cond = 13;
|
|
|
|
}
|
|
|
|
|
2018-12-24 00:31:19 -05:00
|
|
|
if (g_sci->getGameId() == GID_QFG4 && stack.getModule() == 16 &&
|
|
|
|
t.noun == 49 && t.verb == 1 && t.cond == 0 && t.seq == 2) {
|
|
|
|
// Examining the statue inventory item from the monastery - bug #10770
|
|
|
|
// The description says "squid-like monster", yet the icon is
|
|
|
|
// clearly an insect. It turned Chief into "an enormous beetle". We
|
|
|
|
// change the phrase to "monstrous insect".
|
|
|
|
//
|
|
|
|
// Note: The German string contains accented characters.
|
|
|
|
// 0x84 "a with diaeresis"
|
|
|
|
// 0x94 "o with diaeresis"
|
|
|
|
//
|
|
|
|
// Values were pulled from SCI Companion's raw message data. They
|
|
|
|
// are escaped that way here, as encoded bytes.
|
|
|
|
record.tuple = t;
|
|
|
|
record.refTuple = MessageTuple();
|
|
|
|
record.talker = 99;
|
|
|
|
if (g_sci->getSciLanguage() == K_LANG_GERMAN) {
|
|
|
|
record.string = "Die groteske Skulptur eines schrecklichen, monstr\x94sen insekts ist sorgf\x84ltig in die Einkaufstasche eingewickelt.";
|
|
|
|
record.length = 112;
|
|
|
|
} else {
|
|
|
|
record.string = "Carefully wrapped in a shopping bag is the grotesque sculpture of a horrible, monstrous insect.";
|
|
|
|
record.length = 95;
|
|
|
|
}
|
|
|
|
delete reader;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-08-28 02:04:46 +03:00
|
|
|
if (g_sci->getGameId() == GID_QFG4 && stack.getModule() == 579 &&
|
|
|
|
t.noun == 0 && t.verb == 0 && t.cond == 0 && t.seq == 1) {
|
|
|
|
// Talking with the Leshy and telling him about "bush in goo" - bug #10137
|
|
|
|
t.verb = 1;
|
|
|
|
}
|
|
|
|
|
2018-12-24 09:30:44 -05:00
|
|
|
if (g_sci->getGameId() == GID_QFG4 && g_sci->isCD() && stack.getModule() == 520 &&
|
|
|
|
t.noun == 2 && t.verb == 59 && t.cond == 0) {
|
|
|
|
// The CD edition mangled the Rusalka flowers dialogue. - bug #10849
|
|
|
|
// In the floppy edition, there are 3 lines, the first from
|
|
|
|
// the narrator, then two from Rusalka. The CD edition omits
|
|
|
|
// narration and only has the 3rd text, with the 2nd audio! The
|
|
|
|
// 3rd audio is orphaned but available.
|
|
|
|
//
|
|
|
|
// We only restore Rusalka's lines, providing the correct text
|
|
|
|
// for seq:1 to match the audio. We respond to seq:2 requests
|
|
|
|
// with Rusalka's last text. The orphaned audio (seq:3) has its
|
|
|
|
// tuple adjusted to seq:2 in resource_audio.cpp.
|
|
|
|
if (t.seq == 1) {
|
|
|
|
record.tuple = t;
|
|
|
|
record.refTuple = MessageTuple();
|
|
|
|
record.talker = 28;
|
|
|
|
record.string = "Thank you for the beautiful flowers. No one has been so nice to me since I can remember.";
|
|
|
|
record.length = 89;
|
|
|
|
delete reader;
|
|
|
|
return true;
|
|
|
|
} else if (t.seq == 2) {
|
|
|
|
// The CD edition ships with this text at seq:1.
|
|
|
|
// Look it up instead of hardcoding.
|
|
|
|
t.seq = 1;
|
|
|
|
if (!reader->findRecord(t, record)) {
|
|
|
|
delete reader;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
t.seq = 2; // Prevent an endless 2=1 -> 2=1 -> 2=1... loop.
|
|
|
|
record.tuple.seq = 2; // Make the record seq:2 to get the seq:2 audio.
|
|
|
|
record.refTuple = MessageTuple();
|
|
|
|
delete reader;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-30 19:59:36 -07:00
|
|
|
if (g_sci->getGameId() == GID_LAURABOW2 && !g_sci->isCD() && stack.getModule() == 1885 &&
|
|
|
|
t.noun == 1 && t.verb == 6 && t.cond == 16 && t.seq == 4 &&
|
|
|
|
(g_sci->getEngineState()->currentRoomNumber() == 350 ||
|
|
|
|
g_sci->getEngineState()->currentRoomNumber() == 360 ||
|
|
|
|
g_sci->getEngineState()->currentRoomNumber() == 370)) {
|
|
|
|
// Asking Yvette about Tut in act 2 party - bug #10723
|
|
|
|
// Skip the last two lines of dialogue about a murder that hasn't occurred yet.
|
|
|
|
// Sierra fixed this in cd version by creating a copy of this message without those lines.
|
|
|
|
// Room-specific as the message is used again later where it should display in full.
|
|
|
|
t.seq += 2;
|
|
|
|
}
|
|
|
|
|
2013-02-17 20:45:16 +02:00
|
|
|
// Fill in known missing message tuples
|
|
|
|
if (g_sci->getGameId() == GID_SQ4 && stack.getModule() == 16 &&
|
|
|
|
t.noun == 7 && t.verb == 0 && t.cond == 3 && t.seq == 1) {
|
|
|
|
// This fixes the error message shown when speech and subtitles are
|
|
|
|
// enabled simultaneously in SQ4 - the (very) long dialog when Roger
|
|
|
|
// is talking with the aliens is missing - bug #3538416.
|
|
|
|
record.tuple = t;
|
|
|
|
record.refTuple = MessageTuple();
|
|
|
|
record.talker = 7; // Roger
|
|
|
|
// The missing text is just too big to fit in one speech bubble, and
|
|
|
|
// if it's added here manually and drawn on screen, it's painted over
|
|
|
|
// the entrance in the back where the Sequel Police enters, so it
|
|
|
|
// looks very ugly. Perhaps this is why this particular text is missing,
|
|
|
|
// as the text shown in this screen is very short (one-liners).
|
|
|
|
// Just output an empty string here instead of showing an error.
|
|
|
|
record.string = "";
|
2017-01-05 21:05:22 -06:00
|
|
|
record.length = 0;
|
2013-04-15 21:40:11 +02:00
|
|
|
delete reader;
|
2013-02-17 20:45:16 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-05-20 20:37:59 -07:00
|
|
|
// FPFP CD has several message sequences where audio and text were left
|
|
|
|
// out of sync. This is probably because this version didn't formally
|
|
|
|
// support text mode. Most of these texts just say "Dummy Msg". All the
|
|
|
|
// real text is there but it's either concatenated or in a different
|
|
|
|
// tuple. We extract and return the correct text. Fixes #10964
|
|
|
|
if (g_sci->getGameId() == GID_FREDDYPHARKAS && g_sci->isCD() &&
|
|
|
|
g_sci->getLanguage() == Common::EN_ANY && !g_sci->isDemo()) {
|
|
|
|
byte textSeq = 0;
|
|
|
|
uint32 substringIndex = 0;
|
|
|
|
uint32 substringLength = 0;
|
|
|
|
|
|
|
|
// lever brothers' introduction
|
|
|
|
if (stack.getModule() == 220 && t.noun == 24 && t.verb == 0 && t.cond == 0) {
|
|
|
|
switch (t.seq) {
|
|
|
|
case 1: textSeq = 1; substringIndex = 0; substringLength = 25; break;
|
|
|
|
case 2: textSeq = 1; substringIndex = 26; substringLength = 20; break;
|
|
|
|
case 3: textSeq = 1; substringIndex = 47; substringLength = 58; break;
|
|
|
|
case 4: textSeq = 1; substringIndex = 106; substringLength = 34; break;
|
|
|
|
case 5: textSeq = 1; substringIndex = 141; substringLength = 27; break;
|
|
|
|
case 6: textSeq = 1; substringIndex = 169; substringLength = 29; break;
|
|
|
|
case 7: textSeq = 1; substringIndex = 199; substringLength = 52; break;
|
|
|
|
case 8: textSeq = 1; substringIndex = 252; substringLength = 37; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// kenny's introduction
|
|
|
|
if (stack.getModule() == 220 && t.noun == 30 && t.verb == 0 && t.cond == 0) {
|
|
|
|
switch (t.seq) {
|
|
|
|
case 3: textSeq = 3; substringIndex = 0; substringLength = 14; break;
|
|
|
|
case 4: textSeq = 3; substringIndex = 15; substringLength = 245; break;
|
|
|
|
case 5: textSeq = 4; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// helen swatting flies
|
|
|
|
if (stack.getModule() == 660 && t.noun == 35 && t.verb == 0 && t.cond == 0) {
|
|
|
|
switch (t.seq) {
|
|
|
|
case 1: textSeq = 1; substringIndex = 0; substringLength = 42; break;
|
|
|
|
case 2: textSeq = 1; substringIndex = 43; substringLength = 93; break;
|
|
|
|
case 3: textSeq = 1; substringIndex = 137; substringLength = 72; break;
|
|
|
|
case 4: textSeq = 2; break;
|
|
|
|
case 5: textSeq = 1; substringIndex = 210; substringLength = 57; break;
|
|
|
|
case 6: textSeq = 3; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// find the original message record and the record that contains the
|
|
|
|
// correct text, then use the correct substring. the original must
|
|
|
|
// be used to preserve its correct talker and tuple values.
|
|
|
|
if (textSeq != 0 && reader->findRecord(t, record)) {
|
|
|
|
MessageTuple textTuple(t.noun, t.verb, t.cond, textSeq);
|
|
|
|
MessageRecord textRecord;
|
|
|
|
if (reader->findRecord(textTuple, textRecord)) {
|
|
|
|
uint32 textLength = (substringLength == 0) ? textRecord.length : substringLength;
|
|
|
|
if (substringIndex + textLength <= textRecord.length) {
|
|
|
|
record.string = textRecord.string + substringIndex;
|
|
|
|
record.length = textLength;
|
|
|
|
delete reader;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
if (!reader->findRecord(t, record)) {
|
|
|
|
// Tuple not found
|
|
|
|
if (recurse && (stack.size() > 1)) {
|
|
|
|
stack.pop();
|
|
|
|
continue;
|
|
|
|
}
|
2009-05-12 11:28:15 +00:00
|
|
|
|
2010-08-02 22:35:29 +00:00
|
|
|
delete reader;
|
2009-10-10 02:16:23 +00:00
|
|
|
return false;
|
|
|
|
}
|
2009-05-12 11:28:15 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
if (recurse) {
|
|
|
|
MessageTuple &ref = record.refTuple;
|
2009-08-31 12:32:05 +00:00
|
|
|
|
2009-10-11 03:40:11 +00:00
|
|
|
if (ref.noun || ref.verb || ref.cond) {
|
2009-10-10 02:16:23 +00:00
|
|
|
t.seq++;
|
2009-10-11 03:40:11 +00:00
|
|
|
stack.push(ref);
|
2009-10-10 02:16:23 +00:00
|
|
|
continue;
|
2009-05-12 11:28:15 +00:00
|
|
|
}
|
|
|
|
}
|
2009-10-10 02:16:23 +00:00
|
|
|
|
2010-08-02 22:35:29 +00:00
|
|
|
delete reader;
|
2009-10-10 02:16:23 +00:00
|
|
|
return true;
|
2009-05-12 11:28:15 +00:00
|
|
|
}
|
2009-10-10 02:16:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int MessageState::getMessage(int module, MessageTuple &t, reg_t buf) {
|
|
|
|
_cursorStack.init(module, t);
|
|
|
|
return nextMessage(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
int MessageState::nextMessage(reg_t buf) {
|
|
|
|
MessageRecord record;
|
|
|
|
|
|
|
|
if (!buf.isNull()) {
|
|
|
|
if (getRecord(_cursorStack, true, record)) {
|
2019-05-20 20:37:59 -07:00
|
|
|
outputString(buf, processString(record.string, record.length));
|
2009-10-10 02:16:23 +00:00
|
|
|
_lastReturned = record.tuple;
|
|
|
|
_lastReturnedModule = _cursorStack.getModule();
|
2009-10-11 03:40:11 +00:00
|
|
|
_cursorStack.top().seq++;
|
2009-10-10 02:16:23 +00:00
|
|
|
return record.talker;
|
|
|
|
} else {
|
2009-10-11 03:40:11 +00:00
|
|
|
MessageTuple &t = _cursorStack.top();
|
2017-01-05 21:05:22 -06:00
|
|
|
outputString(buf, Common::String::format("Msg %d: %s not found", _cursorStack.getModule(), t.toString().c_str()));
|
2009-10-10 02:16:23 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
CursorStack stack = _cursorStack;
|
2009-05-10 22:25:43 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
if (getRecord(stack, true, record))
|
|
|
|
return record.talker;
|
|
|
|
else
|
|
|
|
return 0;
|
2009-05-12 11:28:15 +00:00
|
|
|
}
|
2009-10-10 02:16:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int MessageState::messageSize(int module, MessageTuple &t) {
|
|
|
|
CursorStack stack;
|
|
|
|
MessageRecord record;
|
2009-05-10 22:25:43 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
stack.init(module, t);
|
|
|
|
if (getRecord(stack, true, record))
|
2017-01-05 21:05:22 -06:00
|
|
|
return record.length + 1;
|
2009-10-10 02:16:23 +00:00
|
|
|
else
|
|
|
|
return 0;
|
2009-02-15 06:10:59 +00:00
|
|
|
}
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
bool MessageState::messageRef(int module, const MessageTuple &t, MessageTuple &ref) {
|
|
|
|
CursorStack stack;
|
|
|
|
MessageRecord record;
|
|
|
|
|
|
|
|
stack.init(module, t);
|
|
|
|
if (getRecord(stack, false, record)) {
|
|
|
|
ref = record.refTuple;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2009-02-15 06:10:59 +00:00
|
|
|
}
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
void MessageState::pushCursorStack() {
|
|
|
|
_cursorStackStack.push(_cursorStack);
|
2009-05-13 19:03:12 +00:00
|
|
|
}
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
void MessageState::popCursorStack() {
|
|
|
|
if (!_cursorStackStack.empty())
|
|
|
|
_cursorStack = _cursorStackStack.pop();
|
|
|
|
else
|
2010-06-17 23:45:38 +00:00
|
|
|
error("Message: attempt to pop from empty stack");
|
2009-05-13 19:03:12 +00:00
|
|
|
}
|
|
|
|
|
2016-10-26 22:59:24 +02:00
|
|
|
int MessageState::hexDigitToWrongInt(char h) {
|
2016-10-25 19:48:55 -05:00
|
|
|
// Hex digits above 9 are incorrectly interpreted by SSCI as 11-16 instead
|
|
|
|
// of 10-15 because of a never-fixed typo
|
2009-10-10 02:16:23 +00:00
|
|
|
if ((h >= 'A') && (h <= 'F'))
|
2016-10-25 19:48:55 -05:00
|
|
|
return h - 'A' + 11;
|
2009-10-10 02:16:23 +00:00
|
|
|
|
|
|
|
if ((h >= 'a') && (h <= 'f'))
|
2016-10-25 19:48:55 -05:00
|
|
|
return h - 'a' + 11;
|
2009-06-04 14:29:20 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
if ((h >= '0') && (h <= '9'))
|
|
|
|
return h - '0';
|
|
|
|
|
|
|
|
return -1;
|
2009-05-13 19:03:12 +00:00
|
|
|
}
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
bool MessageState::stringHex(Common::String &outStr, const Common::String &inStr, uint &index) {
|
|
|
|
// Hex escape sequences of the form \nn, where n is a hex digit
|
|
|
|
if (inStr[index] != '\\')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Check for enough room for a hex escape sequence
|
|
|
|
if (index + 2 >= inStr.size())
|
|
|
|
return false;
|
|
|
|
|
2016-10-26 22:59:24 +02:00
|
|
|
int digit1 = hexDigitToWrongInt(inStr[index + 1]);
|
|
|
|
int digit2 = hexDigitToWrongInt(inStr[index + 2]);
|
2009-10-10 02:16:23 +00:00
|
|
|
|
|
|
|
// Check for hex
|
|
|
|
if ((digit1 == -1) || (digit2 == -1))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
outStr += digit1 * 16 + digit2;
|
|
|
|
index += 3;
|
|
|
|
|
|
|
|
return true;
|
2009-02-15 06:10:59 +00:00
|
|
|
}
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
bool MessageState::stringLit(Common::String &outStr, const Common::String &inStr, uint &index) {
|
|
|
|
// Literal escape sequences of the form \n
|
|
|
|
if (inStr[index] != '\\')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Check for enough room for a literal escape sequence
|
|
|
|
if (index + 1 >= inStr.size())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
outStr += inStr[index + 1];
|
|
|
|
index += 2;
|
|
|
|
|
|
|
|
return true;
|
2009-02-15 06:10:59 +00:00
|
|
|
}
|
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
bool MessageState::stringStage(Common::String &outstr, const Common::String &inStr, uint &index) {
|
2012-02-15 09:53:31 -06:00
|
|
|
// Stage directions of the form (n *), where n is anything but a digit or a lowercase character
|
2009-10-10 02:16:23 +00:00
|
|
|
if (inStr[index] != '(')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (uint i = index + 1; i < inStr.size(); i++) {
|
|
|
|
if (inStr[i] == ')') {
|
|
|
|
// Stage direction found, skip it
|
|
|
|
index = i + 1;
|
|
|
|
|
|
|
|
// Skip trailing white space
|
|
|
|
while ((index < inStr.size()) && ((inStr[index] == '\n') || (inStr[index] == '\r') || (inStr[index] == ' ')))
|
|
|
|
index++;
|
2009-08-31 12:32:05 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
return true;
|
2009-05-13 19:03:12 +00:00
|
|
|
}
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
// If we find a lowercase character or a digit, it's not a stage direction
|
2010-06-10 18:16:05 +00:00
|
|
|
// SCI32 seems to support having digits in stage directions
|
|
|
|
if (((inStr[i] >= 'a') && (inStr[i] <= 'z')) || ((inStr[i] >= '0') && (inStr[i] <= '9') && (getSciVersion() < SCI_VERSION_2)))
|
2009-10-10 02:16:23 +00:00
|
|
|
return false;
|
2009-05-12 11:28:15 +00:00
|
|
|
}
|
2009-05-08 16:21:51 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
// We ran into the end of the string without finding a closing bracket
|
|
|
|
return false;
|
|
|
|
}
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2019-05-20 20:37:59 -07:00
|
|
|
Common::String MessageState::processString(const char *s, uint32 maxLength) {
|
2009-10-10 02:16:23 +00:00
|
|
|
Common::String outStr;
|
|
|
|
Common::String inStr = Common::String(s);
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
uint index = 0;
|
|
|
|
|
2019-05-20 20:37:59 -07:00
|
|
|
while (index < inStr.size() && index < maxLength) {
|
2009-10-10 02:16:23 +00:00
|
|
|
// Check for hex escape sequence
|
|
|
|
if (stringHex(outStr, inStr, index))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Check for literal escape sequence
|
|
|
|
if (stringLit(outStr, inStr, index))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Check for stage direction
|
|
|
|
if (stringStage(outStr, inStr, index))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// None of the above, copy char
|
|
|
|
outStr += inStr[index++];
|
2009-08-31 12:32:05 +00:00
|
|
|
}
|
2009-08-10 18:37:47 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
return outStr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MessageState::outputString(reg_t buf, const Common::String &str) {
|
2009-12-30 16:00:56 +00:00
|
|
|
#ifdef ENABLE_SCI32
|
|
|
|
if (getSciVersion() >= SCI_VERSION_2) {
|
2016-09-04 16:30:47 -05:00
|
|
|
SciArray *sciString = _segMan->lookupArray(buf);
|
|
|
|
sciString->fromString(str);
|
2009-10-10 02:16:23 +00:00
|
|
|
} else {
|
2009-12-30 16:00:56 +00:00
|
|
|
#endif
|
|
|
|
SegmentRef buffer_r = _segMan->dereference(buf);
|
|
|
|
|
|
|
|
if ((unsigned)buffer_r.maxSize >= str.size() + 1) {
|
|
|
|
_segMan->strcpy(buf, str.c_str());
|
|
|
|
} else {
|
2010-07-29 07:58:48 +00:00
|
|
|
// LSL6 sets an exit text here, but the buffer size allocated
|
|
|
|
// is too small. Don't display a warning in this case, as we
|
|
|
|
// don't use the exit text anyway - bug report #3035533
|
|
|
|
if (g_sci->getGameId() == GID_LSL6 && str.hasPrefix("\r\n(c) 1993 Sierra On-Line, Inc")) {
|
|
|
|
// LSL6 buggy exit text, don't show warning
|
|
|
|
} else {
|
|
|
|
warning("Message: buffer %04x:%04x invalid or too small to hold the following text of %i bytes: '%s'", PRINT_REG(buf), str.size() + 1, str.c_str());
|
|
|
|
}
|
2009-10-10 02:16:23 +00:00
|
|
|
|
2009-12-30 16:00:56 +00:00
|
|
|
// Set buffer to empty string if possible
|
|
|
|
if (buffer_r.maxSize > 0)
|
|
|
|
_segMan->strcpy(buf, "");
|
|
|
|
}
|
|
|
|
#ifdef ENABLE_SCI32
|
2009-10-10 02:16:23 +00:00
|
|
|
}
|
2009-12-30 16:00:56 +00:00
|
|
|
#endif
|
2009-10-10 02:16:23 +00:00
|
|
|
}
|
2009-02-15 06:10:59 +00:00
|
|
|
|
2009-10-10 02:16:23 +00:00
|
|
|
void MessageState::lastQuery(int &module, MessageTuple &tuple) {
|
|
|
|
module = _lastReturnedModule;
|
|
|
|
tuple = _lastReturned;
|
2009-02-15 06:10:59 +00:00
|
|
|
}
|
2009-02-21 10:23:36 +00:00
|
|
|
|
|
|
|
} // End of namespace Sci
|