/* 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. * * 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. * * 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. * */ #include "lure/strings.h" #include "lure/disk.h" #include "lure/lure.h" #include "lure/res.h" #include "lure/room.h" #include "common/endian.h" namespace Lure { StringData *int_strings = NULL; StringData::StringData() { int_strings = this; Disk &disk = Disk::getReference(); for (uint8 ctr = 0; ctr < MAX_NUM_CHARS; ++ctr) _chars[ctr] = NULL; _numChars = 0; _names = Disk::getReference().getEntry(NAMES_RESOURCE_ID); _strings[0] = disk.getEntry(STRINGS_RESOURCE_ID); _strings[1] = disk.getEntry(STRINGS_2_RESOURCE_ID); _strings[2] = disk.getEntry(STRINGS_3_RESOURCE_ID); // Add in the list of bit sequences, and what characters they represent MemoryBlock *decoderList = disk.getEntry(STRING_DECODER_RESOURCE_ID); const char *p = (const char *) decoderList->data(); while ((byte)*p != 0xff) { char ascii = *p++; add(p, ascii); p += strlen(p) + 1; } delete decoderList; } StringData::~StringData() { int_strings = NULL; for (uint8 ctr = 0; ctr < MAX_NUM_CHARS; ++ctr) if (_chars[ctr]) delete _chars[ctr]; else break; delete _names; delete _strings[0]; delete _strings[1]; delete _strings[2]; } StringData &StringData::getReference() { return *int_strings; } void StringData::add(const char *sequence, char ascii) { uint32 value = 0; for (uint8 index = 0; index < strlen(sequence); ++index) { if (sequence[index] == '1') value |= (1 << index); else if (sequence[index] != '0') error("Invalid character in string bit-stream sequence"); } if (_numChars == MAX_NUM_CHARS) error("Max characters too lower in string decoder"); _chars[_numChars++] = new CharacterEntry(strlen(sequence), value, ascii); } byte StringData::readBit() { byte result = ((*_srcPos & _bitMask) != 0) ? 1 : 0; _bitMask >>= 1; if (_bitMask == 0) { _bitMask = 0x80; ++_srcPos; } return result; } bool StringData::initPosition(uint16 stringId) { uint16 roomNumber = Room::getReference().roomNumber(); if ((roomNumber >= 0x2A) && (stringId >= STRING_ID_RANGE) && (stringId < STRING_ID_UPPER)) stringId = 0x76; if ((roomNumber < 0x2A) && (stringId >= STRING_ID_UPPER)) stringId = 0x76; if (stringId < STRING_ID_RANGE) _stringTable = _strings[0]->data(); else if (stringId < STRING_ID_RANGE*2) { stringId -= STRING_ID_RANGE; _stringTable = _strings[1]->data(); } else { stringId -= STRING_ID_RANGE * 2; _stringTable = _strings[2]->data(); } _srcPos = _stringTable + 4; uint32 total = 0; int numLoops = stringId >> 5; for (int ctr = 0; ctr < numLoops; ++ctr) { total += READ_LE_UINT16(_srcPos); _srcPos += sizeof(uint16); } numLoops = stringId & 0x1f; if (numLoops!= 0) { byte *tempPtr = _stringTable + (stringId & 0xffe0) + READ_LE_UINT16(_stringTable); for (int ctr = 0; ctr < numLoops; ++ctr) { byte v = *tempPtr++; if ((v & 0x80) == 0) { total += v; } else { total += (v & 0x7f) << 3; } } } _bitMask = 0x80; if ((total & 3) != 0) _bitMask >>= (total & 3) * 2; _srcPos = _stringTable + (total >> 2) + READ_LE_UINT16(_stringTable + 2); // Final positioning to start of string for (;;) { if (readBit() == 0) break; _srcPos += 2; } return readBit() != 0; } // readCharatcer // Reads the next character from the input bit stream char StringData::readCharacter() { uint32 searchValue = 0; // Loop through an increasing number of bits for (uint8 numBits = 1; numBits <= 18; ++numBits) { searchValue |= readBit() << (numBits - 1); // Scan through list for a match for (int index = 0; _chars[index] != NULL; ++index) { if ((_chars[index]->_numBits == numBits) && (_chars[index]->_sequence == searchValue)) return _chars[index]->_ascii; } } error("Unknown bit sequence encountered when decoding string"); return 0; // for compilers that don't support NORETURN } void StringData::getString(uint16 stringId, char *dest, const char *hotspotName, const char *characterName, int hotspotArticle, int characterArticle) { debugC(ERROR_INTERMEDIATE, kLureDebugStrings, "StringData::getString stringId=%xh hotspot=%d,%s character=%d,%s", stringId, hotspotArticle, hotspotName, characterArticle, characterName); StringList &stringList = Resources::getReference().stringList(); char ch; strcpy(dest, ""); char *destPos = dest; stringId &= 0x1fff; // Strip off any article identifier if (stringId == 0) return; bool includeArticles = initPosition(stringId); uint32 charOffset = _srcPos - _stringTable; uint8 charBitMask = _bitMask; ch = readCharacter(); while (ch != '\0') { if (ch == '%') { // Copy over hotspot or action ch = readCharacter(); const char *p = (ch == '1') ? hotspotName : characterName; int article = !includeArticles ? 0 : ((ch == '1') ? hotspotArticle : characterArticle); if (p != NULL) { if (article > 0) { strcpy(destPos, stringList.getString(S_ARTICLE_LIST + article - 1)); strcat(destPos, p); } else { strcpy(destPos, p); } destPos += strlen(destPos); debugC(ERROR_DETAILED, kLureDebugStrings, "String data %xh/%.2xh val=%.2xh name=%s", charOffset, charBitMask, (uint8)ch, p); } } else if ((uint8) ch >= 0xa0) { const char *p = getName((uint8) ch - 0xa0); strcpy(destPos, p); destPos += strlen(p); debugC(ERROR_DETAILED, kLureDebugStrings, "String data %xh/%.2xh val=%.2xh sequence='%s'", charOffset, charBitMask, (uint8)ch, p); } else { *destPos++ = ch; debugC(ERROR_DETAILED, kLureDebugStrings, "String data %xh/%.2xh val=%.2xh char=%c", charOffset, charBitMask, (uint8)ch, ch); } charOffset = _srcPos - _stringTable; charBitMask = _bitMask; // WORKAROUND: Italian version had an unterminated Look description for Prisoner after cutting sack if ((charOffset == 0x1a08) && (charBitMask == 1) && (LureEngine::getReference().getLanguage() == Common::IT_ITA)) // Hardcode for end of string ch = '\0'; else // All other character reads ch = readCharacter(); } debugC(ERROR_DETAILED, kLureDebugStrings, "String data %xh/%.2xh val=%.2xh EOS", charOffset, charBitMask, ch); *destPos = '\0'; } // getName // Returns the name or fragment of word at the specified index in the names resource char *StringData::getName(uint8 nameIndex) { uint16 numNames = READ_LE_UINT16(_names->data()) / 2; if (nameIndex >= numNames) error("Invalid name index was passed to getCharacterName"); uint16 nameStart = READ_LE_UINT16(_names->data() + (nameIndex * 2)); return (char *) (_names->data() + nameStart); } } // namespace Lure