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 
24 #include "common/debug.h"
25 #include "common/endian.h"
26 #include "common/textconsole.h"
27 
28 #include "sky/disk.h"
29 #include "sky/logic.h"
30 #include "sky/text.h"
31 #include "sky/sky.h"
32 #include "sky/skydefs.h"
33 #include "sky/struc.h"
34 #include "sky/compact.h"
35 
36 namespace Sky {
37 
38 #define FIRST_TEXT_SEC	77
39 #define	FIRST_TEXT_BUFFER	274
40 #define LAST_TEXT_BUFFER	284
41 #define NO_OF_TEXT_SECTIONS	8	// 8 sections per language
42 #define	CHAR_SET_FILE	60150
43 #define MAX_SPEECH_SECTION	7
44 #define CHAR_SET_HEADER	128
45 #define	MAX_NO_LINES	10
46 
Text(Disk * skyDisk,SkyCompact * skyCompact)47 Text::Text(Disk *skyDisk, SkyCompact *skyCompact) {
48 	_skyDisk = skyDisk;
49 	_skyCompact = skyCompact;
50 
51 	initHuffTree();
52 
53 	_mainCharacterSet.addr = _skyDisk->loadFile(CHAR_SET_FILE);
54 	_mainCharacterSet.charHeight = MAIN_CHAR_HEIGHT;
55 	_mainCharacterSet.charSpacing = 0;
56 
57 	fnSetFont(0);
58 
59 	if (!SkyEngine::isDemo()) {
60 		_controlCharacterSet.addr = _skyDisk->loadFile(60520);
61 		_controlCharacterSet.charHeight = 12;
62 		_controlCharacterSet.charSpacing = 0;
63 
64 		_linkCharacterSet.addr = _skyDisk->loadFile(60521);
65 		_linkCharacterSet.charHeight = 12;
66 		_linkCharacterSet.charSpacing = 1;
67 	} else {
68 		_controlCharacterSet.addr = NULL;
69 		_linkCharacterSet.addr = NULL;
70 	}
71 }
72 
~Text()73 Text::~Text() {
74 	for (int i = FIRST_TEXT_BUFFER; i <= LAST_TEXT_BUFFER; i++)
75 		if (SkyEngine::_itemList[i]) {
76 			free(SkyEngine::_itemList[i]);
77 			SkyEngine::_itemList[i] = NULL;
78 		}
79 
80 	free(_mainCharacterSet.addr);
81 	free(_controlCharacterSet.addr);
82 	free(_linkCharacterSet.addr);
83 }
84 
fnSetFont(uint32 fontNr)85 void Text::fnSetFont(uint32 fontNr) {
86 	charSet *newCharSet;
87 
88 	switch (fontNr) {
89 	case 0:
90 		newCharSet = &_mainCharacterSet;
91 		break;
92 	case 1:
93 		newCharSet = &_linkCharacterSet;
94 		break;
95 	case 2:
96 		newCharSet = &_controlCharacterSet;
97 		break;
98 	default:
99 		error("Tried to set invalid font (%d)", fontNr);
100 	}
101 
102 	_curCharSet = fontNr;
103 	_characterSet = newCharSet->addr;
104 	_charHeight = (byte)newCharSet->charHeight;
105 	_dtCharSpacing = newCharSet->charSpacing;
106 }
107 
fnTextModule(uint32 textInfoId,uint32 textNo)108 void Text::fnTextModule(uint32 textInfoId, uint32 textNo) {
109 	fnSetFont(1);
110 	uint16* msgData = (uint16 *)_skyCompact->fetchCpt(textInfoId);
111 	DisplayedText textId = lowTextManager(textNo, msgData[1], msgData[2], 209, false);
112 	Logic::_scriptVariables[RESULT] = textId.compactNum;
113 	Compact *textCompact = _skyCompact->fetchCpt(textId.compactNum);
114 	textCompact->xcood = msgData[3];
115 	textCompact->ycood = msgData[4];
116 	fnSetFont(0);
117 }
118 
getText(uint32 textNr)119 void Text::getText(uint32 textNr) { //load text #"textNr" into textBuffer
120 	if (patchMessage(textNr))
121 		return;
122 
123 	uint32 sectionNo = (textNr & 0x0F000) >> 12;
124 
125 	if (SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] == NULL) { //check if already loaded
126 		debug(5, "Loading Text item(s) for Section %d", (sectionNo >> 2));
127 
128 		uint32 fileNo = sectionNo + ((SkyEngine::_systemVars.language * NO_OF_TEXT_SECTIONS) + 60600);
129 		SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] = (void **)_skyDisk->loadFile((uint16)fileNo);
130 	}
131 	uint8 *textDataPtr = (uint8 *)SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo];
132 
133 	uint32 offset = 0;
134 
135 	uint32 blockNr = textNr & 0xFE0;
136 	textNr &= 0x1F;
137 
138 	if (blockNr) {
139 		uint16 *blockPtr = (uint16 *)(textDataPtr + 4);
140 		uint32 nr32MsgBlocks = blockNr >> 5;
141 
142 		do {
143 			offset += READ_LE_UINT16(blockPtr);
144 			blockPtr++;
145 		} while (--nr32MsgBlocks);
146 	}
147 
148 	if (textNr) {
149 		uint8 *blockPtr = textDataPtr + blockNr + READ_LE_UINT16(textDataPtr);
150 		do {
151 			uint16 skipBytes = *blockPtr++;
152 			if (skipBytes & 0x80) {
153 				skipBytes &= 0x7F;
154 				skipBytes <<= 3;
155 			}
156 			offset += skipBytes;
157 		} while (--textNr);
158 	}
159 
160 	uint32 bitPos = offset & 3;
161 	offset >>= 2;
162 	offset += READ_LE_UINT16(textDataPtr + 2);
163 
164 	textDataPtr += offset;
165 
166 	//bit pointer: 0->8, 1->6, 2->4 ...
167 	bitPos ^= 3;
168 	bitPos++;
169 	bitPos <<= 1;
170 
171 	char *dest = (char *)_textBuffer;
172 	char textChar;
173 
174 	do {
175 		textChar = getTextChar(&textDataPtr, &bitPos);
176 		*dest++ = textChar;
177 	} while (textChar);
178 }
179 
fnPointerText(uint32 pointedId,uint16 mouseX,uint16 mouseY)180 void Text::fnPointerText(uint32 pointedId, uint16 mouseX, uint16 mouseY) {
181 	Compact *ptrComp = _skyCompact->fetchCpt(pointedId);
182 	DisplayedText text = lowTextManager(ptrComp->cursorText, TEXT_MOUSE_WIDTH, L_CURSOR, 242, false);
183 	Logic::_scriptVariables[CURSOR_ID] = text.compactNum;
184 	if (Logic::_scriptVariables[MENU]) {
185 		_mouseOfsY = TOP_LEFT_Y - 2;
186 		if (mouseX < 150)
187 			_mouseOfsX = TOP_LEFT_X + 24;
188 		else
189 			_mouseOfsX = TOP_LEFT_X  - 8 - text.textWidth;
190 	} else {
191 		_mouseOfsY = TOP_LEFT_Y - 10;
192 		if (mouseX < 150)
193 			_mouseOfsX = TOP_LEFT_X + 13;
194 		else
195 			_mouseOfsX = TOP_LEFT_X - 8 - text.textWidth;
196 	}
197 	Compact *textCompact = _skyCompact->fetchCpt(text.compactNum);
198 	logicCursor(textCompact, mouseX, mouseY);
199 }
200 
logicCursor(Compact * textCompact,uint16 mouseX,uint16 mouseY)201 void Text::logicCursor(Compact *textCompact, uint16 mouseX, uint16 mouseY) {
202 	textCompact->xcood = (uint16)(mouseX + _mouseOfsX);
203 	textCompact->ycood = (uint16)(mouseY + _mouseOfsY);
204 	if (textCompact->ycood < TOP_LEFT_Y)
205 		textCompact->ycood = TOP_LEFT_Y;
206 }
207 
getTextBit(uint8 ** data,uint32 * bitPos)208 bool Text::getTextBit(uint8 **data, uint32 *bitPos) {
209 	if (*bitPos) {
210 		(*bitPos)--;
211 	} else {
212 		(*data)++;
213 		*bitPos = 7;
214 	}
215 
216 	return (bool)(((**data) >> (*bitPos)) & 1);
217 }
218 
getTextChar(uint8 ** data,uint32 * bitPos)219 char Text::getTextChar(uint8 **data, uint32 *bitPos) {
220 	int pos = 0;
221 	while (1) {
222 		if (getTextBit(data, bitPos))
223 			pos = _huffTree[pos].rChild;
224 		else
225 			pos = _huffTree[pos].lChild;
226 
227 		if (_huffTree[pos].lChild == 0 && _huffTree[pos].rChild == 0) {
228 			return _huffTree[pos].value;
229 		}
230 	}
231 }
232 
displayText(uint32 textNum,uint8 * dest,bool center,uint16 pixelWidth,uint8 color)233 DisplayedText Text::displayText(uint32 textNum, uint8 *dest, bool center, uint16 pixelWidth, uint8 color) {
234 	//Render text into buffer *dest
235 	getText(textNum);
236 	return displayText(_textBuffer, dest, center, pixelWidth, color);
237 }
238 
displayText(char * textPtr,uint8 * dest,bool center,uint16 pixelWidth,uint8 color)239 DisplayedText Text::displayText(char *textPtr, uint8 *dest, bool center, uint16 pixelWidth, uint8 color) {
240 	//Render text pointed to by *textPtr in buffer *dest
241 	uint32 centerTable[10];
242 	uint16 lineWidth = 0;
243 
244 	uint32 numLines = 0;
245 	_numLetters = 2;
246 
247 	// work around bug #778105 (line width exceeded)
248 	char *tmpPtr = strstr(textPtr, "MUND-BEATMUNG!");
249 	if (tmpPtr)
250 		strcpy(tmpPtr, "MUND BEATMUNG!");
251 
252 	// work around bug #1151924 (line width exceeded when talking to gardener using spanish text)
253 	// This text apparently only is broken in the floppy versions, the CD versions contain
254 	// the correct string "MANIFESTACION - ARTISTICA.", which doesn't break the algorithm/game.
255 	tmpPtr = strstr(textPtr, "MANIFESTACION-ARTISTICA.");
256 	if (tmpPtr)
257 		strcpy(tmpPtr, "MANIFESTACION ARTISTICA.");
258 
259 	char *curPos = textPtr;
260 	char *lastSpace = textPtr;
261 	uint8 textChar = (uint8)*curPos++;
262 
263 	while (textChar >= 0x20) {
264 		if ((_curCharSet == 1) && (textChar >= 0x80))
265 			textChar = 0x20;
266 
267 		textChar -= 0x20;
268 		if (textChar == 0) {
269 			lastSpace = curPos; //keep track of last space
270 			centerTable[numLines] = lineWidth;
271 		}
272 
273 		lineWidth += _characterSet[textChar];	//add character width
274 		lineWidth += (uint16)_dtCharSpacing;	//include character spacing
275 
276 		if (pixelWidth <= lineWidth) {
277 			if (*(lastSpace-1) == 10)
278 				error("line width exceeded");
279 
280 			*(lastSpace-1) = 10;
281 			lineWidth = 0;
282 			numLines++;
283 			curPos = lastSpace;	//go back for new count
284 		}
285 
286 		textChar = (uint8)*curPos++;
287 		_numLetters++;
288 	}
289 
290 	uint32 dtLastWidth = lineWidth;	//save width of last line
291 	centerTable[numLines] = lineWidth; //and update centering table
292 	numLines++;
293 
294 	if (numLines > MAX_NO_LINES)
295 		error("Maximum no. of lines exceeded");
296 
297 	uint32 dtLineSize = pixelWidth * _charHeight;
298 	uint32 numBytes = (dtLineSize * numLines) + sizeof(DataFileHeader) + 4;
299 
300 	if (!dest)
301 		dest = (uint8 *)malloc(numBytes);
302 
303 	// clear text sprite buffer
304 	memset(dest + sizeof(DataFileHeader), 0, numBytes - sizeof(DataFileHeader));
305 
306 	//make the header
307 	((DataFileHeader *)dest)->s_width = pixelWidth;
308 	((DataFileHeader *)dest)->s_height = (uint16)(_charHeight * numLines);
309 	((DataFileHeader *)dest)->s_sp_size = (uint16)(pixelWidth * _charHeight * numLines);
310 	((DataFileHeader *)dest)->s_offset_x = 0;
311 	((DataFileHeader *)dest)->s_offset_y = 0;
312 
313 	//reset position
314 	curPos = textPtr;
315 
316 	uint8 *curDest = dest +  sizeof(DataFileHeader); //point to where pixels start
317 	byte *prevDest = curDest;
318 	uint32 *centerTblPtr = centerTable;
319 
320 	do {
321 		if (center) {
322 			uint32 width = (pixelWidth - *centerTblPtr) >> 1;
323 			centerTblPtr++;
324 			curDest += width;
325 		}
326 
327 		textChar = (uint8)*curPos++;
328 		while (textChar >= 0x20) {
329 			makeGameCharacter(textChar - 0x20, _characterSet, curDest, color, pixelWidth);
330 			textChar = *curPos++;
331 		}
332 
333 		prevDest = curDest = prevDest + dtLineSize;	//start of last line + start of next
334 
335 	} while (textChar >= 10);
336 
337 	DisplayedText ret;
338 	memset(&ret, 0, sizeof(ret));
339 	ret.textData = dest;
340 	ret.textWidth = dtLastWidth;
341 	return ret;
342 }
343 
makeGameCharacter(uint8 textChar,uint8 * charSetPtr,uint8 * & dest,uint8 color,uint16 bufPitch)344 void Text::makeGameCharacter(uint8 textChar, uint8 *charSetPtr, uint8 *&dest, uint8 color, uint16 bufPitch) {
345 	bool maskBit, dataBit;
346 	uint8 charWidth = (uint8)((*(charSetPtr + textChar)) + 1 - _dtCharSpacing);
347 	uint16 data, mask;
348 	byte *charSpritePtr = charSetPtr + (CHAR_SET_HEADER + ((_charHeight << 2) * textChar));
349 	byte *startPos = dest;
350 	byte *curPos = startPos;
351 
352 	for (int i = 0; i < _charHeight; i++) {
353 		byte *prevPos = curPos;
354 
355 		data = READ_BE_UINT16(charSpritePtr);
356 		mask = READ_BE_UINT16(charSpritePtr + 2);
357 		charSpritePtr += 4;
358 
359 		for (int j = 0; j < charWidth; j++) {
360 
361 			maskBit = (mask & 0x8000) != 0; //check mask
362 			mask <<= 1;
363 			dataBit = (data & 0x8000) != 0; //check data
364 			data <<= 1;
365 
366 			if (maskBit) {
367 				if (dataBit)
368 					*curPos = color;
369 				else
370 					*curPos = 240; //black edge
371 			}
372 			curPos++;
373 		}
374 		//advance a line
375 		curPos = prevPos + bufPitch;
376 	}
377 	//update position
378 	dest = startPos + charWidth + _dtCharSpacing * 2 - 1;
379 }
380 
lowTextManager(uint32 textNum,uint16 width,uint16 logicNum,uint8 color,bool center)381 DisplayedText Text::lowTextManager(uint32 textNum, uint16 width, uint16 logicNum, uint8 color, bool center) {
382 	getText(textNum);
383 	DisplayedText textInfo = displayText(_textBuffer, NULL, center, width, color);
384 
385 	uint32 compactNum = FIRST_TEXT_COMPACT;
386 	Compact *cpt = _skyCompact->fetchCpt(compactNum);
387 	while (cpt->status != 0) {
388 		compactNum++;
389 		cpt = _skyCompact->fetchCpt(compactNum);
390 	}
391 
392 	cpt->flag = (uint16)(compactNum - FIRST_TEXT_COMPACT) + FIRST_TEXT_BUFFER;
393 
394 	if (SkyEngine::_itemList[cpt->flag])
395 		free(SkyEngine::_itemList[cpt->flag]);
396 
397 	SkyEngine::_itemList[cpt->flag] = textInfo.textData;
398 
399 	cpt->logic = logicNum;
400 	cpt->status = ST_LOGIC | ST_FOREGROUND | ST_RECREATE;
401 	cpt->screen = (uint16) Logic::_scriptVariables[SCREEN];
402 
403 	textInfo.compactNum = (uint16)compactNum;
404 	return textInfo;
405 }
406 
changeTextSpriteColor(uint8 * sprData,uint8 newCol)407 void Text::changeTextSpriteColor(uint8 *sprData, uint8 newCol) {
408 	DataFileHeader *header = (DataFileHeader *)sprData;
409 	sprData += sizeof(DataFileHeader);
410 	for (uint16 cnt = 0; cnt < header->s_sp_size; cnt++)
411 		if (sprData[cnt] >= 241)
412 			sprData[cnt] = newCol;
413 }
414 
giveCurrentCharSet()415 uint32 Text::giveCurrentCharSet() {
416 	return _curCharSet;
417 }
418 
initHuffTree()419 void Text::initHuffTree() {
420 	switch (SkyEngine::_systemVars.gameVersion) {
421 	case 109:
422 		_huffTree = _huffTree_00109;
423 		break;
424 	case 272: // FIXME: Extract data
425 	case 267:
426 		_huffTree = _huffTree_00267;
427 		break;
428 	case 288:
429 		_huffTree = _huffTree_00288;
430 		break;
431 	case 303:
432 		_huffTree = _huffTree_00303;
433 		break;
434 	case 331:
435 		_huffTree = _huffTree_00331;
436 		break;
437 	case 348:
438 		_huffTree = _huffTree_00348;
439 		break;
440 	case 365:
441 		_huffTree = _huffTree_00365;
442 		break;
443 	case 368:
444 		_huffTree = _huffTree_00368;
445 		break;
446 	case 372:
447 		_huffTree = _huffTree_00372;
448 		break;
449 	default:
450 		error("Unknown game version %d", SkyEngine::_systemVars.gameVersion);
451 	}
452 }
453 
patchMessage(uint32 textNum)454 bool Text::patchMessage(uint32 textNum) {
455 	uint16 patchIdx = _patchLangIdx[SkyEngine::_systemVars.language];
456 	uint16 patchNum = _patchLangNum[SkyEngine::_systemVars.language];
457 	for (uint16 cnt = 0; cnt < patchNum; cnt++) {
458 		if (_patchedMessages[cnt + patchIdx].textNr == textNum) {
459 			strcpy(_textBuffer, _patchedMessages[cnt + patchIdx].text);
460 			return true;
461 		}
462 	}
463 	return false;
464 }
465 
466 const PatchMessage Text::_patchedMessages[NUM_PATCH_MSG] = {
467 	{ 28724, "Testo e Parlato" }, // - italian
468 	{ 28707, "Solo Testo" },
469 	{ 28693, "Solo Parlato" },
470 	{ 28724, "Text och tal" }, // - swedish
471 	{ 28707, "Endast text" },
472 	{ 28693, "Endast tal" },
473 	{ 28686, "Musikvolym" },
474 	{ 4336, "Wir befinden uns EINHUNDERTZWANZIG METER #ber dem ERBODEN!" }, // - german
475 	{ 28686, "Volume de musique" }, // - french
476 };
477 
478 const uint16 Text::_patchLangIdx[8] = {
479 	0xFFFF, // SKY_ENGLISH
480 	7,		// SKY_GERMAN
481 	8,		// SKY_FRENCH
482 	0xFFFF, // SKY_USA
483 	3,		// SKY_SWEDISH
484 	0,		// SKY_ITALIAN
485 	0xFFFF, // SKY_PORTUGUESE
486 	0xFFFF  // SKY_SPANISH
487 };
488 
489 const uint16 Text::_patchLangNum[8] = {
490 	0, // SKY_ENGLISH
491 	1, // SKY_GERMAN
492 	1, // SKY_FRENCH
493 	0, // SKY_USA
494 	4, // SKY_SWEDISH
495 	3, // SKY_ITALIAN
496 	0, // SKY_PORTUGUESE
497 	0  // SKY_SPANISH
498 };
499 
500 } // End of namespace Sky
501