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/textconsole.h"
25 #include "common/util.h"
26 
27 #include "sword1/objectman.h"
28 #include "sword1/sworddefs.h"
29 #include "sword1/swordres.h"
30 #include "sword1/sword1.h"
31 
32 namespace Sword1 {
33 
ObjectMan(ResMan * pResourceMan)34 ObjectMan::ObjectMan(ResMan *pResourceMan) {
35 	_resMan = pResourceMan;
36 }
37 
initialize()38 void ObjectMan::initialize() {
39 	uint16 cnt;
40 	for (cnt = 0; cnt < TOTAL_SECTIONS; cnt++)
41 		_liveList[cnt] = 0; // we don't need to close the files here. When this routine is
42 	                        // called, the memory was flushed() anyways, so these resources
43 	                        // already *are* closed.
44 
45 	_liveList[128] = _liveList[129] = _liveList[130] = _liveList[131] = _liveList[133] =
46 	                                      _liveList[134] = _liveList[145] = _liveList[146] = _liveList[TEXT_sect] = 1;
47 
48 	for (cnt = 0; cnt < TOTAL_SECTIONS; cnt++) {
49 		if (_liveList[cnt])
50 			_cptData[cnt] = (uint8 *)_resMan->cptResOpen(_objectList[cnt]) + sizeof(Header);
51 		else
52 			_cptData[cnt] = NULL;
53 	}
54 }
55 
~ObjectMan()56 ObjectMan::~ObjectMan() {
57 	for (uint16 cnt = 0; cnt < TOTAL_SECTIONS; cnt++)
58 		if (_liveList[cnt])
59 			_resMan->resClose(_objectList[cnt]);
60 }
61 
sectionAlive(uint16 section)62 bool ObjectMan::sectionAlive(uint16 section) {
63 	return (_liveList[section] > 0);
64 }
65 
megaEntering(uint16 section)66 void ObjectMan::megaEntering(uint16 section) {
67 	_liveList[section]++;
68 	if (_liveList[section] == 1)
69 		_cptData[section] = ((uint8 *)_resMan->cptResOpen(_objectList[section])) + sizeof(Header);
70 }
71 
megaLeaving(uint16 section,int id)72 void ObjectMan::megaLeaving(uint16 section, int id) {
73 	if (_liveList[section] == 0)
74 		error("mega %d is leaving empty section %d", id, section);
75 	_liveList[section]--;
76 	if ((_liveList[section] == 0) && (id != PLAYER)) {
77 		_resMan->resClose(_objectList[section]);
78 		_cptData[section] = NULL;
79 	}
80 	/* if the player is leaving the section then we have to close the resources after
81 	   mainloop ends, because the screen will still need the resources*/
82 }
83 
fnCheckForTextLine(uint32 textId)84 uint8 ObjectMan::fnCheckForTextLine(uint32 textId) {
85 	uint8 retVal = 0;
86 	if (!_textList[textId / ITM_PER_SEC][0])
87 		return 0; // section does not exist
88 
89 	uint8 lang = SwordEngine::_systemVars.language;
90 	uint32 *textData = (uint32 *)((uint8 *)_resMan->openFetchRes(_textList[textId / ITM_PER_SEC][lang]) + sizeof(Header));
91 	if ((textId & ITM_ID) < _resMan->readUint32(textData)) {
92 		textData++;
93 		if (textData[textId & ITM_ID])
94 			retVal = 1;
95 	}
96 	_resMan->resClose(_textList[textId / ITM_PER_SEC][lang]);
97 	return retVal;
98 }
99 
lockText(uint32 textId)100 char *ObjectMan::lockText(uint32 textId) {
101 	uint8 lang = SwordEngine::_systemVars.language;
102 	char *text = lockText(textId, lang);
103 	if (text == 0) {
104 		if (lang != BS1_ENGLISH) {
105 			text = lockText(textId, BS1_ENGLISH);
106 			if (text != 0)
107 				warning("Missing translation for textId %u (\"%s\")", textId, text);
108 			unlockText(textId, BS1_ENGLISH);
109 		}
110 
111 		return _missingSubTitleStr;
112 	}
113 	return text;
114 }
115 
lockText(uint32 textId,uint8 lang)116 char *ObjectMan::lockText(uint32 textId, uint8 lang) {
117 	char *addr = (char *)_resMan->openFetchRes(_textList[textId / ITM_PER_SEC][lang]);
118 	if (addr == 0)
119 		return NULL;
120 	addr += sizeof(Header);
121 	if ((textId & ITM_ID) >= _resMan->readUint32(addr)) {
122 		// Workaround for missing sentences in some languages in the demo.
123 		switch(textId) {
124 		case 8455194:
125 			return const_cast<char *>(_translationId8455194[lang]);
126 		case 8455195:
127 			return const_cast<char *>(_translationId8455195[lang]);
128 		case 8455196:
129 			return const_cast<char *>(_translationId8455196[lang]);
130 		case 8455197:
131 			return const_cast<char *>(_translationId8455197[lang]);
132 		case 8455198:
133 			return const_cast<char *>(_translationId8455198[lang]);
134 		case 8455199:
135 			return const_cast<char *>(_translationId8455199[lang]);
136 		case 8455200:
137 			return const_cast<char *>(_translationId8455200[lang]);
138 		case 8455201:
139 			return const_cast<char *>(_translationId8455201[lang]);
140 		case 8455202:
141 			return const_cast<char *>(_translationId8455202[lang]);
142 		case 8455203:
143 			return const_cast<char *>(_translationId8455203[lang]);
144 		case 8455204:
145 			return const_cast<char *>(_translationId8455204[lang]);
146 		case 8455205:
147 			return const_cast<char *>(_translationId8455205[lang]);
148 		case 6488080:
149 			return const_cast<char *>(_translationId6488080[lang]);
150 		case 6488081:
151 			return const_cast<char *>(_translationId6488081[lang]);
152 		case 6488082:
153 			return const_cast<char *>(_translationId6488082[lang]);
154 		case 6488083:
155 			return const_cast<char *>(_translationId6488083[lang]);
156 		}
157 
158 		warning("ObjectMan::lockText(%d): only %d texts in file", textId & ITM_ID, _resMan->readUint32(addr));
159 		return NULL;
160 	}
161 	uint32 offset = _resMan->readUint32(addr + ((textId & ITM_ID) + 1) * 4);
162 	if (offset == 0) {
163 		// Workaround bug for missing sentence in some languages in Syria (see bug #1977094).
164 		// We use the hardcoded text in this case.
165 		if (textId == 2950145)
166 			return const_cast<char *>(_translationId2950145[lang]);
167 
168 		warning("ObjectMan::lockText(%d): text number has no text lines", textId);
169 		return NULL;
170 	}
171 	return addr + offset;
172 }
173 
unlockText(uint32 textId)174 void ObjectMan::unlockText(uint32 textId) {
175 	unlockText(textId, SwordEngine::_systemVars.language);
176 }
177 
unlockText(uint32 textId,uint8 lang)178 void ObjectMan::unlockText(uint32 textId, uint8 lang) {
179 	_resMan->resClose(_textList[textId / ITM_PER_SEC][lang]);
180 }
181 
lastTextNumber(int section)182 uint32 ObjectMan::lastTextNumber(int section) {
183 	uint8 *data = (uint8 *)_resMan->openFetchRes(_textList[section][SwordEngine::_systemVars.language]) + sizeof(Header);
184 	uint32 result = _resMan->readUint32(data) - 1;
185 	_resMan->resClose(_textList[section][SwordEngine::_systemVars.language]);
186 	return result;
187 }
188 
fetchObject(uint32 id)189 Object *ObjectMan::fetchObject(uint32 id) {
190 	uint8 *addr = _cptData[id / ITM_PER_SEC];
191 	if (!addr)
192 		error("fetchObject: section %d is not open", id / ITM_PER_SEC);
193 	id &= ITM_ID;
194 	// DON'T do endian conversion here. it's already done.
195 	return (Object *)(addr + * (uint32 *)(addr + (id + 1) * 4));
196 }
197 
fetchNoObjects(int section)198 uint32 ObjectMan::fetchNoObjects(int section) {
199 	if (_cptData[section] == NULL)
200 		error("fetchNoObjects: section %d is not open", section);
201 	return *(uint32 *)_cptData[section];
202 }
203 
closeSection(uint32 screen)204 void ObjectMan::closeSection(uint32 screen) {
205 	if (_liveList[screen] == 0)  // close the section that PLAYER has just left, if it's empty now
206 		_resMan->resClose(_objectList[screen]);
207 }
208 
loadLiveList(uint16 * src)209 void ObjectMan::loadLiveList(uint16 *src) {
210 	for (uint16 cnt = 0; cnt < TOTAL_SECTIONS; cnt++) {
211 		if (_liveList[cnt]) {
212 			_resMan->resClose(_objectList[cnt]);
213 			_cptData[cnt] = NULL;
214 		}
215 		_liveList[cnt] = src[cnt];
216 		if (_liveList[cnt])
217 			_cptData[cnt] = ((uint8 *)_resMan->cptResOpen(_objectList[cnt])) + sizeof(Header);
218 	}
219 }
220 
saveLiveList(uint16 * dest)221 void ObjectMan::saveLiveList(uint16 *dest) {
222 	memcpy(dest, _liveList, TOTAL_SECTIONS * sizeof(uint16));
223 }
224 
225 // String displayed when a subtitle sentence is missing in the cluster file.
226 // It happens with at least one sentence in Syria in some languages (see bug
227 // #1977094).
228 // Note: an empty string or a null pointer causes a crash.
229 
230 char ObjectMan::_missingSubTitleStr[] = " ";
231 
232 // Missing translation for textId 2950145 (see bug #1977094).
233 // Currently text is missing for Portuguese languages. (It's possible that it
234 // is not needed. The English version of the game does not include Portuguese
235 // so I cannot check.)
236 
237 const char *const ObjectMan::_translationId2950145[7] = {
238 	"Oh?",     // English (not needed)
239 	"Quoi?",   // French
240 	"Oh?",     // German
241 	"Eh?",     // Italian
242 	"\277Eh?", // Spanish
243 	"Ano?",    // Czech
244 	NULL       // Portuguese
245 };
246 
247 // The translations for the next texts are missing in the demo but are present
248 // in the full game. The translations were therefore extracted from the full game.
249 
250 // Missing translation for textId 8455194 (in the demo).
251 const char *const ObjectMan::_translationId8455194[7] = {
252 	NULL, // "Who was the guy you were supposed to meet?",              // English (not needed)
253 	"Qui \351tait l'homme que vous deviez rencontrer?",                 // French
254 	"Wer war der Typ, den Du treffen wolltest?",                        // German
255 	"Chi dovevi incontrare?",                                           // Italian
256 	"\277Qui\351n era el hombre con el que ten\355as que encontrarte?", // Spanish
257 	NULL,                                                               // Czech
258 	NULL                                                                // Portuguese
259 };
260 
261 // Missing translation for textId 8455195 (in the demo).
262 const char *const ObjectMan::_translationId8455195[7] = {
263 	NULL, // "His name was Plantard. I didn't know him, but he called me last night.",                    // English (not needed)
264 	"Son nom \351tait Plantard. Je ne le connaissais pas, mais il m'avait t\351l\351phon\351 la veille.", // French
265 	"Sein Name war Plantard. Ich kannte ihn nicht, aber er hat mich letzte Nacht angerufen.",             // German
266 	"Si chiamava Plantard. Mi ha chiamato ieri sera, ma non lo conoscevo.",                               // Italian
267 	"Su nombre era Plantard. Yo no lo conoc\355a pero \351l me llam\363 ayer por la noche.",              // Spanish
268 	NULL,                                                                                                 // Czech
269 	NULL                                                                                                  // Portuguese
270 };
271 
272 // Missing translation for textId 8455196 (in the demo).
273 const char *const ObjectMan::_translationId8455196[7] = {
274 	NULL, // "He said he had a story which would interest me.",           // English (not needed)
275 	"Il a dit qu'il avait une histoire qui devrait m'int\351resser.",     // French
276 	"Er sagte, er h\344tte eine Story, die mich interessieren w\374rde.", // German
277 	"Mi disse che aveva una storia che mi poteva interessare.",           // Italian
278 	"Dijo que ten\355a una historia que me interesar\355a.",              // Spanish
279 	NULL,                                                                 // Czech
280 	NULL                                                                  // Portuguese
281 };
282 
283 // Missing translation for textId 8455197 (in the demo).
284 const char *const ObjectMan::_translationId8455197[7] = {
285 	NULL, // "He asked me to meet him at the caf\351.",          // English (not needed)
286 	"Il m'a demand\351 de le rencontrer au caf\351.",            // French
287 	"Er fragte mich, ob wir uns im Caf\351 treffen k\366nnten.", // German
288 	"Mi chiese di incontrarci al bar.",                          // Italian
289 	"Me pidi\363 que nos encontr\341ramos en el caf\351.",       // Spanish
290 	NULL,                                                        // Czech
291 	NULL                                                         // Portuguese
292 };
293 
294 // Missing translation for textId 8455198 (in the demo).
295 const char *const ObjectMan::_translationId8455198[7] = {
296 	NULL, // "I guess I'll never know what he wanted to tell me...",  // English (not needed)
297 	"Je suppose que je ne saurai jamais ce qu'il voulait me dire...", // French
298 	"Ich werde wohl nie erfahren, was er mir sagen wollte...",        // German
299 	"Penso che non sapr\362 mai che cosa voleva dirmi...",            // Italian
300 	"Supongo que nunca sabr\351 qu\351 me quer\355a contar...",       // Spanish
301 	NULL,                                                             // Czech
302 	NULL                                                              // Portuguese
303 };
304 
305 // Missing translation for textId 8455199 (in the demo).
306 const char *const ObjectMan::_translationId8455199[7] = {
307 	NULL, // "Not unless you have Rosso's gift for psychic interrogation.",           // English (not needed)
308 	"Non, \340 moins d'avoir les dons de Rosso pour les interrogatoires psychiques.", // French
309 	"Es sei denn, Du h\344ttest Rosso's Gabe der parapsychologischen Befragung.",     // German
310 	"A meno che tu non riesca a fare interrogatori telepatici come Rosso.",           // Italian
311 	"A no ser que tengas el don de Rosso para la interrogaci\363n ps\355quica.",      // Spanish
312 	NULL,                                                                             // Czech
313 	NULL                                                                              // Portuguese
314 };
315 
316 // Missing translation for textId 8455200 (in the demo).
317 const char *const ObjectMan::_translationId8455200[7] = {
318 	NULL, // "How did Plantard get your name?",     // English (not needed)
319 	"Comment Plantard a-t-il obtenu votre nom?",    // French
320 	"Woher hat Plantard Deinen Namen?",             // German
321 	"Come ha fatto Plantard a sapere il tuo nome?", // Italian
322 	"\277C\363mo consigui\363 Plantard tu nombre?", // Spanish
323 	NULL,                                           // Czech
324 	NULL                                            // Portuguese
325 };
326 
327 // Missing translation for textId 8455201 (in the demo).
328 const char *const ObjectMan::_translationId8455201[7] = {
329 	NULL, // "Through the newspaper - La Libert\351.", // English (not needed)
330 	"Par mon journal... La Libert\351.",      // French
331 	"\334ber die Zeitung - La Libert\351.",   // German
332 	"Tramite il giornale La Libert\351.",     // Italian
333 	"Por el peri\363dico - La Libert\351.",   // Spanish
334 	NULL,                                     // Czech
335 	NULL                                      // Portuguese
336 };
337 
338 // Missing translation for textId 8455202 (in the demo).
339 const char *const ObjectMan::_translationId8455202[7] = {
340 	NULL, // "I'd written an article linking two unsolved murders, one in Italy, the other in Japan.",                                                    // English (not needed)
341 	"J'ai \351crit un article o\371 je faisais le lien entre deux meurtres inexpliqu\351s, en Italie et au japon.",                                       // French
342 	"Ich habe einen Artikel geschrieben, in dem ich zwei ungel\366ste Morde miteinander in Verbindung bringe, einen in Italien, einen anderen in Japan.", // German
343 	"Ho scritto un articolo che metteva in collegamento due omicidi insoluti in Italia e Giappone.",                                                      // Italian
344 	"Yo hab\355a escrito un art\355culo conectando dos asesinatos sin resolver, uno en Italia, el otro en Jap\363n.",                                     // Spanish
345 	NULL,                                                                                                                                                 // Czech
346 	NULL                                                                                                                                                  // Portuguese
347 };
348 
349 // Missing translation for textId 8455203 (in the demo).
350 const char *const ObjectMan::_translationId8455203[7] = {
351 	NULL, // "The cases were remarkably similar...",      // English (not needed)
352 	"Les affaires \351taient quasiment identiques...",    // French
353 	"Die F\344lle sind sich bemerkenswert \344hnlich...", // German
354 	"I casi erano sorprendentemente uguali...",           // Italian
355 	"Los casos eran incre\355blemente parecidos...",      // Spanish
356 	NULL,                                                 // Czech
357 	NULL                                                  // Portuguese
358 };
359 
360 // Missing translation for textId 8455204 (in the demo).
361 const char *const ObjectMan::_translationId8455204[7] = {
362 	NULL, // "...a wealthy victim, no apparent motive, and a costumed killer.",              // English (not needed)
363 	"...une victime riche, pas de motif apparent, et un tueur d\351guis\351.",              // French
364 	"...ein wohlhabendes Opfer, kein offensichtliches Motiv, und ein verkleideter Killer.", // German
365 	"...una vittima ricca, nessun motivo apparente e un assassino in costume.",             // Italian
366 	"...una v\355ctima rica, sin motivo aparente, y un asesino disfrazado.",                // Spanish
367 	NULL,                                                                                   // Czech
368 	NULL                                                                                    // Portuguese
369 };
370 
371 // Missing translation for textId 8455205 (in the demo).
372 const char *const ObjectMan::_translationId8455205[7] = {
373 	NULL, // "Plantard said he could supply me with more information.",        // English (not needed)
374 	"Plantard m'a dit qu'il pourrait me fournir des renseignements.",          // French
375 	"Plantard sagte, er k\366nne mir weitere Informationen beschaffen.",       // German
376 	"Plantard mi disse che mi avrebbe fornito ulteriori informazioni.",        // Italian
377 	"Plantard dijo que \351l me pod\355a proporcionar m\341s informaci\363n.", // Spanish
378 	NULL,                                                                      // Czech
379 	NULL                                                                       // Portuguese
380 };
381 
382 // Missing translation for textId 6488080 (in the demo).
383 const char *const ObjectMan::_translationId6488080[7] = {
384 	NULL, // "I wasn't going to head off all over Paris until I'd investigated some more.", // English (not needed)
385 	"Je ferais mieux d'enqu\351ter un peu par ici avant d'aller me promener ailleurs.",     // French
386 	"Ich durchquere nicht ganz Paris, bevor ich etwas mehr herausgefunden habe.",           // German
387 	"Non mi sarei incamminato per tutta Parigi prima di ulteriori indagini.",               // Italian
388 	"No iba a ponerme a recorrer Par\355s sin haber investigado un poco m\341s.",           // Spanish
389 	NULL,                                                                                   // Czech
390 	NULL                                                                                    // Portuguese
391 };
392 
393 // The next three sentences are specific to the demo and only the english text is present.
394 // The translations were provided by:
395 // French: Thierry Crozat
396 // German: Simon Sawatzki
397 // Italian: Matteo Angelino
398 // Spanish: Tomás Maidagan
399 
400 // Missing translation for textId 6488081 (in the demo).
401 const char *const ObjectMan::_translationId6488081[7] = {
402 	NULL, // "I wasn't sure what I was going to do when I caught up with that clown...", // English (not needed)
403 	"Je ne savais pas ce que je ferais quand je rattraperais le clown...",               // French
404 	"Ich wu\337te nicht, worauf ich mich einlie\337, als ich dem Clown nachjagte...",    // German
405 	"Non sapevo cosa avrei fatto una volta raggiunto quel clown...",                     // Italian
406 	"No sab\355a muy bien qu\351 es lo que har\355a cuando alcanzara al payaso...",      // Spanish
407 	NULL,                                                                                // Czech
408 	NULL                                                                                 // Portuguese
409 };
410 
411 // Missing translation for textId 6488082 (in the demo).
412 const char *const ObjectMan::_translationId6488082[7] = {
413 	NULL, // "...but before I knew it, I was drawn into a desperate race between two ruthless enemies.",                             // English (not needed)
414 	"...mais avant de m'en rendre compte je me retrouvais happ\351 dans une course effr\351n\351e entre deux ennemis impitoyables.", // French
415 	"... doch bevor ich mich versah, war ich inmitten eines Wettlaufs von zwei r\374cksichtslosen Feinden.",                         // German
416 	"... ma prima che me ne rendessi conto, fui trascinato in una corsa disperata con due spietati nemici.",                         // Italian
417 	"...pero sin darme cuenta, acab\351 en medio de una desesperada carrera entre dos despiadados enemigos.",                        // Spanish
418 	NULL,                                                                                                                            // Czech
419 	NULL                                                                                                                             // Portuguese
420 };
421 
422 // Missing translation for textId 6488083 (in the demo).
423 const char *const ObjectMan::_translationId6488083[7] = {
424 	NULL, // "The goal: the mysterious power of the Broken Sword.",    // English (not needed)
425 	"Le but: les pouvoirs myst\351rieux de l'\351p\351e bris\351e.",   // French
426 	"Das Ziel: die geheimnisvolle Macht des zerbrochenen Schwertes.",  // German
427 	"Obiettivo: il misterioso potere della Spada spezzata.",           // Italian
428 	"El objetivo: el misterioso poder de la Espada Rota.",             // Spanish
429 	NULL,                                                              // Czech
430 	NULL                                                               // Portuguese
431 };
432 
433 } // End of namespace Sword1
434